creevey 0.10.0-beta.4 → 0.10.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -41
- package/dist/client/addon/components/Addon.js +17 -7
- package/dist/client/addon/components/Addon.js.map +1 -1
- package/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/components/Tools.js +17 -7
- package/dist/client/addon/components/Tools.js.map +1 -1
- package/dist/client/addon/withCreevey.d.ts +2 -1
- package/dist/client/addon/withCreevey.js +11 -1
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
- package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
- package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
- package/dist/client/shared/components/PageHeader/PageHeader.js +20 -8
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.d.ts +1 -1
- package/dist/client/shared/components/ResultsPage.js +43 -13
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +8 -1
- package/dist/client/shared/creeveyClientApi.js.map +1 -1
- package/dist/client/shared/helpers.d.ts +1 -3
- package/dist/client/shared/helpers.js +4 -19
- package/dist/client/shared/helpers.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +42 -14
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +20 -7
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyLoader.js +2 -2
- package/dist/client/web/CreeveyLoader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +2 -2
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +1 -1
- package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
- package/dist/client/web/KeyboardEventsContext.js +79 -64
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/index-B0Xv0lOY.js +802 -0
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +17 -7
- package/dist/client/web/index.js.map +1 -1
- package/dist/client/web/themes.d.ts +2 -0
- package/dist/client/web/themes.js +22 -0
- package/dist/client/web/themes.js.map +1 -0
- package/dist/creevey.js +16 -9
- package/dist/creevey.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.js +27 -5
- package/dist/server/config.js.map +1 -1
- package/dist/server/connection.d.ts +3 -0
- package/dist/server/connection.js +28 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/docker.d.ts +1 -1
- package/dist/server/docker.js +56 -32
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +64 -11
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +2 -1
- package/dist/server/logger.js +7 -3
- package/dist/server/logger.js.map +1 -1
- package/dist/server/master/api.js +1 -1
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/pool.d.ts +4 -3
- package/dist/server/master/pool.js +13 -66
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +71 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +3 -0
- package/dist/server/master/runner.js +76 -10
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +1 -1
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.js +13 -11
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.d.ts +1 -1
- package/dist/server/playwright/docker-file.js +15 -6
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/docker.d.ts +2 -1
- package/dist/server/playwright/docker.js +10 -2
- package/dist/server/playwright/docker.js.map +1 -1
- package/dist/server/playwright/index-source.mjs +16 -0
- package/dist/server/playwright/internal.d.ts +6 -6
- package/dist/server/playwright/internal.js +143 -91
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +1 -1
- package/dist/server/playwright/webdriver.js +5 -8
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +6 -4
- package/dist/server/providers/browser.js.map +1 -1
- package/dist/server/providers/hybrid.js +1 -1
- package/dist/server/providers/hybrid.js.map +1 -1
- package/dist/server/reporter.d.ts +4 -19
- package/dist/server/reporter.js +30 -21
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +3 -4
- package/dist/server/selenium/internal.js +127 -108
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +8 -6
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +1 -1
- package/dist/server/selenium/webdriver.js +5 -9
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +2 -2
- package/dist/server/testsFiles/parser.js +45 -5
- package/dist/server/testsFiles/parser.js.map +1 -1
- package/dist/server/utils.d.ts +19 -1
- package/dist/server/utils.js +87 -8
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +5 -4
- package/dist/server/webdriver.js +23 -10
- package/dist/server/webdriver.js.map +1 -1
- package/dist/server/worker/chai-image.d.ts +1 -2
- package/dist/server/worker/chai-image.js +4 -3
- package/dist/server/worker/chai-image.js.map +1 -1
- package/dist/server/worker/context.d.ts +3 -0
- package/dist/server/worker/context.js +15 -0
- package/dist/server/worker/context.js.map +1 -0
- package/dist/server/worker/match-image.d.ts +4 -4
- package/dist/server/worker/match-image.js +7 -4
- package/dist/server/worker/match-image.js.map +1 -1
- package/dist/server/worker/start.js +45 -73
- package/dist/server/worker/start.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/types.d.ts +40 -8
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/docs/cli.md +12 -0
- package/docs/config.md +179 -165
- package/docs/storybook.md +60 -0
- package/docs/tests.md +50 -45
- package/package.json +64 -63
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/withCreevey.ts +10 -2
- package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
- package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
- package/src/client/shared/components/PageHeader/PageHeader.tsx +4 -2
- package/src/client/shared/components/ResultsPage.tsx +31 -8
- package/src/client/shared/creeveyClientApi.ts +9 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +27 -8
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyLoader.tsx +1 -1
- package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
- package/src/client/web/KeyboardEventsContext.tsx +61 -73
- package/src/client/web/themes.ts +24 -0
- package/src/creevey.ts +16 -10
- package/src/server/config.ts +28 -6
- package/src/server/connection.ts +26 -0
- package/src/server/docker.ts +63 -34
- package/src/server/index.ts +72 -14
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +23 -59
- package/src/server/master/queue.ts +77 -0
- package/src/server/master/runner.ts +94 -10
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +16 -11
- package/src/server/playwright/docker-file.ts +18 -6
- package/src/server/playwright/docker.ts +16 -3
- package/src/server/playwright/index-source.mjs +16 -0
- package/src/server/playwright/internal.ts +182 -111
- package/src/server/playwright/webdriver.ts +6 -9
- package/src/server/providers/browser.ts +6 -4
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +37 -34
- package/src/server/selenium/internal.ts +131 -116
- package/src/server/selenium/selenoid.ts +8 -6
- package/src/server/selenium/webdriver.ts +6 -10
- package/src/server/telemetry.ts +2 -2
- package/src/server/testsFiles/parser.ts +52 -4
- package/src/server/utils.ts +97 -9
- package/src/server/webdriver.ts +24 -16
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/context.ts +14 -0
- package/src/server/worker/match-image.ts +12 -8
- package/src/server/worker/start.ts +49 -86
- package/src/shared/index.ts +1 -1
- package/src/types.ts +44 -8
- package/types/global.d.ts +1 -0
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
- package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
package/docs/tests.md
CHANGED
@@ -1,63 +1,68 @@
|
|
1
|
-
## Write tests
|
1
|
+
## Write interactive screenshot tests
|
2
2
|
|
3
|
-
|
3
|
+
In most cases following Storybook's ideology of [writing stories](https://storybook.js.org/docs/get-started/whats-a-story) is enough to test your UI components. Where each component has a separate stories file with its different states. But sometimes you might have pretty complicated components with a lot of interactions and internal states. In this case, you can write tests for your stories.
|
4
4
|
|
5
|
-
|
6
|
-
import React from 'react';
|
7
|
-
import { Story } from '@storybook/react';
|
8
|
-
import { CreeveyStory } from 'creevey';
|
9
|
-
import MyComponent from './src/components/MyComponent';
|
5
|
+
There are two different ways how to write interactive tests with Creevey:
|
10
6
|
|
11
|
-
|
7
|
+
### Write tests in `*.creevey.ts` files
|
12
8
|
|
13
|
-
|
14
|
-
Basic.parameters = {
|
15
|
-
creevey: {
|
16
|
-
captureElement: '#storybook-root',
|
17
|
-
tests: {
|
18
|
-
async click() {
|
19
|
-
await this.browser.actions().click(this.captureElement).perform();
|
9
|
+
It's the recommended way to write tests. It allows you to run these tests by Creevey itself and utilize webdriver benefits. The crucial part of it is webdriver action calls are more close to real user interactions and mitigate flakiness and false-negative results. Here is a simple example of how to write tests in `*.creevey.ts` files
|
20
10
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
11
|
+
```ts
|
12
|
+
// stories/MyComponent.creevey.ts
|
13
|
+
import { kind, story, test } from 'creevey';
|
14
|
+
|
15
|
+
kind('MyComponent', () => {
|
16
|
+
story('Story', ({ setStoryParameters }) => {
|
17
|
+
// It's possible to pass Creevey parameters to story
|
18
|
+
setStoryParameters({
|
19
|
+
captureElement: 'span[data-test-id~="x"]',
|
20
|
+
ignoreElements: [],
|
21
|
+
});
|
22
|
+
|
23
|
+
test('idle', async (context) => {
|
24
|
+
await context.matchImage(await context.takeScreenshot());
|
25
|
+
});
|
26
|
+
|
27
|
+
test('input', async (context) => {
|
28
|
+
await context.webdriver.keyboard.press('Tab');
|
29
|
+
const focus = await context.takeScreenshot();
|
30
|
+
await context.webdriver.keyboard.type('Hello Creevey');
|
31
|
+
const input = await context.takeScreenshot();
|
32
|
+
await context.matchImages({ focus, input });
|
33
|
+
});
|
34
|
+
});
|
35
|
+
});
|
26
36
|
```
|
27
37
|
|
28
|
-
|
38
|
+
In the example above, we used Playwright API to interact with the story. But Creevey also supports Selenium webdriver. And in that case `context.webdriver` will be an instance of Selenium webdriver. Obviously Selenium API is different from Playwright.
|
29
39
|
|
30
|
-
|
40
|
+
### Using Storybook's `play` function
|
31
41
|
|
32
|
-
|
33
|
-
import React from 'react';
|
34
|
-
import { CSFStory } from 'creevey';
|
35
|
-
import MyForm from './src/components/MyForm';
|
42
|
+
Storybook allows you to write tests in the story file itself by using [`play` function](https://storybook.js.org/docs/writing-tests/component-testing). It's a good way to write simple tests. But there are couple drawbacks of this approach:
|
36
43
|
|
37
|
-
|
44
|
+
- You can have only one test per story. Which is not a big deal, but sometimes you might not want to have multiple stories with the same markup.
|
45
|
+
- Tests are running in browser environment and use https://testing-library.com API under the hood. It's good for unit tests, but might not be suitable for visual regression tests, because testing-library relies on DOM API and not even close to real user interactions. For example, you might have a button that could be visible for user, but it's covered by some other transparent element. With testing-library the button easily accessible and clickable, but the user can't interact with it.
|
38
46
|
|
39
|
-
|
40
|
-
Basic.story = {
|
41
|
-
parameters: {
|
42
|
-
creevey: {
|
43
|
-
captureElement: '#storybook-root',
|
44
|
-
delay: 1000,
|
45
|
-
tests: {
|
46
|
-
async submit() {
|
47
|
-
const input = await this.browser.findElement({ css: '.my-input' });
|
47
|
+
Here is an example of how to write tests using Storybook's `play` function:
|
48
48
|
|
49
|
-
|
49
|
+
```tsx
|
50
|
+
// stories/MyComponent.stories.tsx
|
51
|
+
import React from 'react';
|
52
|
+
import { Meta, StoryObj } from '@storybook/react';
|
53
|
+
import { fireEvent, within } from '@storybook/test';
|
54
|
+
import MyComponent from './src/components/MyComponent';
|
50
55
|
|
51
|
-
|
56
|
+
export default {
|
57
|
+
title: 'MyComponent',
|
58
|
+
component: MyComponent,
|
59
|
+
};
|
52
60
|
|
53
|
-
|
61
|
+
export const Basic: StoryObj<typeof MyComponent> = {
|
62
|
+
play: async ({ canvasElement }) => {
|
63
|
+
const slider = await within(canvasElement).findByTestId('slider');
|
54
64
|
|
55
|
-
|
56
|
-
},
|
57
|
-
},
|
58
|
-
},
|
65
|
+
await fireEvent.change(slider, { target: { value: 50 } });
|
59
66
|
},
|
60
67
|
};
|
61
68
|
```
|
62
|
-
|
63
|
-
NOTE: In this example I fill some simple form and submit it. Also as you could see, I taking two different screenshots `empty` and `submitted` and assert these in the end.
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "creevey",
|
3
3
|
"description": "Cross-browser screenshot testing tool for Storybook with fancy UI Runner",
|
4
|
-
"version": "0.10.0-beta.
|
4
|
+
"version": "0.10.0-beta.40",
|
5
5
|
"type": "commonjs",
|
6
6
|
"bin": "dist/cli.js",
|
7
7
|
"main": "./dist/index.js",
|
@@ -64,7 +64,7 @@
|
|
64
64
|
"build": "yarn prebuild && yarn build:client && yarn build:creevey && yarn postbuild",
|
65
65
|
"build:client": "vite build",
|
66
66
|
"build:creevey": "tsc --build tsconfig.prod.json",
|
67
|
-
"postbuild": "cp \"\"scripts/dist/*.d.ts\"\" dist/",
|
67
|
+
"postbuild": "cp \"\"scripts/dist/*.d.ts\"\" dist/ && cp \"\"src/server/playwright/index-source.mjs\"\" dist/server/playwright/index-source.mjs",
|
68
68
|
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
|
69
69
|
"build-storybook": "storybook build",
|
70
70
|
"chromatic": "chromatic --project-token=chpt_80df83ca94e6fb4",
|
@@ -74,11 +74,11 @@
|
|
74
74
|
"node": ">=18.0"
|
75
75
|
},
|
76
76
|
"peerDependencies": {
|
77
|
-
"playwright": "*",
|
77
|
+
"playwright-core": "*",
|
78
78
|
"selenium-webdriver": "*"
|
79
79
|
},
|
80
80
|
"peerDependenciesMeta": {
|
81
|
-
"playwright": {
|
81
|
+
"playwright-core": {
|
82
82
|
"optional": true
|
83
83
|
},
|
84
84
|
"selenium-webdriver": {
|
@@ -87,33 +87,33 @@
|
|
87
87
|
},
|
88
88
|
"dependencies": {
|
89
89
|
"@koa/cors": "^5.0.0",
|
90
|
-
"@octokit/core": "^6.1.
|
91
|
-
"@storybook/icons": "^1.
|
90
|
+
"@octokit/core": "^6.1.4",
|
91
|
+
"@storybook/icons": "^1.4.0",
|
92
92
|
"@types/chai": "^4.3.20",
|
93
|
-
"@types/dockerode": "^3.3.
|
93
|
+
"@types/dockerode": "^3.3.37",
|
94
94
|
"@types/koa": "^2.15.0",
|
95
95
|
"@types/koa-bodyparser": "^4.3.12",
|
96
96
|
"@types/koa-mount": "^4.0.5",
|
97
97
|
"@types/koa-static": "^4.0.4",
|
98
98
|
"@types/koa__cors": "^5.0.0",
|
99
|
-
"@types/lodash": "^4.17.
|
99
|
+
"@types/lodash": "^4.17.16",
|
100
100
|
"@types/micromatch": "^4.0.9",
|
101
101
|
"@types/minimist": "^1.2.5",
|
102
102
|
"@types/pixelmatch": "^5.2.6",
|
103
103
|
"@types/pngjs": "^6.0.5",
|
104
|
-
"@types/qs": "^6.9.
|
105
|
-
"@types/react": "^18.3.
|
106
|
-
"@types/react-dom": "^18.3.
|
107
|
-
"@types/selenium-webdriver": "^4.1.
|
104
|
+
"@types/qs": "^6.9.18",
|
105
|
+
"@types/react": "^18.3.20",
|
106
|
+
"@types/react-dom": "^18.3.6",
|
107
|
+
"@types/selenium-webdriver": "^4.1.28",
|
108
108
|
"@types/shelljs": "^0.8.15",
|
109
|
-
"@types/ws": "^8.
|
109
|
+
"@types/ws": "^8.18.1",
|
110
110
|
"chai": "^4.5.0",
|
111
111
|
"chalk": "^4.1.2",
|
112
|
-
"chokidar": "^4.0.
|
113
|
-
"dockerode": "^4.0.
|
112
|
+
"chokidar": "^4.0.3",
|
113
|
+
"dockerode": "^4.0.5",
|
114
114
|
"find-cache-dir": "^5.0.0",
|
115
115
|
"get-port": "^7.1.0",
|
116
|
-
"koa": "^2.
|
116
|
+
"koa": "^2.16.0",
|
117
117
|
"koa-bodyparser": "^4.4.1",
|
118
118
|
"koa-mount": "^4.0.0",
|
119
119
|
"koa-static": "^5.0.0",
|
@@ -122,72 +122,73 @@
|
|
122
122
|
"loglevel-plugin-prefix": "^0.8.4",
|
123
123
|
"micromatch": "^4.0.8",
|
124
124
|
"minimist": "^1.2.8",
|
125
|
-
"odiff-bin": "^3.1
|
125
|
+
"odiff-bin": "^3.2.1",
|
126
|
+
"package-manager-detector": "^0.2.11",
|
127
|
+
"pidtree": "^0.6.0",
|
126
128
|
"pixelmatch": "^6.0.0",
|
127
129
|
"pngjs": "^7.0.0",
|
128
130
|
"polished": "^4.3.1",
|
129
|
-
"qs": "^6.
|
130
|
-
"semver": "^7.
|
131
|
-
"shelljs": "^0.
|
131
|
+
"qs": "^6.14.0",
|
132
|
+
"semver": "^7.7.1",
|
133
|
+
"shelljs": "^0.9.2",
|
132
134
|
"tar-stream": "^3.1.7",
|
133
|
-
"tsx": "^4.19.
|
134
|
-
"uuid": "^11.0
|
135
|
-
"ws": "^8.18.
|
136
|
-
"yocto-spinner": "^0.
|
135
|
+
"tsx": "^4.19.3",
|
136
|
+
"uuid": "^11.1.0",
|
137
|
+
"ws": "^8.18.1",
|
138
|
+
"yocto-spinner": "^0.2.1"
|
137
139
|
},
|
138
140
|
"devDependencies": {
|
139
|
-
"@chromatic-com/storybook": "^3.2.
|
140
|
-
"@eslint/js": "^9.
|
141
|
-
"@storybook/addon-essentials": "^8.
|
142
|
-
"@storybook/addon-interactions": "^8.
|
143
|
-
"@storybook/blocks": "^8.
|
144
|
-
"@storybook/channels": "^8.
|
145
|
-
"@storybook/components": "^8.
|
146
|
-
"@storybook/
|
147
|
-
"@storybook/
|
148
|
-
"@storybook/
|
149
|
-
"@storybook/react": "^8.
|
150
|
-
"@storybook/
|
151
|
-
"@storybook/
|
152
|
-
"@storybook/
|
153
|
-
"@storybook/types": "^8.4.1",
|
141
|
+
"@chromatic-com/storybook": "^3.2.6",
|
142
|
+
"@eslint/js": "^9.23.0",
|
143
|
+
"@storybook/addon-essentials": "^8.6.12",
|
144
|
+
"@storybook/addon-interactions": "^8.6.12",
|
145
|
+
"@storybook/blocks": "^8.6.12",
|
146
|
+
"@storybook/channels": "^8.6.12",
|
147
|
+
"@storybook/components": "^8.6.12",
|
148
|
+
"@storybook/manager-api": "^8.6.12",
|
149
|
+
"@storybook/preview-api": "^8.6.12",
|
150
|
+
"@storybook/react": "^8.6.12",
|
151
|
+
"@storybook/react-vite": "^8.6.12",
|
152
|
+
"@storybook/test": "^8.6.12",
|
153
|
+
"@storybook/theming": "^8.6.12",
|
154
|
+
"@storybook/types": "^8.6.12",
|
154
155
|
"@types/eslint": "^9.6.1",
|
155
156
|
"@types/eslint__js": "^8.42.3",
|
156
|
-
"@types/node": "^18.19.
|
157
|
+
"@types/node": "^18.19.86",
|
157
158
|
"@types/resize-observer-browser": "^0.1.11",
|
158
|
-
"@types/semver": "^7",
|
159
|
+
"@types/semver": "^7.7.0",
|
159
160
|
"@types/tar-stream": "^3.1.3",
|
160
161
|
"@types/tmp": "^0.2.6",
|
161
|
-
"@vitejs/plugin-react-swc": "^3.
|
162
|
-
"browserstack-local": "^1.5.
|
163
|
-
"chromatic": "^11.
|
164
|
-
"concurrently": "^9.
|
162
|
+
"@vitejs/plugin-react-swc": "^3.8.1",
|
163
|
+
"browserstack-local": "^1.5.6",
|
164
|
+
"chromatic": "^11.28.0",
|
165
|
+
"concurrently": "^9.1.2",
|
165
166
|
"conventional-changelog-cli": "^5.0.0",
|
166
|
-
"dotenv": "^16.4.
|
167
|
-
"eslint": "^9.
|
168
|
-
"eslint-config-prettier": "^
|
169
|
-
"eslint-plugin-prettier": "^5.2.
|
170
|
-
"eslint-plugin-react": "^7.37.
|
171
|
-
"eslint-plugin-react-hooks": "^5.
|
167
|
+
"dotenv": "^16.4.7",
|
168
|
+
"eslint": "^9.23.0",
|
169
|
+
"eslint-config-prettier": "^10.1.1",
|
170
|
+
"eslint-plugin-prettier": "^5.2.6",
|
171
|
+
"eslint-plugin-react": "^7.37.5",
|
172
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
172
173
|
"git-cz": "^4.9.0",
|
173
|
-
"globals": "^
|
174
|
-
"husky": "^9.1.
|
174
|
+
"globals": "^16.0.0",
|
175
|
+
"husky": "^9.1.7",
|
175
176
|
"immer": "^10.1.1",
|
176
|
-
"lint-staged": "^15.
|
177
|
+
"lint-staged": "^15.5.0",
|
177
178
|
"pinst": "^3.0.0",
|
178
|
-
"playwright-core": "^1.
|
179
|
-
"prettier": "^3.
|
179
|
+
"playwright-core": "^1.51.1",
|
180
|
+
"prettier": "^3.5.3",
|
180
181
|
"react": "^18.3.1",
|
181
182
|
"react-dom": "^18.3.1",
|
182
183
|
"react-is": "^18.3.1",
|
183
|
-
"selenium-webdriver": "^4.
|
184
|
-
"storybook": "^8.
|
184
|
+
"selenium-webdriver": "^4.30.0",
|
185
|
+
"storybook": "^8.6.12",
|
185
186
|
"tmp": "^0.2.3",
|
186
|
-
"typescript": "^5.
|
187
|
-
"typescript-eslint": "^8.
|
188
|
-
"use-immer": "^0.
|
189
|
-
"vite": "^5.4.
|
190
|
-
"vitest": "^2.1.
|
187
|
+
"typescript": "^5.8.2",
|
188
|
+
"typescript-eslint": "^8.29.0",
|
189
|
+
"use-immer": "^0.11.0",
|
190
|
+
"vite": "^5.4.17",
|
191
|
+
"vitest": "^2.1.9"
|
191
192
|
},
|
192
193
|
"config": {
|
193
194
|
"commitizen": {
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import React, { JSX } from 'react';
|
2
2
|
import { Loader } from '@storybook/components';
|
3
3
|
import { styled } from '@storybook/theming';
|
4
|
-
import { TestData } from '../../../types.js';
|
4
|
+
import { noop, TestData } from '../../../types.js';
|
5
5
|
import { ResultsPage } from '../../shared/components/ResultsPage.js';
|
6
6
|
import { getTestPath } from '../../shared/helpers.js';
|
7
7
|
import TestSelect from './TestSelect.js';
|
8
|
-
|
8
|
+
|
9
9
|
interface PanelProps {
|
10
10
|
tests: TestData[];
|
11
11
|
selectedTestId: string;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { Renderer, StoryContextForEnhancers } from '@storybook/
|
1
|
+
import type { Renderer, StoryContextForEnhancers } from '@storybook/types';
|
2
2
|
import { makeDecorator, PreviewWeb, StoryStore } from '@storybook/preview-api';
|
3
3
|
import { Channel } from '@storybook/channels';
|
4
4
|
import {
|
@@ -12,9 +12,11 @@ import {
|
|
12
12
|
} from '../../types.js';
|
13
13
|
import { serializeRawStories } from '../../shared/index.js';
|
14
14
|
import { getConnectionUrl } from '../shared/helpers.js';
|
15
|
+
import isEqual from 'lodash/isEqual.js';
|
15
16
|
|
16
17
|
declare global {
|
17
18
|
interface Window {
|
19
|
+
__CREEVEY_ENV__: boolean;
|
18
20
|
__CREEVEY_SERVER_HOST__: string;
|
19
21
|
__CREEVEY_SERVER_PORT__: number;
|
20
22
|
__CREEVEY_WORKER_ID__: number;
|
@@ -139,6 +141,7 @@ let captureResolver: () => void;
|
|
139
141
|
let waitForCreevey: Promise<void>;
|
140
142
|
let creeveyReady: () => void;
|
141
143
|
let setStoriesCounter = 0;
|
144
|
+
let globals = {};
|
142
145
|
|
143
146
|
export function withCreevey(): ReturnType<typeof makeDecorator> {
|
144
147
|
const addonsChannel = (): Channel => window.__STORYBOOK_ADDONS_CHANNEL__;
|
@@ -225,7 +228,10 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
225
228
|
}
|
226
229
|
}
|
227
230
|
|
228
|
-
function updateGlobals(
|
231
|
+
function updateGlobals(newGlobals: StorybookGlobals): void {
|
232
|
+
if (isEqual(globals, newGlobals)) return;
|
233
|
+
|
234
|
+
globals = newGlobals;
|
229
235
|
addonsChannel().emit(StorybookEvents.UPDATE_GLOBALS, { globals });
|
230
236
|
}
|
231
237
|
|
@@ -274,6 +280,7 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
274
280
|
});
|
275
281
|
}
|
276
282
|
|
283
|
+
window.__CREEVEY_ENV__ = false;
|
277
284
|
window.__CREEVEY_GET_STORIES__ = getStories;
|
278
285
|
window.__CREEVEY_SELECT_STORY__ = selectStory;
|
279
286
|
window.__CREEVEY_UPDATE_GLOBALS__ = updateGlobals;
|
@@ -315,6 +322,7 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
315
322
|
});
|
316
323
|
}
|
317
324
|
|
325
|
+
// TODO It's not accessible from the outside the package
|
318
326
|
export async function capture(options?: CaptureOptions): Promise<void> {
|
319
327
|
if (!isTestBrowser) return;
|
320
328
|
|
@@ -46,6 +46,24 @@ export const SwapView = withTheme(({ theme, expect, actual, diff }: ViewPropsWit
|
|
46
46
|
setImage((prevImage) => (prevImage == 'actual' ? 'expect' : 'actual'));
|
47
47
|
}, []);
|
48
48
|
|
49
|
+
const handleKeyDown = useCallback(
|
50
|
+
(e: KeyboardEvent) => {
|
51
|
+
if (e.code === 'Space' && e.altKey) {
|
52
|
+
e.preventDefault();
|
53
|
+
handleChangeView();
|
54
|
+
}
|
55
|
+
},
|
56
|
+
[handleChangeView],
|
57
|
+
);
|
58
|
+
|
59
|
+
useEffect(() => {
|
60
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
61
|
+
|
62
|
+
return () => {
|
63
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
64
|
+
};
|
65
|
+
}, [handleKeyDown]);
|
66
|
+
|
49
67
|
useEffect(() => {
|
50
68
|
if (loaded) readyForCapture();
|
51
69
|
}, [loaded]);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { JSX, useEffect } from 'react';
|
1
|
+
import React, { JSX, useContext, useEffect } from 'react';
|
2
2
|
import { Tabs } from '@storybook/components';
|
3
3
|
import { CloseAltIcon } from '@storybook/icons';
|
4
4
|
import { styled, withTheme, Theme } from '@storybook/theming';
|
@@ -6,6 +6,7 @@ import { ImagesViewMode, Images } from '../../../../types.js';
|
|
6
6
|
import { getImageUrl } from '../../helpers.js';
|
7
7
|
import { ImagePreview } from './ImagePreview.js';
|
8
8
|
import { viewModes } from '../../viewMode.js';
|
9
|
+
import { CreeveyContext } from '../../../web/CreeveyContext.js';
|
9
10
|
|
10
11
|
interface PageHeaderProps {
|
11
12
|
title: string[];
|
@@ -76,6 +77,7 @@ export function PageHeader({
|
|
76
77
|
onImageChange,
|
77
78
|
onViewModeChange,
|
78
79
|
}: PageHeaderProps): JSX.Element | null {
|
80
|
+
const { isReport } = useContext(CreeveyContext);
|
79
81
|
const imageEntires = Object.entries(images) as [string, Images][];
|
80
82
|
|
81
83
|
const handleViewModeChange = (mode: string): void => {
|
@@ -110,7 +112,7 @@ export function PageHeader({
|
|
110
112
|
<ImagePreview
|
111
113
|
key={name}
|
112
114
|
imageName={name}
|
113
|
-
url={`${getImageUrl(title, name)}/${image.actual}`}
|
115
|
+
url={`${getImageUrl(title, name, isReport)}/${image.actual}`}
|
114
116
|
isActive={name === imageName}
|
115
117
|
onClick={onImageChange}
|
116
118
|
error={imagesWithError.includes(name)}
|
@@ -1,12 +1,13 @@
|
|
1
|
-
import React, { JSX, useState } from 'react';
|
1
|
+
import React, { JSX, useCallback, useContext, useEffect, useState } from 'react';
|
2
2
|
import { Placeholder, ScrollArea } from '@storybook/components';
|
3
3
|
import { styled, withTheme, Theme } from '@storybook/theming';
|
4
4
|
import { ImagesView } from './ImagesView/ImagesView.js';
|
5
5
|
import { PageHeader } from './PageHeader/PageHeader.js';
|
6
6
|
import { PageFooter } from './PageFooter/PageFooter.js';
|
7
7
|
import { getImageUrl } from '../helpers.js';
|
8
|
-
import { getViewMode, VIEW_MODE_KEY } from '../viewMode.js';
|
8
|
+
import { getViewMode, VIEW_MODE_KEY, viewModes } from '../viewMode.js';
|
9
9
|
import { ImagesViewMode, TestResult } from '../../../types.js';
|
10
|
+
import { CreeveyContext } from '../../web/CreeveyContext.js';
|
10
11
|
|
11
12
|
interface ResultsPageProps {
|
12
13
|
path: string[];
|
@@ -65,8 +66,9 @@ export function ResultsPageInternal({
|
|
65
66
|
onRetryChange,
|
66
67
|
}: ResultsPageProps): JSX.Element {
|
67
68
|
const result = results[retry - 1] ?? {};
|
69
|
+
const { isReport } = useContext(CreeveyContext);
|
68
70
|
const [viewMode, setViewMode] = useState<ImagesViewMode>(getViewMode());
|
69
|
-
const url = getImageUrl(path, imageName);
|
71
|
+
const url = getImageUrl(path, imageName, isReport);
|
70
72
|
const image = result.images?.[imageName];
|
71
73
|
const canApprove = Boolean(image && approved?.[imageName] != retry - 1 && result.status != 'success');
|
72
74
|
const hasDiffAndExpect = canApprove && Boolean(image?.diff && image.expect);
|
@@ -77,10 +79,31 @@ export function ResultsPageInternal({
|
|
77
79
|
)
|
78
80
|
: [];
|
79
81
|
|
80
|
-
const
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
const handleKeyDown = useCallback(
|
83
|
+
(e: KeyboardEvent) => {
|
84
|
+
if (!canApprove) return;
|
85
|
+
if (e.code === 'Tab') {
|
86
|
+
e.preventDefault();
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
88
|
+
if (e.shiftKey) setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) - 1) % viewModes.length)!);
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
90
|
+
else setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) + 1) % viewModes.length)!);
|
91
|
+
}
|
92
|
+
},
|
93
|
+
[canApprove],
|
94
|
+
);
|
95
|
+
|
96
|
+
useEffect(() => {
|
97
|
+
localStorage.setItem(VIEW_MODE_KEY, viewMode);
|
98
|
+
}, [viewMode]);
|
99
|
+
|
100
|
+
useEffect(() => {
|
101
|
+
document.addEventListener('keydown', handleKeyDown, false);
|
102
|
+
|
103
|
+
return () => {
|
104
|
+
document.removeEventListener('keydown', handleKeyDown, false);
|
105
|
+
};
|
106
|
+
}, [handleKeyDown]);
|
84
107
|
|
85
108
|
return (
|
86
109
|
<Container height={height}>
|
@@ -92,7 +115,7 @@ export function ResultsPageInternal({
|
|
92
115
|
errorMessage={result.error}
|
93
116
|
showViewModes={hasDiffAndExpect}
|
94
117
|
viewMode={viewMode}
|
95
|
-
onViewModeChange={
|
118
|
+
onViewModeChange={setViewMode}
|
96
119
|
onImageChange={onImageChange}
|
97
120
|
imagesWithError={imagesWithError}
|
98
121
|
/>
|
@@ -12,6 +12,7 @@ export interface CreeveyClientApi {
|
|
12
12
|
|
13
13
|
export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
14
14
|
let clientApiResolver: (api: CreeveyClientApi) => void = noop;
|
15
|
+
let clientApiRejecter: (error: Error | Event) => void = noop;
|
15
16
|
const updateListeners = new Set<(update: CreeveyUpdate) => void>();
|
16
17
|
let statusRequest: Promise<CreeveyStatus> | null = null;
|
17
18
|
let statusResolver: (status: CreeveyStatus) => void = noop;
|
@@ -22,6 +23,10 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
22
23
|
ws.send(JSON.stringify(request));
|
23
24
|
}
|
24
25
|
|
26
|
+
ws.addEventListener('error', (event) => {
|
27
|
+
clientApiRejecter(event);
|
28
|
+
});
|
29
|
+
|
25
30
|
ws.addEventListener('open', () => {
|
26
31
|
clientApiResolver({
|
27
32
|
start(ids: string[]) {
|
@@ -64,5 +69,8 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
64
69
|
});
|
65
70
|
// TODO Reconnect
|
66
71
|
|
67
|
-
return new Promise((resolve) =>
|
72
|
+
return new Promise((resolve, reject) => {
|
73
|
+
clientApiResolver = resolve;
|
74
|
+
clientApiRejecter = reject;
|
75
|
+
});
|
68
76
|
}
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import { themes, ThemeVars } from '@storybook/theming';
|
2
1
|
import { parse, stringify } from 'qs';
|
3
2
|
import { RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
4
3
|
import { TestData, isTest, isDefined, TestStatus, CreeveySuite, CreeveyTest, CreeveyStatus } from '../../types.js';
|
@@ -300,12 +299,14 @@ export function getConnectionUrl(): string {
|
|
300
299
|
.join(':');
|
301
300
|
}
|
302
301
|
|
303
|
-
export function getImageUrl(path: string[], imageName: string): string {
|
302
|
+
export function getImageUrl(path: string[], imageName: string, isReport?: boolean): string {
|
304
303
|
// path => [title, story, test, browser]
|
305
304
|
const browser = path.slice(-1)[0];
|
306
305
|
const imagesUrl = window.location.host
|
307
306
|
? `${window.location.protocol}//${getConnectionUrl()}${
|
308
|
-
window.location.pathname == '/'
|
307
|
+
window.location.pathname == '/' && !isReport
|
308
|
+
? '/report'
|
309
|
+
: window.location.pathname.split('/').slice(0, -1).join('/')
|
309
310
|
}/${encodeURI(path.slice(0, -1).join('/'))}`
|
310
311
|
: encodeURI(path.slice(0, -1).join('/'));
|
311
312
|
|
@@ -393,27 +394,6 @@ export function useCalcScale(diffImageRef: RefObject<HTMLImageElement>, loaded:
|
|
393
394
|
return scale;
|
394
395
|
}
|
395
396
|
|
396
|
-
const CREEVEY_THEME = 'Creevey_theme';
|
397
|
-
|
398
|
-
function isTheme(theme?: string | null): theme is ThemeVars['base'] {
|
399
|
-
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
400
|
-
}
|
401
|
-
|
402
|
-
function initialTheme(): ThemeVars['base'] {
|
403
|
-
const theme = localStorage.getItem(CREEVEY_THEME);
|
404
|
-
return isTheme(theme) ? theme : 'light';
|
405
|
-
}
|
406
|
-
|
407
|
-
export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
|
408
|
-
const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
|
409
|
-
|
410
|
-
useEffect(() => {
|
411
|
-
localStorage.setItem(CREEVEY_THEME, theme);
|
412
|
-
}, [theme]);
|
413
|
-
|
414
|
-
return [theme, setTheme];
|
415
|
-
}
|
416
|
-
|
417
397
|
export function setSearchParams(testPath: string[]): void {
|
418
398
|
const pageUrl = `?${stringify({ testPath })}`;
|
419
399
|
window.history.pushState({ testPath }, '', pageUrl);
|