fully working plugin, just missing more features

This commit is contained in:
Joshua 2022-09-09 14:56:47 +02:00
parent 1831604a83
commit faeb258eb9
6 changed files with 184 additions and 207 deletions

View file

@ -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
};

View file

@ -0,0 +1,5 @@
const readFileMaybe = require("../tasks/readFileMaybe");
module.exports = (on, config) => {
on("task", { readFileMaybe });
};

View 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
View 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
},
};

View 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;
};

View file

@ -0,0 +1,9 @@
const fs = require("fs");
module.exports = (filename) => {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, "utf8");
}
return false;
};