mirror of
https://github.com/datashard/snapshot.git
synced 2024-11-22 06:02:29 +00:00
fully working plugin, just missing more features
This commit is contained in:
parent
1831604a83
commit
faeb258eb9
6 changed files with 184 additions and 207 deletions
213
src/index.js
213
src/index.js
|
@ -1,211 +1,10 @@
|
|||
'use strict'
|
||||
"use strict";
|
||||
|
||||
/* global cy, Cypress */
|
||||
const itsName = require('its-name')
|
||||
const { initStore } = require('snap-shot-store')
|
||||
const la = require('lazy-ass')
|
||||
const is = require('check-more-types')
|
||||
const compare = require('snap-shot-compare')
|
||||
const path = require('path')
|
||||
// global cy, Cypress
|
||||
|
||||
const {
|
||||
serializeDomElement,
|
||||
serializeToHTML,
|
||||
identity,
|
||||
countSnapshots
|
||||
} = require('./utils')
|
||||
|
||||
const DEFAULT_CONFIG_OPTIONS = {
|
||||
// using relative snapshots requires a simple
|
||||
// 'readFileMaybe' plugin to be configured
|
||||
// see https://on.cypress.io/task#Read-a-file-that-might-not-exist
|
||||
useRelativeSnapshots: false,
|
||||
snapshotFileName: 'snapshots.js'
|
||||
}
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
function compareValues ({ expected, value }) {
|
||||
const noColor = true
|
||||
const json = true
|
||||
return compare({ expected, value, noColor, json })
|
||||
}
|
||||
|
||||
function registerCypressSnapshot () {
|
||||
la(is.fn(global.before), 'missing global before function')
|
||||
la(is.fn(global.after), 'missing global after function')
|
||||
la(is.object(global.Cypress), 'missing Cypress object')
|
||||
|
||||
const useRelative = Cypress.config('useRelativeSnapshots')
|
||||
const config = {
|
||||
useRelativeSnapshots: useRelative === undefined ? DEFAULT_CONFIG_OPTIONS.useRelativeSnapshots : useRelative,
|
||||
snapshotFileName: Cypress.config('snapshotFileName') || DEFAULT_CONFIG_OPTIONS.snapshotFileName
|
||||
}
|
||||
|
||||
console.log('registering @cypress/snapshot')
|
||||
|
||||
let storeSnapshot
|
||||
|
||||
// for each full test name, keeps number of snapshots
|
||||
// allows using multiple snapshots inside single test
|
||||
// without confusing them
|
||||
// eslint-disable-next-line immutable/no-let
|
||||
let counters = {}
|
||||
|
||||
function getSnapshotIndex (key) {
|
||||
if (key in counters) {
|
||||
// eslint-disable-next-line immutable/no-mutation
|
||||
counters[key] += 1
|
||||
} else {
|
||||
// eslint-disable-next-line immutable/no-mutation
|
||||
counters[key] = 1
|
||||
}
|
||||
return counters[key]
|
||||
}
|
||||
|
||||
let snapshotFileName = config.snapshotFileName
|
||||
if (config.useRelativeSnapshots) {
|
||||
let relative = Cypress.spec.relative
|
||||
if (Cypress.platform === 'win32') {
|
||||
relative = relative.replace(/\\/g, path.sep)
|
||||
}
|
||||
|
||||
snapshotFileName = path.join(path.dirname(relative), config.snapshotFileName)
|
||||
}
|
||||
|
||||
function evaluateLoadedSnapShots (js) {
|
||||
la(is.string(js), 'expected JavaScript snapshot source', js)
|
||||
console.log('read snapshots.js file')
|
||||
const store = eval(js) || {}
|
||||
console.log('have %d snapshot(s)', countSnapshots(store))
|
||||
storeSnapshot = initStore(store)
|
||||
}
|
||||
|
||||
global.before(function loadSnapshots () {
|
||||
let readFile
|
||||
|
||||
if (config.useRelativeSnapshots) {
|
||||
readFile = cy
|
||||
.task('readFileMaybe', snapshotFileName)
|
||||
.then(function (contents) {
|
||||
if (!contents) {
|
||||
return cy.writeFile(snapshotFileName, '', 'utf-8', { log: false })
|
||||
}
|
||||
|
||||
return contents
|
||||
})
|
||||
} else {
|
||||
readFile = cy
|
||||
.readFile(snapshotFileName, 'utf-8')
|
||||
}
|
||||
|
||||
readFile.then(evaluateLoadedSnapShots)
|
||||
// no way to catch an error yet
|
||||
})
|
||||
|
||||
function getTestName (test) {
|
||||
const names = itsName(test)
|
||||
// la(is.strings(names), 'could not get name from current test', test)
|
||||
return names
|
||||
}
|
||||
|
||||
function getSnapshotName (test, humanName) {
|
||||
const names = getTestName(test)
|
||||
const key = names.join(' - ')
|
||||
const index = humanName || getSnapshotIndex(key)
|
||||
names.push(String(index))
|
||||
return names
|
||||
}
|
||||
|
||||
function setSnapshot (name, value, $el) {
|
||||
// snapshots were not initialized
|
||||
if (!storeSnapshot) {
|
||||
return
|
||||
}
|
||||
|
||||
// show just the last part of the name list (the index)
|
||||
const message = Cypress._.last(name)
|
||||
console.log('current snapshot name', name)
|
||||
|
||||
const devToolsLog = {
|
||||
value
|
||||
}
|
||||
if (Cypress.dom.isJquery($el)) {
|
||||
// only add DOM elements, otherwise "expected" value is enough
|
||||
devToolsLog.$el = $el
|
||||
}
|
||||
|
||||
const options = {
|
||||
name: 'snapshot',
|
||||
message,
|
||||
consoleProps: () => devToolsLog
|
||||
}
|
||||
|
||||
if ($el) {
|
||||
options.$el = $el
|
||||
}
|
||||
|
||||
const cyRaiser = ({ value, expected }) => {
|
||||
const result = compareValues({ expected, value })
|
||||
result.orElse((json) => {
|
||||
// by deleting property and adding it at the last position
|
||||
// we reorder how the object is displayed
|
||||
// We want convenient:
|
||||
// - message
|
||||
// - expected
|
||||
// - value
|
||||
devToolsLog.message = json.message
|
||||
devToolsLog.expected = expected
|
||||
delete devToolsLog.value
|
||||
devToolsLog.value = value
|
||||
throw new Error(`Snapshot difference. To update, delete snapshot and rerun test.\n${json.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
Cypress.log(options)
|
||||
storeSnapshot({
|
||||
value,
|
||||
name,
|
||||
raiser: cyRaiser
|
||||
})
|
||||
}
|
||||
|
||||
const pickSerializer = (asJson, value) => {
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
return asJson ? serializeDomElement : serializeToHTML
|
||||
}
|
||||
return identity
|
||||
}
|
||||
|
||||
function snapshot (value, { name, json } = {}) {
|
||||
console.log('human name', name)
|
||||
const snapshotName = getSnapshotName(this.test, name)
|
||||
const serializer = pickSerializer(json, value)
|
||||
const serialized = serializer(value)
|
||||
setSnapshot(snapshotName, serialized, value)
|
||||
|
||||
// always just pass value
|
||||
return value
|
||||
}
|
||||
|
||||
Cypress.Commands.add('snapshot', { prevSubject: true }, snapshot)
|
||||
|
||||
global.after(function saveSnapshots () {
|
||||
if (storeSnapshot) {
|
||||
const snapshots = storeSnapshot()
|
||||
console.log('%d snapshot(s) on finish', countSnapshots(snapshots))
|
||||
console.log(snapshots)
|
||||
|
||||
snapshots.__version = Cypress.version
|
||||
const s = JSON.stringify(snapshots, null, 2)
|
||||
const str = `module.exports = ${s}\n`
|
||||
cy.writeFile(snapshotFileName, str, 'utf-8', { log: false })
|
||||
}
|
||||
})
|
||||
|
||||
return snapshot
|
||||
}
|
||||
const { functions } = require("./utils/index");
|
||||
|
||||
module.exports = {
|
||||
register: registerCypressSnapshot
|
||||
}
|
||||
register: functions.register,
|
||||
tasks: functions.tasks
|
||||
};
|
||||
|
|
5
src/utils/functions/addTasks.js
Normal file
5
src/utils/functions/addTasks.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const readFileMaybe = require("../tasks/readFileMaybe");
|
||||
|
||||
module.exports = (on, config) => {
|
||||
on("task", { readFileMaybe });
|
||||
};
|
13
src/utils/functions/register.js
Normal file
13
src/utils/functions/register.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const lazy = require("lazy-ass");
|
||||
const is = require("check-more-types");
|
||||
const snapshot = require("../snapshots/snapshot");
|
||||
|
||||
module.exports = () => {
|
||||
lazy(is.fn(global.before), "Missing global before function");
|
||||
lazy(is.fn(global.after), "Missing global after function");
|
||||
lazy(is.object(global.Cypress), "Missing Cypress object");
|
||||
|
||||
Cypress.Commands.add("snapshot", { prevSubject: true }, snapshot);
|
||||
|
||||
return snapshot;
|
||||
};
|
28
src/utils/index.js
Normal file
28
src/utils/index.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const serializeToHTML = require("./serializers/serializeToHTML");
|
||||
const serializeDomElement = require("./serializers/serializeDomElement");
|
||||
const compareValues = require("./snapshots/compareValues");
|
||||
const readFileMaybe = require("./tasks/readFileMaybe");
|
||||
const identity = (x) => x;
|
||||
const publicProps = (name) => !name.startsWith("__");
|
||||
const countSnapshots = (snapshots) =>
|
||||
Object.keys(snapshots).filter(publicProps).length;
|
||||
|
||||
module.exports = {
|
||||
serializers: {
|
||||
serializeDomElement,
|
||||
serializeToHTML,
|
||||
identity,
|
||||
countSnapshots,
|
||||
},
|
||||
snapshots: {
|
||||
compareValues,
|
||||
},
|
||||
config: require("./config"),
|
||||
functions: {
|
||||
register: require("./functions/register"),
|
||||
tasks: require("./functions/addTasks")
|
||||
},
|
||||
tasks: {
|
||||
readFileMaybe
|
||||
},
|
||||
};
|
123
src/utils/snapshots/snapshot.js
Normal file
123
src/utils/snapshots/snapshot.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
const serializeDomElement = require("../serializers/serializeDomElement");
|
||||
const serializeToHTML = require("../serializers/serializeToHTML");
|
||||
const compareValues = require("./compareValues");
|
||||
const { initStore } = require("snap-shot-store");
|
||||
// const itsName = require("its-name");
|
||||
const path = require("path");
|
||||
const identity = (x) => x;
|
||||
|
||||
// Value = the JSON we want to store/compare
|
||||
// name = the Human Readable name of this Snapshot
|
||||
// json = decides if we convert DOM elements into JSON when storing in the snapshot file
|
||||
const pickSerializer = (asJson, value) => {
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
return asJson ? serializeDomElement : serializeToHTML;
|
||||
}
|
||||
return identity;
|
||||
};
|
||||
|
||||
let counters = {};
|
||||
|
||||
const newStore = (name) => {
|
||||
return initStore(name);
|
||||
};
|
||||
|
||||
const get_snapshot_key = (key) => {
|
||||
if (key in counters) {
|
||||
// eslint-disable-next-line immutable/no-mutation
|
||||
counters[key] += 1;
|
||||
} else {
|
||||
// eslint-disable-next-line immutable/no-mutation
|
||||
counters[key] = 1;
|
||||
}
|
||||
return counters[key];
|
||||
};
|
||||
|
||||
const store_snapshot = (store, props = { value, name, path, raiser }) => {
|
||||
const fileName = props.name
|
||||
.join("_")
|
||||
.replace(/ /gi, "-")
|
||||
.replace(/\//gi, "-");
|
||||
const snapshotPath = Cypress.config("snapshot").snapshotPath || "cypress/snapshots"
|
||||
const expectedPath = path.join(snapshotPath, `${fileName}.json`);
|
||||
// console.log("\x1b[31m%s\x1b[30m", "file: path", expectedPath);
|
||||
cy.task("readFileMaybe", expectedPath).then((exist) => {
|
||||
// console.log("\x1b[35m%s\x1b[30m", "file: exists", exist);
|
||||
if(exist){
|
||||
props.raiser({value: props.value, expected: JSON.parse(exist)})
|
||||
} else {
|
||||
cy.writeFile(expectedPath, JSON.stringify(props.value))
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const set_snapshot = (store, { snapshotName, snapshotPath, serialized, value }) => {
|
||||
if (!store) return; // no store was initialized
|
||||
|
||||
const message = Cypress._.last(snapshotName);
|
||||
console.log("Current Snapshot name", snapshotName);
|
||||
|
||||
const devToolsLog = { $el: serialized };
|
||||
// console.log({snapshotName, serialized, value})
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
devToolsLog.$el = value;
|
||||
}
|
||||
|
||||
const options = {
|
||||
name: "snapshot",
|
||||
message,
|
||||
consoleProps: () => devToolsLog,
|
||||
};
|
||||
|
||||
if (value) options.$el = value;
|
||||
|
||||
const raiser = ({ value, expected }) => {
|
||||
const result = compareValues({ expected, value });
|
||||
result.orElse((json) => {
|
||||
devToolsLog.message = json.message;
|
||||
devToolsLog.expected = expected;
|
||||
delete devToolsLog.value;
|
||||
devToolsLog.value = value;
|
||||
|
||||
throw new Error(
|
||||
`Snapshot Difference. To update, delete snapshot file and rerun test.\n${json.message}`
|
||||
);
|
||||
});
|
||||
};
|
||||
Cypress.log(options);
|
||||
|
||||
store_snapshot(store, {
|
||||
value,
|
||||
name: snapshotName,
|
||||
path: snapshotPath,
|
||||
raiser,
|
||||
});
|
||||
};
|
||||
|
||||
const get_test_name = (test) => test.titlePath;
|
||||
const get_snapshot_name = (test, custom_name) => {
|
||||
const names = get_test_name(test);
|
||||
// const key = names.join(" - ");
|
||||
const index = custom_name || get_snapshot_key(key);
|
||||
names.push(String(index));
|
||||
// console.log("names", names)
|
||||
if(custom_name) return [custom_name]
|
||||
return names;
|
||||
};
|
||||
|
||||
module.exports = (value, step, { humanName, snapshotPath, json } = {}) => {
|
||||
const snapshotName = get_snapshot_name(Cypress.currentTest, humanName || step);
|
||||
const serializer = pickSerializer(json, value);
|
||||
const serialized = serializer(value);
|
||||
const store = newStore(serialized || {});
|
||||
set_snapshot(store, {
|
||||
snapshotName,
|
||||
snapshotPath,
|
||||
serialized,
|
||||
value,
|
||||
});
|
||||
// return undefined;
|
||||
};
|
||||
|
||||
|
||||
|
9
src/utils/tasks/readFileMaybe.js
Normal file
9
src/utils/tasks/readFileMaybe.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const fs = require("fs");
|
||||
|
||||
module.exports = (filename) => {
|
||||
if (fs.existsSync(filename)) {
|
||||
return fs.readFileSync(filename, "utf8");
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
Loading…
Reference in a new issue