mirror of
https://github.com/datashard/snapshot.git
synced 2025-04-28 21:17:21 +00:00
Compare commits
142 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a953a3c9e6 | ||
|
76ed0ebf45 | ||
|
390053f9c6 | ||
|
e561036926 | ||
|
9351d344a7 | ||
|
e4f9cbc5cf | ||
|
71fe145047 | ||
|
0256d8c359 | ||
|
a12634c9a3 | ||
|
8add20790e | ||
|
dc1290ec04 | ||
|
b4f841d878 | ||
|
930662045c | ||
|
d45d7479f0 | ||
|
bc6ffad697 | ||
|
4b96c41056 | ||
|
f51bcae462 | ||
|
b1ce5f2fdf | ||
|
5f861d4949 | ||
|
960e699075 | ||
|
cb39e029c3 | ||
|
7c6f997bc0 | ||
|
9a41366c82 | ||
|
5db74ebcb6 | ||
|
b61e5c0552 | ||
|
e2ef9784e4 | ||
|
7816594c08 | ||
|
1fe4562b9f | ||
|
e0e49ea0de | ||
|
f9f95315fe | ||
|
897ad51548 | ||
|
253a4e44ea | ||
|
4316f5e1c2 | ||
|
097495615b | ||
|
fea6332aa8 | ||
|
0d303b9da5 | ||
|
5a1a5981d8 | ||
|
0e9e1d3e70 | ||
|
771013c5a5 | ||
|
097014dccd | ||
|
d02b5c6b29 | ||
|
6dfcbfed38 | ||
|
dccbde9fe4 | ||
|
2ce0c6d92b | ||
|
59d550b56f | ||
|
dbda85c6e1 | ||
|
7f629969d5 | ||
|
af82f108ad | ||
|
6e0b81a522 | ||
|
f10f29cf1d | ||
|
0c8054b44f | ||
|
fc42087ced | ||
|
c9be09dc48 | ||
|
e8f8434188 | ||
|
f5d6b7f63d | ||
|
4cb9f8ca1d | ||
|
4736fb269e | ||
|
faeb258eb9 | ||
|
1831604a83 | ||
|
69a16313ba | ||
|
eef6298744 | ||
|
1636db6455 | ||
|
3cdf0af343 | ||
|
2cd172d773 | ||
|
be214cc564 | ||
|
62ac60e853 | ||
|
5496c991d7 | ||
|
7d5b8f0ecf | ||
|
1c0d12c8fd | ||
|
723c38aff1 | ||
|
4d41380baf | ||
|
06315af59d | ||
|
ad22061e8c | ||
|
c8c0926eaf | ||
|
3be76221b7 | ||
|
6874038f49 | ||
|
462424ebff | ||
|
feb7cecefb | ||
|
7b5fb9df14 | ||
|
a9098fd871 | ||
|
8873483ec0 | ||
|
258c74f434 | ||
|
568f90abff | ||
|
e8163bbc0f | ||
|
577e1b9ad6 | ||
|
4da8038d70 | ||
|
3e93d94746 | ||
|
d0d2bbf11f | ||
|
68d2f9befa | ||
|
9e94d1127f | ||
|
7237921979 | ||
|
5645eb726c | ||
|
c93de89a2d | ||
|
3d70fd7fa7 | ||
|
c66c6ead31 | ||
|
fadbab260a | ||
|
98410416dd | ||
|
aa3f354b81 | ||
|
7a7589925d | ||
|
330e5164be | ||
|
9dc2366222 | ||
|
69378d5f8b | ||
|
4ba55f61ed | ||
|
5a51b5281d | ||
|
3aa891143c | ||
|
4b2c49f11f | ||
|
34ff0e81e5 | ||
|
ac03ee6a67 | ||
|
5d77a7606d | ||
|
cb82c07d19 | ||
|
2ad14a4193 | ||
|
5cebcba032 | ||
|
031f8c84b3 | ||
|
253b32ce03 | ||
|
528a1a40ef | ||
|
a7b83afb15 | ||
|
6b401edcc1 | ||
|
d038e4d744 | ||
|
e7f4fea295 | ||
|
9e88b2bb1c | ||
|
d2f3b0e282 | ||
|
f809133e79 | ||
|
17c6a82150 | ||
|
36bff51d36 | ||
|
46cb474a5d | ||
|
f5503c4756 | ||
|
69e2409df9 | ||
|
16ff270a83 | ||
|
fda76e41bf | ||
|
a2d588c399 | ||
|
96b8bd1ab6 | ||
|
54ba9abc7e | ||
|
744e0982d5 | ||
|
320a2086cf | ||
|
0456c17f8e | ||
|
428bc3c007 | ||
|
b4fb254867 | ||
|
66de7256fc | ||
|
9670a9e90a | ||
|
fdb16c4b54 | ||
|
1f62602224 | ||
|
a7f44204b3 |
48 changed files with 5050 additions and 8252 deletions
.eslintrc
.github
.gitignore.npmignore.npmrc.travis.yml.vscode
LICENSEREADME.mdcypress.config.jscypress.jsoncypress
e2e
fixtures
integration
plugins
support
img
issue_template.mdpackage-lock.jsonpackage.jsonrenovate.jsonsnapshots.jssrc
12
.eslintrc
12
.eslintrc
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"plugin:cypress-dev/general"
|
||||
],
|
||||
"rules": {
|
||||
"comma-dangle": "off",
|
||||
"no-debugger": "warn"
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
BIN
.github/assets/Correct.png
vendored
Normal file
BIN
.github/assets/Correct.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 9 KiB |
BIN
.github/assets/Error.png
vendored
Normal file
BIN
.github/assets/Error.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 14 KiB |
BIN
.github/assets/config.png
vendored
Normal file
BIN
.github/assets/config.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 76 KiB |
BIN
.github/assets/updated-mismatch.png
vendored
Normal file
BIN
.github/assets/updated-mismatch.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 41 KiB |
12
.github/workflows/cypress.yml
vendored
Normal file
12
.github/workflows/cypress.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: Cypress
|
||||
on: [push, pull_request, workflow_call, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies
|
||||
run: npm i
|
||||
- name: Test
|
||||
run: npm run cypress:run
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
|||
node_modules/
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
cypress/videos/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
cypress/videos/
|
||||
cypress/screenshots/
|
||||
cypress/snapshots/Arrays.json
|
5
.npmignore
Normal file
5
.npmignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
cypress/
|
||||
cypress.config.js
|
||||
.github/workflows/
|
||||
.vscode/
|
||||
renovate.json
|
8
.npmrc
8
.npmrc
|
@ -1,4 +1,4 @@
|
|||
registry=http://registry.npmjs.org/
|
||||
save-exact=true
|
||||
progress=false
|
||||
package-lock=true
|
||||
registry=https://registry.npmjs.org/
|
||||
save-exact=true
|
||||
progress=false
|
||||
package-lock=true
|
||||
|
|
16
.travis.yml
16
.travis.yml
|
@ -1,16 +0,0 @@
|
|||
language: node_js
|
||||
notifications:
|
||||
email: true
|
||||
node_js:
|
||||
- 10
|
||||
|
||||
# Retry install on fail to avoid failing a build on network/disk/external errors
|
||||
install:
|
||||
- travis_retry npm ci
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
- npm run cypress:run
|
||||
|
||||
after_success:
|
||||
- npm run semantic-release
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"standard.enable": false,
|
||||
"eslint.enable": true,
|
||||
"eslint.autoFixOnSave": false,
|
||||
"git.ignoreLimitWarning": true,
|
||||
"standard.autoFixOnSave": false
|
||||
}
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2023-2024 Joshua data@shard.wtf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
243
README.md
243
README.md
|
@ -1,151 +1,92 @@
|
|||
# @cypress/snapshot
|
||||
|
||||
> Adds value / object / DOM element snapshot testing support to Cypress test runner
|
||||
|
||||
[![NPM][npm-icon] ][npm-url]
|
||||
|
||||
[![Build status][ci-image] ][ci-url]
|
||||
[![semantic-release][semantic-image] ][semantic-url]
|
||||
[![renovate-app badge][renovate-badge]][renovate-app]
|
||||
|
||||
## Install
|
||||
|
||||
Requires [Node](https://nodejs.org/en/) version 6 or above.
|
||||
|
||||
```sh
|
||||
npm install --save-dev @cypress/snapshot
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
After installing, add the following to your `cypress/support/commands.js` file
|
||||
|
||||
```js
|
||||
require('@cypress/snapshot').register()
|
||||
```
|
||||
|
||||
This registers a new command to create new snapshot or compare value to old snapshot
|
||||
|
||||
```js
|
||||
describe('my tests', () => {
|
||||
it('works', () => {
|
||||
cy.log('first snapshot')
|
||||
cy.wrap({ foo: 42 }).snapshot()
|
||||
cy.log('second snapshot')
|
||||
cy.wrap({ bar: 101 }).snapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('focused input field', () => {
|
||||
it('is empty and then typed into', () => {
|
||||
cy.visit('http://todomvc.com/examples/react/')
|
||||
cy
|
||||
.focused()
|
||||
.snapshot('initial')
|
||||
.type('eat healthy breakfast')
|
||||
.snapshot('after typing')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
The snapshot object can be found in file `snapshots.js`. In the above case it would look something like this
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
"focused input field": {
|
||||
"is empty and then typed into": {
|
||||
"initial": {
|
||||
"tagName": "input",
|
||||
"attributes": {
|
||||
"class": "new-todo",
|
||||
"placeholder": "What needs to be done?",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"after typing": {
|
||||
"tagName": "input",
|
||||
"attributes": {
|
||||
"class": "new-todo",
|
||||
"placeholder": "What needs to be done?",
|
||||
"value": "eat healthy breakfast"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"my tests": {
|
||||
"works": {
|
||||
"1": {
|
||||
"foo": 42
|
||||
},
|
||||
"2": {
|
||||
"bar": 101
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you change the site values, the saved snapshot will no longer match, throwing an error
|
||||
|
||||

|
||||
|
||||
Click on the `SNAPSHOT` step in the Command Log to see expected and current value printed in the DevTools.
|
||||
|
||||
### Options
|
||||
|
||||
You can control snapshot comparison and behavior through a few options.
|
||||
|
||||
```js
|
||||
cy.get(...).snapshot({
|
||||
name: 'human snapshot name', // to use in the snapshot file
|
||||
json: false // convert DOM elements into JSON
|
||||
// when storing in the snapshot file
|
||||
})
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug this module run with environment variable `DEBUG=@cypress/snapshot`
|
||||
|
||||
### Small print
|
||||
|
||||
Author: Gleb Bahmutov <gleb@cypress.io> © Cypress.io 2017
|
||||
|
||||
License: MIT - do anything with the code, but don't blame u if it does not work.
|
||||
|
||||
Support: if you find any problems with this module, email / tweet /
|
||||
[open issue](https://github.com/cypress-io/snapshot/issues) on Github
|
||||
|
||||
## MIT License
|
||||
|
||||
Copyright (c) 2017 Cypress.io <gleb@cypress.io>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
[npm-icon]: https://nodei.co/npm/@cypress/snapshot.svg?downloads=true
|
||||
[npm-url]: https://npmjs.org/package/@cypress/snapshot
|
||||
[ci-image]: https://travis-ci.org/cypress-io/snapshot.svg?branch=master
|
||||
[ci-url]: https://travis-ci.org/cypress-io/snapshot
|
||||
[semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
|
||||
[semantic-url]: https://github.com/semantic-release/semantic-release
|
||||
[renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
|
||||
[renovate-app]: https://renovateapp.com/
|
||||
# @datashard/snapshot
|
||||
|
||||
> Adds JSON Snapshot comparison to Cypress
|
||||
|
||||
## ⚠️ Breaking Changes ⚠️
|
||||
|
||||
- With V3, the `readFileMaybe` task has been removed, we now rely on `cy.fixture` internally.
|
||||
- the previous `SNAPSHOT_UPDATE` Environment/Config Variable has been changed to `updateSnapshots`
|
||||
|
||||
> [!DANGER]
|
||||
This means that previous tests will likely be broken, *please make sure that your tests pass before updating to the latest version of this Plugin*.
|
||||
|
||||
This current release will be released as `3.0.0-beta`, should Bugs be found by me or my Employer, I will open Issues/PRs to fix those, should anyone else find Bugs/Edgecases, etc. please open an Issue.
|
||||
|
||||
## Install
|
||||
|
||||
Requires Node 16 or above
|
||||
|
||||
```sh
|
||||
npm i --save-dev @datashard/snapshot
|
||||
```
|
||||
|
||||
## Import
|
||||
|
||||
After Installing, you'll need to add the following import into your Commands/Support File
|
||||
|
||||
> by default this will be `cypress/support/e2e.js`
|
||||
|
||||
```js
|
||||
require('@datashard/snapshot').regsiter()
|
||||
```
|
||||
|
||||
This will register a new Command `.snapshot()`, to create and compare JSON Snapshots.
|
||||
|
||||
## Config
|
||||
|
||||
You can pass `updateSnapshots` and `useFolders` as options in the `cypress.config.js` file
|
||||
|
||||

|
||||
Alternatively, you can also add `snapshotUpdate` as an Environment Variable to update your snapshots.
|
||||
|
||||
Simply pass `--env updateSnapshots=true` when running Cypress.
|
||||
|
||||
## Usage
|
||||
|
||||
If properly added, usage of this plugin is rather simple, just add `.snapshot()` to cypress functions that return valid JSON. (i.e. `cy.wrap`)
|
||||
|
||||
When properly added, you can chain `.snapshot()` off of `cy` functions like `cy.wrap`, just make sure they return valid JSON.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
describe("my test", () => {
|
||||
it("works", () => {
|
||||
cy.log("first snapshot");
|
||||
cy.wrap({ foo: 42 }).snapshot("foo");
|
||||
cy.log("second snapshot");
|
||||
cy.wrap({ bar: 101 }).snapshot("bar");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This Plugin will then save your snapshots as
|
||||
|
||||
```ts
|
||||
// useFolders: false
|
||||
cypress/fixtures/my-test__works__foo.json
|
||||
cypress/fixtures/my-test__works__bar.json
|
||||
|
||||
// useFolders: true
|
||||
cypress/fixtures/my-test/works/foo.json
|
||||
cypress/fixtures/my-test/works/bar.json
|
||||
|
||||
// {fixtureFolder}/<Context>-<Describe>-<It>-<Name?>.json
|
||||
// {fixtureFolder}/<Context>/<Describe>/<It>/<Name?>.json
|
||||
|
||||
```
|
||||
|
||||
Snapshots will generally be saved using this the Convention mentioned in the Comment of the above Codeblock, which is provided by the named Cypress Test Steps.
|
||||
|
||||
Passing a name to the Snapshot function is required, but not checked, if you want to take multiple snapshots in the same block.
|
||||
|
||||
If you have two or more Snapshots in the same Block, the next one ***WILL*** always overwrite the previous one while updating, causing the First Snapshot in the Block to Fail.
|
||||
While running your Tests, if a value changed, it will, of course, no longer match the snapshot and throw an Error.
|
||||
|
||||
Which will look like this:
|
||||
|
||||

|
||||
|
||||
When the Test succeeds, it will instead log a Success in the Log and let you know where the File has been saved to, relative to the Fixture Snapshot Folder
|
||||
|
||||

|
||||
|
|
16
cypress.config.js
Normal file
16
cypress.config.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
// fixturesFolder: "cypress/fixtures",
|
||||
snapshot: {
|
||||
// updateSnapshots: true,
|
||||
useFolders: true,
|
||||
// useSnapshotFolder: true
|
||||
},
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
{}
|
78
cypress/e2e/1.cy.js
Normal file
78
cypress/e2e/1.cy.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* eslint-env mocha */
|
||||
/* global cy */
|
||||
describe("datashard/snapshot", () => {
|
||||
context("simple types"
|
||||
// , { env: { updateSnapshots: true } }
|
||||
, () => {
|
||||
it("works with objects", () => {
|
||||
cy.wrap({
|
||||
"foo": "bar",
|
||||
"Fizzy Drink": "Pop"
|
||||
}).snapshot();
|
||||
});
|
||||
|
||||
it("works with numbers", () => {
|
||||
cy.wrap(42).snapshot({
|
||||
snapshotPath: "cypress/fixtures/snapshots",
|
||||
snapshotName: "Numbers",
|
||||
});
|
||||
});
|
||||
|
||||
it("works with strings", () => {
|
||||
cy.wrap("foo-bar").snapshot({
|
||||
snapshotPath: "cypress/fixtures/snapshots",
|
||||
snapshotName: "Strings",
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
"works with arrays",
|
||||
{
|
||||
env: {
|
||||
updateSnapshots: true,
|
||||
},
|
||||
},
|
||||
() => {
|
||||
cy.wrap([1, 2, 3, 4]).snapshot({
|
||||
snapshotPath: "cypress/fixtures/snapshots",
|
||||
snapshotName: "Arrays",
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
context("complex types"
|
||||
// , { env: { SNAPSHOT_UPDATE: true } }
|
||||
, () => {
|
||||
it('works with more "complicated" Objects', () => {
|
||||
cy.wrap({
|
||||
"status": 200,
|
||||
"response": {
|
||||
"array": [0, 1, 2, "4"],
|
||||
"object": {
|
||||
"with": "more details"
|
||||
}
|
||||
},
|
||||
"thisisnew": "wtf"
|
||||
}).snapshot()
|
||||
})
|
||||
it("works based on fixtures", () => {
|
||||
cy
|
||||
.wrap({
|
||||
"jsonapi": {
|
||||
"version": "2.0"
|
||||
},
|
||||
"included": [
|
||||
{
|
||||
"type": "users",
|
||||
"id": "2",
|
||||
"attributes": {
|
||||
"name": "Test"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
.snapshot();
|
||||
});
|
||||
})
|
||||
});
|
23
cypress/e2e/2.cy.js
Normal file
23
cypress/e2e/2.cy.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* eslint-env mocha */
|
||||
/* global cy */
|
||||
describe("Random Describe", () => {
|
||||
context("Random Context", () => {
|
||||
it("Random It", () => {
|
||||
cy.wrap(42).snapshot("Numbers");
|
||||
cy.wrap("foo-bar").snapshot("Strings");
|
||||
cy.wrap([1, 2, 3]).snapshot("Arrays");
|
||||
});
|
||||
|
||||
// it("works with numbers", () => {
|
||||
// console.log(cy.wrap(42))
|
||||
// });
|
||||
|
||||
// it("works with strings", () => {
|
||||
// console.log(cy.wrap("foo-bar"))
|
||||
// });
|
||||
|
||||
// it("works with arrays", () => {
|
||||
// console.log(cy.wrap([1, 2, 3]))
|
||||
// });
|
||||
});
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": 42
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": "foo-bar"
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"jsonapi": {
|
||||
"version": "2.0"
|
||||
},
|
||||
"included": [
|
||||
{
|
||||
"type": "users",
|
||||
"id": "2",
|
||||
"attributes": {
|
||||
"name": "Test"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"status": 200,
|
||||
"response": {
|
||||
"array": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
"4"
|
||||
],
|
||||
"object": {
|
||||
"with": "more details"
|
||||
}
|
||||
},
|
||||
"thisisnew": "wtf"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": 42
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"foo": "bar",
|
||||
"Fizzy Drink": "Pop"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"data": "foo-bar"
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/* eslint-env mocha */
|
||||
/* global cy */
|
||||
describe('@cypress/snapshot', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io')
|
||||
})
|
||||
|
||||
context('simple types', () => {
|
||||
it('works with objects', () => {
|
||||
cy.wrap({ foo: 42 }).snapshot()
|
||||
})
|
||||
|
||||
it('works with numbers', () => {
|
||||
cy.wrap(42).snapshot()
|
||||
})
|
||||
|
||||
it('works with strings', () => {
|
||||
cy.wrap('foo-bar').snapshot()
|
||||
})
|
||||
|
||||
it('works with arrays', () => {
|
||||
cy.wrap([1, 2, 3]).snapshot()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,17 +1,17 @@
|
|||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
// register .snapshot() command
|
||||
require('../..').register()
|
||||
// register .snapshot() command
|
||||
|
||||
|
||||
require('../../src/index').register()
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
Binary file not shown.
Before ![]() (image error) Size: 84 KiB |
|
@ -1,12 +0,0 @@
|
|||
Thank you for taking time to open a new issue. Please answer a few questions to help us fix it faster. You can delete text that is irrelevant to the issue.
|
||||
|
||||
## Is this a bug report or a feature request?
|
||||
|
||||
If this is a bug report, please provide as much info as possible
|
||||
|
||||
- version
|
||||
- platform
|
||||
- expected behavior
|
||||
- actual behavior
|
||||
|
||||
If this is a new feature request, please describe it below
|
11798
package-lock.json
generated
11798
package-lock.json
generated
File diff suppressed because it is too large
Load diff
148
package.json
148
package.json
|
@ -1,98 +1,50 @@
|
|||
{
|
||||
"name": "@cypress/snapshot",
|
||||
"description": "Adds value / object / DOM element snapshot testing support to Cypress test runner",
|
||||
"version": "0.0.0-development",
|
||||
"author": "Gleb Bahmutov <gleb@cypress.io>",
|
||||
"bugs": "https://github.com/cypress-io/snapshot/issues",
|
||||
"config": {
|
||||
"pre-git": {
|
||||
"commit-msg": "simple",
|
||||
"pre-commit": [
|
||||
"npm prune",
|
||||
"npm run deps",
|
||||
"npm test",
|
||||
"git add src/*.js",
|
||||
"npm run ban"
|
||||
],
|
||||
"pre-push": [
|
||||
"npm run unused-deps",
|
||||
"npm run license",
|
||||
"npm run ban -- --all",
|
||||
"npm run size"
|
||||
],
|
||||
"post-commit": [],
|
||||
"post-merge": []
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"files": [
|
||||
"img",
|
||||
"src/*.js",
|
||||
"!src/*-spec.js"
|
||||
],
|
||||
"homepage": "https://github.com/cypress-io/snapshot#readme",
|
||||
"keywords": [
|
||||
"cypress",
|
||||
"cypress-io",
|
||||
"plugin",
|
||||
"snapshot",
|
||||
"testing"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"registry": "http://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cypress-io/snapshot.git"
|
||||
},
|
||||
"scripts": {
|
||||
"ban": "ban",
|
||||
"deps": "deps-ok && dependency-check --no-dev .",
|
||||
"issues": "git-issues",
|
||||
"license": "license-checker --production --onlyunknown --csv",
|
||||
"lint": "eslint --fix src/*.js",
|
||||
"pretest": "npm run lint",
|
||||
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
|
||||
"test": "npm run unit",
|
||||
"unit": "mocha src/*-spec.js",
|
||||
"unused-deps": "dependency-check --unused --no-dev . --entry src/add-initial-snapshot-file.js",
|
||||
"postinstall": "node src/add-initial-snapshot-file.js",
|
||||
"semantic-release": "semantic-release pre && npm publish --access public && semantic-release post",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"release": {
|
||||
"analyzeCommits": "simple-commit-message",
|
||||
"generateNotes": "github-post-release"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ban-sensitive-files": "1.9.2",
|
||||
"cypress": "1.1.4",
|
||||
"debug": "3.1.0",
|
||||
"dependency-check": "2.9.1",
|
||||
"deps-ok": "1.2.1",
|
||||
"eslint": "4.13.0",
|
||||
"eslint-plugin-cypress-dev": "1.1.2",
|
||||
"git-issues": "1.3.1",
|
||||
"github-post-release": "1.13.1",
|
||||
"license-checker": "15.0.0",
|
||||
"mocha": "4.0.1",
|
||||
"pre-git": "3.16.0",
|
||||
"semantic-release": "^8.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wildpeaks/snapshot-dom": "1.2.1",
|
||||
"am-i-a-dependency": "1.1.2",
|
||||
"check-more-types": "2.24.0",
|
||||
"its-name": "1.0.0",
|
||||
"js-beautify": "1.7.5",
|
||||
"lazy-ass": "1.6.0",
|
||||
"snap-shot-compare": "2.7.1",
|
||||
"snap-shot-store": "1.2.3"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "@datashard/snapshot",
|
||||
"description": "Adds JSON Snapshot testing support to Cypress",
|
||||
"version": "3.0.0-beta.1",
|
||||
"author": "Joshua <data@shard.wtf>",
|
||||
"bugs": "https://github.com/datashard/snapshot/issues",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"files": [
|
||||
"img",
|
||||
"src/*",
|
||||
"src/*/**",
|
||||
"!src/*-spec.js"
|
||||
],
|
||||
"homepage": "https://shard.wtf/snapshot",
|
||||
"keywords": [
|
||||
"cypress",
|
||||
"cypress-io",
|
||||
"plugin",
|
||||
"snapshot",
|
||||
"testing", "json"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/datashard/snapshot.git"
|
||||
},
|
||||
"scripts": {
|
||||
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
|
||||
"unused-deps": "dependency-check --unused --no-dev . --entry src/index.js",
|
||||
"semantic-release": "semantic-release",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:update": "cypress run --env updateSnapshots=true",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "12.13.0",
|
||||
"debug": "3.2.7",
|
||||
"dependency-check": "2.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wildpeaks/snapshot-dom": "1.6.0",
|
||||
"check-more-types": "2.24.0",
|
||||
"js-beautify": "1.13.13",
|
||||
"lazy-ass": "1.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"automerge": true,
|
||||
"major": {
|
||||
"automerge": false
|
||||
},
|
||||
"updateNotScheduled": false,
|
||||
"timezone": "America/New_York",
|
||||
"schedule": [
|
||||
"every weekend"
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
},
|
||||
"separatePatchReleases": true,
|
||||
"separateMultipleMajor": true,
|
||||
"masterIssue": true,
|
||||
"labels": [
|
||||
"type: dependencies",
|
||||
"renovate"
|
||||
]
|
||||
}
|
||||
{
|
||||
"extends": ["config:base"],
|
||||
"automerge": true,
|
||||
"major": {
|
||||
"automerge": false
|
||||
},
|
||||
"updateNotScheduled": false,
|
||||
"timezone": "Europe/Berlin",
|
||||
"schedule": ["every weekend"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
},
|
||||
"separatePatchReleases": true,
|
||||
"separateMultipleMajor": true,
|
||||
"masterIssue": true,
|
||||
"labels": ["type: dependencies", "renovate"]
|
||||
}
|
||||
|
|
25
snapshots.js
25
snapshots.js
|
@ -1,25 +0,0 @@
|
|||
module.exports = {
|
||||
"@cypress/snapshot": {
|
||||
"simple types": {
|
||||
"works with objects": {
|
||||
"1": {
|
||||
"foo": 42
|
||||
}
|
||||
},
|
||||
"works with numbers": {
|
||||
"1": 42
|
||||
},
|
||||
"works with strings": {
|
||||
"1": "foo-bar"
|
||||
},
|
||||
"works with arrays": {
|
||||
"1": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"__version": "1.1.4"
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
const debug = require('debug')('@cypress/snapshot')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const amDependency = require('am-i-a-dependency')()
|
||||
|
||||
if (amDependency) {
|
||||
// yes, do something interesting
|
||||
// someone is executing "npm install foo"
|
||||
debug('post install - in folder', process.cwd())
|
||||
// we are in <owner>/node_modules/@cypress/snapshot
|
||||
// but want to be simply in <owner> folder
|
||||
const ownerFolder = path.normalize(path.join(process.cwd(), '..', '..', '..'))
|
||||
const filename = path.join(ownerFolder, utils.SNAPSHOT_FILE_NAME)
|
||||
|
||||
if (!fs.existsSync(filename)) {
|
||||
// save initial empty snapshot object
|
||||
debug('writing initial file', filename)
|
||||
fs.writeFileSync(filename, '{}\n')
|
||||
} else {
|
||||
debug('file %s already exists', filename)
|
||||
}
|
||||
} else {
|
||||
debug('not a dependency install')
|
||||
}
|
179
src/index.js
179
src/index.js
|
@ -1,173 +1,6 @@
|
|||
'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 {
|
||||
serializeDomElement,
|
||||
serializeReactToHTML,
|
||||
identity,
|
||||
countSnapshots
|
||||
} = require('./utils')
|
||||
|
||||
/* 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')
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
const SNAPSHOT_FILENAME = 'snapshots.js'
|
||||
|
||||
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 () {
|
||||
cy
|
||||
.readFile(SNAPSHOT_FILENAME, 'utf-8', { log: false })
|
||||
.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\n${json.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
Cypress.log(options)
|
||||
storeSnapshot({
|
||||
value,
|
||||
name,
|
||||
raiser: cyRaiser
|
||||
})
|
||||
}
|
||||
|
||||
const pickSerializer = (asJson, value) => {
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
return asJson ? serializeDomElement : serializeReactToHTML
|
||||
}
|
||||
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(SNAPSHOT_FILENAME, str, 'utf-8', { log: false })
|
||||
}
|
||||
})
|
||||
|
||||
return snapshot
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register: registerCypressSnapshot
|
||||
}
|
||||
// global cy, Cypress
|
||||
const register = require("./register");
|
||||
|
||||
module.exports = {
|
||||
register
|
||||
};
|
||||
|
|
119
src/register.js
Normal file
119
src/register.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
const lazy = require("lazy-ass");
|
||||
const is = require("check-more-types");
|
||||
const snapshot = require("./snapshot");
|
||||
|
||||
const baseStyles = [
|
||||
{
|
||||
name: 'info',
|
||||
color: '#cbd5e1',
|
||||
},
|
||||
{
|
||||
name: 'success',
|
||||
color: '#10b981',
|
||||
},
|
||||
{
|
||||
name: 'warning',
|
||||
color: '#fbbf24',
|
||||
},
|
||||
{
|
||||
name: 'error',
|
||||
color: '#dc2626',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Helper function to convert hex colors to rgb
|
||||
* @param {string} hex - hex color
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "255 255 255"
|
||||
* hex2rgb("#ffffff")
|
||||
*/
|
||||
function hex2rgb(hex) {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
const b = parseInt(hex.slice(5, 7), 16)
|
||||
|
||||
return `${r} ${g} ${b}`
|
||||
}
|
||||
|
||||
baseStyles.forEach((style) => {
|
||||
createCustomLog(style.name, style.color)
|
||||
})
|
||||
|
||||
/**
|
||||
* Create a custom log
|
||||
* @param {string} name - Name of the custom log
|
||||
* @param {string} baseColor - Base color of the custom log in hex format
|
||||
*
|
||||
* @example
|
||||
* // Create a custom log with name "misc" and base color "#9333ea"
|
||||
* createCustomLog("misc", "#9333ea")
|
||||
*/
|
||||
function createCustomLog(name, baseColor) {
|
||||
if (!name || !baseColor) {
|
||||
throw new Error('Missing parameters')
|
||||
}
|
||||
|
||||
const logStyle = document.createElement('style')
|
||||
|
||||
logStyle.textContent = `
|
||||
.command.command-name-log-${name} span.command-method {
|
||||
margin-right: 0.5rem;
|
||||
min-width: 10px;
|
||||
border-radius: 0.125rem;
|
||||
border-width: 1px;
|
||||
padding-left: 0.375rem;
|
||||
padding-right: 0.375rem;
|
||||
padding-top: 0.125rem;
|
||||
padding-bottom: 0.125rem;
|
||||
text-transform: uppercase;
|
||||
|
||||
border-color: rgb(${hex2rgb(baseColor)} / 1);
|
||||
background-color: rgb(${hex2rgb(baseColor)} / 0.2);
|
||||
color: rgb(${hex2rgb(baseColor)} / 1) !important;
|
||||
}
|
||||
|
||||
.command.command-name-log-${name} span.command-message{
|
||||
color: rgb(${hex2rgb(baseColor)} / 1);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.command.command-name-log-${name} span.command-message strong,
|
||||
.command.command-name-log-${name} span.command-message em {
|
||||
color: rgb(${hex2rgb(baseColor)} / 1);
|
||||
}
|
||||
`
|
||||
|
||||
Cypress.$(window.top.document.head).append(logStyle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a message with a formatted style
|
||||
* @param {Object} log - The message to be printed.
|
||||
* @param {string} log.title - The title of the message.
|
||||
* @param {string} log.message - The content of the message.
|
||||
* @param {('info' | 'warning' | 'error' | 'success')} log.type - The type of the message.
|
||||
*
|
||||
* @example
|
||||
* // Print a message with a formatted style e.g. success
|
||||
* cy.print({ title: 'foo', message: 'bar', type: 'success' })
|
||||
*/
|
||||
function cyPrint({ title, message, type }) {
|
||||
Cypress.log({
|
||||
name: `log-${type}`,
|
||||
displayName: `${title}`,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
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: "optional" }, snapshot);
|
||||
Cypress.Commands.add('SNAPSHOT_prettyprint', cyPrint)
|
||||
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
/* eslint-env mocha */
|
||||
const api = require('.')
|
||||
const la = require('lazy-ass')
|
||||
const is = require('check-more-types')
|
||||
|
||||
describe('@cypress/snapshot', () => {
|
||||
it('is an object', () => {
|
||||
la(is.object(api))
|
||||
})
|
||||
|
||||
it('has register', () => {
|
||||
la(is.fn(api.register))
|
||||
})
|
||||
})
|
124
src/snapshot.js
Normal file
124
src/snapshot.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
const serializeDomElement = require("./utils/serializers/serializeDomElement");
|
||||
const serializeToHTML = require("./utils/serializers/serializeToHTML");
|
||||
const compareValues = require("./utils/compareValues");
|
||||
const path = require("path");
|
||||
const identity = (x) => x;
|
||||
|
||||
const pickSerializer = (asJson, value) => {
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
return asJson ? serializeDomElement : serializeToHTML;
|
||||
}
|
||||
return identity;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
const parseTextToJSON = (text) => text.replace(/\| [✅➖➕⭕]/g, "").trim().replace(/(.*?),\s*(\}|])/g, "$1$2").replace(/},(?!")$/g, "}").replaceAll(/[╺┿╳]/g, "")
|
||||
|
||||
const store_snapshot = ({ value, name, raiser } = { value, name, raiser }) => {
|
||||
if (Cypress.env().updateSnapshots || Cypress.config('snapshot').updateSnapshots) {
|
||||
cy.SNAPSHOT_prettyprint({ title: "INFO", type: "info", message: "Saving Snapshot" })
|
||||
cy.writeFile(path.join(Cypress.config().fixturesFolder, `${name}.json`), JSON.stringify(value, null, 2))
|
||||
} else {
|
||||
cy.fixture(name).then(expected => raiser({ value, expected }))
|
||||
}
|
||||
};
|
||||
|
||||
const set_snapshot = ({ snapshotName, serialized, value }) => {
|
||||
let devToolsLog = { $el: serialized };
|
||||
if (Cypress.dom.isJquery(value)) {
|
||||
devToolsLog.$el = value;
|
||||
}
|
||||
|
||||
let options = {
|
||||
name: "snapshot",
|
||||
message: snapshotName,
|
||||
consoleProps: () => { return devToolsLog },
|
||||
...(value && { $el: value })
|
||||
};
|
||||
|
||||
const raiser = ({ value, expected }) => {
|
||||
const result = compareValues({ expected, value });
|
||||
if ((!Cypress.env().updateSnapshots || !Cypress.config('snapshot').updateSnapshots) && !result.success) {
|
||||
devToolsLog = () => {
|
||||
return { expected, value }
|
||||
};
|
||||
|
||||
const error = (inError) => `
|
||||
Snapshot Difference found.\nPlease Update the Snapshot ${inError ? `\n${JSON.stringify(JSON.parse(parseTextToJSON(result.result)), null, 3).replaceAll(' ', " ")}` : ""}`
|
||||
|
||||
Cypress.log({ message: error(true) })
|
||||
|
||||
throw new Error(error());
|
||||
} else {
|
||||
cy.SNAPSHOT_prettyprint({
|
||||
title: "SUCCESS",
|
||||
message: snapshotName,
|
||||
type: "success"
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
store_snapshot({
|
||||
value,
|
||||
name: snapshotName,
|
||||
raiser,
|
||||
});
|
||||
};
|
||||
|
||||
function replaceCharacters(str, asFolder, sep) {
|
||||
if (asFolder) {
|
||||
if (!sep) throw new Error("Separator not Passed.")
|
||||
return str
|
||||
.replace(/ /gi, '-')
|
||||
.replace(/\//gi, "-")
|
||||
.replaceAll('"', '')
|
||||
.replaceAll(sep, '/')
|
||||
} else {
|
||||
return str
|
||||
.replaceAll(' ', '-')
|
||||
.replaceAll('/', '-')
|
||||
.replaceAll('"', '')
|
||||
}
|
||||
}
|
||||
|
||||
const get_snapshot_name = (asFolder, stepName) => {
|
||||
const names = Cypress.currentTest.titlePath;
|
||||
const sep = ">>datashard.work<<"
|
||||
|
||||
|
||||
if (stepName && typeof stepName !== 'object') {
|
||||
names.push(stepName)
|
||||
}
|
||||
|
||||
|
||||
if (asFolder) return replaceCharacters(names.join(sep), true, sep)
|
||||
else return replaceCharacters(names.join('__'), false)
|
||||
};
|
||||
|
||||
module.exports = (value, stepName, options = { json: true }) => {
|
||||
if (typeof stepName === 'object') options = { ...options, ...stepName }
|
||||
if (typeof value !== "object" || Array.isArray(value))
|
||||
value = { data: value };
|
||||
const serializer = pickSerializer(options.json, value);
|
||||
const serialized = serializer(value);
|
||||
let useFolders;
|
||||
if(Cypress.config('snapshot').useSnapshotFolder === undefined || Cypress.config('snapshot').useSnapshotFolder === true) {
|
||||
useFolders = true
|
||||
} else {
|
||||
useFolders = false
|
||||
}
|
||||
options.asFolder = Cypress.config('snapshot').useFolders || false
|
||||
|
||||
set_snapshot({
|
||||
snapshotName: `/${useFolders ? "snapshots/" : ""}${get_snapshot_name(options.asFolder, stepName)}`,
|
||||
serialized,
|
||||
value,
|
||||
});
|
||||
};
|
61
src/utils.js
61
src/utils.js
|
@ -1,61 +0,0 @@
|
|||
const sd = require('@wildpeaks/snapshot-dom')
|
||||
const beautify = require('js-beautify').html
|
||||
|
||||
// converts DOM element to a JSON object
|
||||
function serializeDomElement ($el) {
|
||||
// console.log('snapshot value!', $el)
|
||||
const json = sd.toJSON($el[0])
|
||||
// console.log('as json', json)
|
||||
|
||||
// hmm, why is value not serialized?
|
||||
if ($el.context.value && !json.attributes.value) {
|
||||
json.attributes.value = $el.context.value
|
||||
}
|
||||
|
||||
return deleteReactIdFromJson(json)
|
||||
}
|
||||
|
||||
// remove React id, too transient
|
||||
function deleteReactIdFromJson (json) {
|
||||
if (json.attributes) {
|
||||
delete json.attributes['data-reactid']
|
||||
}
|
||||
|
||||
if (Array.isArray(json.childNodes)) {
|
||||
json.childNodes.forEach(deleteReactIdFromJson)
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
const stripReactIdAttributes = (html) => {
|
||||
const dataReactId = /data\-reactid="[\.\d\$\-abcdfef]+"/g
|
||||
return html.replace(dataReactId, '')
|
||||
}
|
||||
|
||||
const serializeReactToHTML = (el$) => {
|
||||
const html = el$[0].outerHTML
|
||||
const stripped = stripReactIdAttributes(html)
|
||||
const options = {
|
||||
wrap_line_length: 80,
|
||||
indent_inner_html: true,
|
||||
indent_size: 2,
|
||||
wrap_attributes: 'force'
|
||||
}
|
||||
const pretty = beautify(stripped, options)
|
||||
return pretty
|
||||
}
|
||||
|
||||
const identity = (x) => x
|
||||
|
||||
const publicProps = (name) => !name.startsWith('__')
|
||||
|
||||
const countSnapshots = (snapshots) =>
|
||||
Object.keys(snapshots).filter(publicProps).length
|
||||
|
||||
module.exports = {
|
||||
SNAPSHOT_FILE_NAME: 'snapshots.js',
|
||||
serializeDomElement,
|
||||
serializeReactToHTML,
|
||||
identity,
|
||||
countSnapshots
|
||||
}
|
79
src/utils/compareValues.js
Normal file
79
src/utils/compareValues.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
|
||||
const containsDiffChars = str => ["╺", "┿", "╳"].some(emoji => str.includes(emoji));
|
||||
const isNestedData = (expected, value) => expected && value && typeof expected == 'object' && typeof value == 'object';
|
||||
|
||||
|
||||
const checkDataState = (expected, value) => {
|
||||
let result;
|
||||
if (expected === value) {
|
||||
result = `| ✅ "${value}",`;
|
||||
} else {
|
||||
result = `| ⭕ "⭕╳ ${JSON.stringify(expected)?.replaceAll('"', "'")} | ${value?.replaceAll('"', "'")}",`;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const compare = (expected, value) => {
|
||||
let compareResult = "";
|
||||
|
||||
if (isNestedData(expected, value)) {
|
||||
if (Array.isArray(expected)) {
|
||||
compareResult += `[`;
|
||||
let dataX, dataY;
|
||||
|
||||
if (expected.length >= value.length) {
|
||||
dataX = expected;
|
||||
dataY = value;
|
||||
} else {
|
||||
dataX = value;
|
||||
dataY = expected;
|
||||
}
|
||||
|
||||
dataX.forEach(function (item, index) {
|
||||
const resultset = compare(item, dataY[index]);
|
||||
compareResult += resultset.result;
|
||||
});
|
||||
compareResult += `],`;
|
||||
} else {
|
||||
let dataX, dataY;
|
||||
compareResult += `{`;
|
||||
if (Object.keys(expected).length >= Object.keys(value).length) {
|
||||
dataX = expected;
|
||||
dataY = value;
|
||||
} else {
|
||||
dataX = value;
|
||||
dataY = expected;
|
||||
}
|
||||
|
||||
Object.keys(dataX).forEach((key) => {
|
||||
const resultset = compare(dataX[key], dataY[key]);
|
||||
compareResult += `"${key}": ${resultset.result}`;
|
||||
});
|
||||
compareResult += `},`;
|
||||
}
|
||||
} else {
|
||||
compareResult = checkDataState(expected, value);
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
success: !containsDiffChars(compareResult),
|
||||
result: compareResult,
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, result: compareResult };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* expected: { [k:string]: any},
|
||||
* value: {[k:string]:any}
|
||||
* }} values
|
||||
*/
|
||||
|
||||
module.exports = function compareValues(values) {
|
||||
return compare(values.expected, values.value);
|
||||
};
|
16
src/utils/serializers/deleteTransientIdsFromJson.js
Normal file
16
src/utils/serializers/deleteTransientIdsFromJson.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
// remove React and Angular ids, which are transient
|
||||
module.exports = function deleteTransientIdsFromJson(json) {
|
||||
if (json.attributes) {
|
||||
delete json.attributes["data-reactid"];
|
||||
|
||||
Object.keys(json.attributes)
|
||||
.filter((key) => key.startsWith("_ng"))
|
||||
.forEach((attr) => delete json.attributes[attr]);
|
||||
delete json.attributes[""];
|
||||
}
|
||||
|
||||
if (Array.isArray(json.childNodes)) {
|
||||
json.childNodes.forEach(deleteTransientIdsFromJson);
|
||||
}
|
||||
return json;
|
||||
};
|
16
src/utils/serializers/serializeDomElement.js
Normal file
16
src/utils/serializers/serializeDomElement.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
const sd = require("@wildpeaks/snapshot-dom");
|
||||
const deleteTransientIdsFromJson = require("./deleteTransientIdsFromJson");
|
||||
|
||||
// converts DOM element to a JSON object
|
||||
module.exports = function serializeDomElement($el) {
|
||||
// console.log('snapshot value!', $el)
|
||||
const json = sd.toJSON($el[0]);
|
||||
// console.log('as json', json)
|
||||
|
||||
// hmm, why is value not serialized?
|
||||
if ($el.context.value && !json.attributes.value) {
|
||||
json.attributes.value = $el.context.value;
|
||||
}
|
||||
|
||||
return deleteTransientIdsFromJson(json);
|
||||
};
|
15
src/utils/serializers/serializeToHTML.js
Normal file
15
src/utils/serializers/serializeToHTML.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const stripTransientIdAttributes = require("./stripTransientIdAttributes");
|
||||
const beautify = require("js-beautify").html;
|
||||
|
||||
module.exports = (el$) => {
|
||||
const html = el$[0].outerHTML;
|
||||
const stripped = stripTransientIdAttributes(html);
|
||||
const options = {
|
||||
wrap_line_length: 80,
|
||||
indent_inner_html: true,
|
||||
indent_size: 2,
|
||||
wrap_attributes: "force",
|
||||
};
|
||||
const pretty = beautify(stripped, options);
|
||||
return pretty;
|
||||
};
|
5
src/utils/serializers/stripTransientIdAttributes.js
Normal file
5
src/utils/serializers/stripTransientIdAttributes.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = (html) => {
|
||||
const dataReactId = /data\-reactid="[\.\d\$\-abcdfef]+"/g;
|
||||
const angularId = /_ng(content|host)\-[0-9a-z-]+(="")?/g;
|
||||
return html.replace(dataReactId, "").replace(angularId, "");
|
||||
};
|
Loading…
Add table
Reference in a new issue