creevey 0.10.0-beta.9 → 0.10.0-rc.1
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/AUTHORS +2 -0
- package/CHANGELOG.md +281 -0
- package/README.md +19 -41
- package/dist/client/addon/components/Addon.js +18 -8
- 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/TestSelect.js +2 -2
- package/dist/client/addon/components/TestSelect.js.map +1 -1
- package/dist/client/addon/components/Tools.js +19 -9
- package/dist/client/addon/components/Tools.js.map +1 -1
- package/dist/client/addon/controller.d.ts +1 -1
- package/dist/client/addon/controller.js +3 -3
- package/dist/client/addon/controller.js.map +1 -1
- package/dist/client/addon/decorator.d.ts +1 -1
- package/dist/client/addon/makeDecorator.d.ts +9 -0
- package/dist/client/addon/makeDecorator.js +48 -0
- package/dist/client/addon/makeDecorator.js.map +1 -0
- package/dist/client/addon/manager.js +38 -39
- package/dist/client/addon/manager.js.map +1 -1
- package/dist/client/addon/preset.d.ts +0 -1
- package/dist/client/addon/preset.js +3 -2
- package/dist/client/addon/preset.js.map +1 -1
- package/dist/client/addon/preview.d.ts +1 -1
- package/dist/client/addon/withCreevey.d.ts +5 -3
- package/dist/client/addon/withCreevey.js +5 -20
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.d.ts +2 -2
- package/dist/client/shared/components/ImagesView/BlendView.js +18 -8
- package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/ImagesView.js +1 -1
- package/dist/client/shared/components/ImagesView/ImagesView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +2 -2
- package/dist/client/shared/components/ImagesView/SideBySideView.js +19 -9
- package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.d.ts +2 -2
- package/dist/client/shared/components/ImagesView/SlideView.js +19 -9
- package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.d.ts +2 -2
- package/dist/client/shared/components/ImagesView/SwapView.js +19 -9
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/common.d.ts +1 -1
- package/dist/client/shared/components/PageFooter/PageFooter.js +1 -1
- package/dist/client/shared/components/PageFooter/PageFooter.js.map +1 -1
- package/dist/client/shared/components/PageFooter/Paging.js +1 -1
- package/dist/client/shared/components/PageFooter/Paging.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +2 -2
- package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
- package/dist/client/shared/components/PageHeader/PageHeader.js +34 -13
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.d.ts +2 -2
- package/dist/client/shared/components/ResultsPage.js +22 -10
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +18 -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.d.ts +1 -0
- package/dist/client/web/CreeveyApp.js +22 -9
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +1 -0
- package/dist/client/web/CreeveyContext.js +18 -7
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyLoader.d.ts +1 -1
- package/dist/client/web/CreeveyLoader.js +3 -3
- package/dist/client/web/CreeveyLoader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Checkbox.d.ts +4 -4
- package/dist/client/web/CreeveyView/SideBar/Checkbox.js +36 -6
- package/dist/client/web/CreeveyView/SideBar/Checkbox.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +18 -8
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +26 -12
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +28 -17
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +32 -12
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +6 -6
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +20 -11
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -11
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +2 -2
- package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js +2 -2
- package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +2 -2
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +3 -2
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Toggle.js +1 -1
- package/dist/client/web/CreeveyView/SideBar/Toggle.js.map +1 -1
- package/dist/client/web/KeyboardEventsContext.js +17 -7
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/index-CtSq3IhG.js +518 -0
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +26 -11
- 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.d.ts +1 -1
- package/dist/creevey.js +122 -41
- package/dist/creevey.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/playwright/generator.d.ts +34 -0
- package/dist/playwright/generator.js +267 -0
- package/dist/playwright/generator.js.map +1 -0
- package/dist/playwright/helpers.d.ts +2 -0
- package/dist/playwright/helpers.js +29 -0
- package/dist/playwright/helpers.js.map +1 -0
- package/dist/playwright/reporter.d.ts +83 -0
- package/dist/playwright/reporter.js +334 -0
- package/dist/playwright/reporter.js.map +1 -0
- package/dist/playwright/setup.d.ts +3 -0
- package/dist/playwright/setup.js +72 -0
- package/dist/playwright/setup.js.map +1 -0
- package/dist/playwright.d.ts +1 -0
- package/dist/playwright.js +3 -1
- package/dist/playwright.js.map +1 -1
- package/dist/server/compare.d.ts +18 -0
- package/dist/server/compare.js +182 -0
- package/dist/server/compare.js.map +1 -0
- package/dist/server/config.d.ts +3 -3
- package/dist/server/config.js +75 -8
- 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 +54 -32
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +166 -64
- package/dist/server/index.js.map +1 -1
- package/dist/server/master/api.d.ts +11 -6
- package/dist/server/master/api.js +88 -25
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/handlers/capture-handler.d.ts +5 -0
- package/dist/server/master/handlers/capture-handler.js +25 -0
- package/dist/server/master/handlers/capture-handler.js.map +1 -0
- package/dist/server/master/handlers/index.d.ts +4 -0
- package/dist/server/master/handlers/index.js +21 -0
- package/dist/server/master/handlers/index.js.map +1 -0
- package/dist/server/master/handlers/ping-handler.d.ts +2 -0
- package/dist/server/master/handlers/ping-handler.js +8 -0
- package/dist/server/master/handlers/ping-handler.js.map +1 -0
- package/dist/server/master/handlers/static-handler.d.ts +1 -0
- package/dist/server/master/handlers/static-handler.js +20 -0
- package/dist/server/master/handlers/static-handler.js.map +1 -0
- package/dist/server/master/handlers/stories-handler.d.ts +4 -0
- package/dist/server/master/handlers/stories-handler.js +24 -0
- package/dist/server/master/handlers/stories-handler.js.map +1 -0
- package/dist/server/master/master.js +7 -24
- package/dist/server/master/master.js.map +1 -1
- package/dist/server/master/pool.d.ts +1 -0
- package/dist/server/master/pool.js +5 -3
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +1 -1
- package/dist/server/master/queue.js +14 -6
- package/dist/server/master/queue.js.map +1 -1
- package/dist/server/master/runner.d.ts +6 -6
- package/dist/server/master/runner.js +98 -130
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.d.ts +1 -1
- package/dist/server/master/server.js +193 -88
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.d.ts +1 -2
- package/dist/server/master/start.js +13 -29
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/master/testsManager.d.ts +81 -0
- package/dist/server/master/testsManager.js +282 -0
- package/dist/server/master/testsManager.js.map +1 -0
- package/dist/server/playwright/docker-file.d.ts +1 -1
- package/dist/server/playwright/docker-file.js +17 -8
- 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 +7 -7
- package/dist/server/playwright/internal.js +137 -79
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +3 -3
- package/dist/server/playwright/webdriver.js +0 -6
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +4 -3
- package/dist/server/providers/browser.js.map +1 -1
- package/dist/server/providers/hybrid.js +2 -2
- package/dist/server/providers/hybrid.js.map +1 -1
- package/dist/server/report.d.ts +10 -0
- package/dist/server/report.js +45 -0
- package/dist/server/report.js.map +1 -0
- package/dist/server/reporters/creevey.d.ts +7 -0
- package/dist/server/reporters/creevey.js +63 -0
- package/dist/server/reporters/creevey.js.map +1 -0
- package/dist/server/reporters/index.d.ts +2 -0
- package/dist/server/reporters/index.js +16 -0
- package/dist/server/reporters/index.js.map +1 -0
- package/dist/server/reporters/junit.d.ts +16 -0
- package/dist/server/reporters/junit.js +167 -0
- package/dist/server/reporters/junit.js.map +1 -0
- package/dist/server/reporters/teamcity.d.ts +7 -0
- package/dist/server/reporters/teamcity.js +60 -0
- package/dist/server/reporters/teamcity.js.map +1 -0
- package/dist/server/selenium/internal.d.ts +3 -3
- package/dist/server/selenium/internal.js +48 -34
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +12 -6
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +3 -3
- package/dist/server/selenium/webdriver.js +4 -8
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/shutdown.d.ts +1 -0
- package/dist/server/shutdown.js +23 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/stories.d.ts +0 -1
- package/dist/server/stories.js +0 -12
- package/dist/server/stories.js.map +1 -1
- package/dist/server/telemetry.js +3 -3
- package/dist/server/telemetry.js.map +1 -1
- package/dist/server/testsFiles/parser.js +45 -5
- package/dist/server/testsFiles/parser.js.map +1 -1
- package/dist/server/utils.d.ts +23 -0
- package/dist/server/utils.js +113 -13
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +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 +8 -12
- package/dist/server/worker/match-image.js +11 -178
- package/dist/server/worker/match-image.js.map +1 -1
- package/dist/server/worker/start.d.ts +2 -2
- package/dist/server/worker/start.js +27 -63
- package/dist/server/worker/start.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +9 -7
- package/dist/shared/index.js.map +1 -1
- package/dist/types.d.ts +84 -43
- package/dist/types.js +65 -1
- package/dist/types.js.map +1 -1
- package/docs/cli.md +80 -0
- package/docs/config.md +179 -165
- package/docs/examples/playwright-reporer/playwright.config.ts +36 -0
- package/docs/migration-0.9-to-0.10.md +182 -0
- package/docs/playwright-reporter.md +170 -0
- package/docs/storybook.md +60 -0
- package/docs/tests.md +50 -45
- package/package.json +78 -83
- package/playwright.config.mts +46 -0
- package/src/client/addon/components/Addon.tsx +1 -1
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/components/TestSelect.tsx +2 -2
- package/src/client/addon/components/Tools.tsx +2 -2
- package/src/client/addon/controller.ts +4 -4
- package/src/client/addon/makeDecorator.ts +69 -0
- package/src/client/addon/manager.ts +38 -37
- package/src/client/addon/preset.ts +2 -1
- package/src/client/addon/withCreevey.ts +10 -18
- package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
- package/src/client/shared/components/ImagesView/ImagesView.tsx +1 -1
- package/src/client/shared/components/ImagesView/SideBySideView.tsx +2 -2
- package/src/client/shared/components/ImagesView/SlideView.tsx +2 -2
- package/src/client/shared/components/ImagesView/SwapView.tsx +2 -2
- package/src/client/shared/components/ImagesView/common.ts +1 -1
- package/src/client/shared/components/PageFooter/PageFooter.tsx +1 -1
- package/src/client/shared/components/PageFooter/Paging.tsx +1 -1
- package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -1
- package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
- package/src/client/shared/components/ResultsPage.tsx +6 -4
- package/src/client/shared/creeveyClientApi.ts +19 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +5 -2
- package/src/client/web/CreeveyContext.tsx +2 -0
- package/src/client/web/CreeveyLoader.tsx +2 -2
- package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +3 -3
- package/src/client/web/CreeveyView/SideBar/Search.tsx +1 -1
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +21 -19
- package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +10 -8
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +9 -7
- package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +2 -2
- package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +3 -2
- package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -1
- package/src/client/web/index.tsx +10 -5
- package/src/client/web/themes.ts +24 -0
- package/src/creevey.ts +92 -38
- package/src/playwright/generator.ts +360 -0
- package/src/playwright/helpers.ts +31 -0
- package/src/playwright/reporter.ts +381 -0
- package/src/playwright/setup.ts +84 -0
- package/src/playwright.ts +1 -0
- package/src/server/compare.ts +260 -0
- package/src/server/config.ts +52 -9
- package/src/server/connection.ts +26 -0
- package/src/server/docker.ts +62 -34
- package/src/server/index.ts +165 -79
- package/src/server/master/api.ts +94 -28
- package/src/server/master/handlers/capture-handler.ts +20 -0
- package/src/server/master/handlers/index.ts +4 -0
- package/src/server/master/handlers/ping-handler.ts +6 -0
- package/src/server/master/handlers/static-handler.ts +16 -0
- package/src/server/master/handlers/stories-handler.ts +20 -0
- package/src/server/master/master.ts +10 -27
- package/src/server/master/pool.ts +7 -3
- package/src/server/master/queue.ts +21 -7
- package/src/server/master/runner.ts +123 -134
- package/src/server/master/server.ts +214 -101
- package/src/server/master/start.ts +19 -41
- package/src/server/master/testsManager.ts +316 -0
- package/src/server/playwright/docker-file.ts +20 -8
- package/src/server/playwright/docker.ts +16 -3
- package/src/server/playwright/index-source.mjs +16 -0
- package/src/server/playwright/internal.ts +169 -96
- package/src/server/playwright/webdriver.ts +4 -10
- package/src/server/providers/browser.ts +4 -3
- package/src/server/providers/hybrid.ts +2 -3
- package/src/server/report.ts +51 -0
- package/src/server/reporters/creevey.ts +74 -0
- package/src/server/reporters/index.ts +11 -0
- package/src/server/reporters/junit.ts +207 -0
- package/src/server/reporters/teamcity.ts +74 -0
- package/src/server/selenium/internal.ts +62 -45
- package/src/server/selenium/selenoid.ts +13 -6
- package/src/server/selenium/webdriver.ts +8 -12
- package/src/server/shutdown.ts +19 -0
- package/src/server/stories.ts +1 -12
- package/src/server/telemetry.ts +3 -3
- package/src/server/testsFiles/parser.ts +52 -4
- package/src/server/utils.ts +123 -14
- package/src/server/webdriver.ts +1 -1
- package/src/server/worker/context.ts +14 -0
- package/src/server/worker/match-image.ts +16 -248
- package/src/server/worker/start.ts +32 -75
- package/src/shared/index.ts +10 -8
- package/src/types.ts +91 -58
- package/types/global.d.ts +1 -0
- package/dist/client/web/assets/index-BE9CL5_G.js +0 -591
- package/dist/server/reporter.d.ts +0 -26
- package/dist/server/reporter.js +0 -108
- package/dist/server/reporter.js.map +0 -1
- package/dist/server/update.d.ts +0 -2
- package/dist/server/update.js +0 -53
- package/dist/server/update.js.map +0 -1
- package/src/server/reporter.ts +0 -139
- package/src/server/update.ts +0 -74
package/src/client/web/index.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { Suspense } from 'react';
|
1
|
+
import React, { Suspense, lazy } from 'react';
|
2
2
|
import { createRoot } from 'react-dom/client';
|
3
3
|
import { CreeveyApp } from './CreeveyApp.js';
|
4
4
|
|
@@ -24,7 +24,7 @@ function loadCreeveyData(): Promise<CreeveyStatus['tests']> {
|
|
24
24
|
});
|
25
25
|
}
|
26
26
|
|
27
|
-
const CreeveyAppAsync =
|
27
|
+
const CreeveyAppAsync = lazy(async () => {
|
28
28
|
let isReport = false;
|
29
29
|
let creeveyStatus: CreeveyStatus;
|
30
30
|
let creeveyApi: CreeveyClientApi | undefined;
|
@@ -36,11 +36,11 @@ const CreeveyAppAsync = React.lazy(async () => {
|
|
36
36
|
// NOTE: Failed to get status from API
|
37
37
|
// NOTE: It might happen on circle ci from artifact
|
38
38
|
isReport = true;
|
39
|
-
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
|
39
|
+
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
|
40
40
|
}
|
41
41
|
} else {
|
42
42
|
isReport = true;
|
43
|
-
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
|
43
|
+
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
|
44
44
|
}
|
45
45
|
|
46
46
|
return {
|
@@ -48,7 +48,12 @@ const CreeveyAppAsync = React.lazy(async () => {
|
|
48
48
|
return (
|
49
49
|
<CreeveyApp
|
50
50
|
api={creeveyApi}
|
51
|
-
initialState={{
|
51
|
+
initialState={{
|
52
|
+
isReport,
|
53
|
+
isRunning: creeveyStatus.isRunning,
|
54
|
+
tests: treeifyTests(creeveyStatus.tests),
|
55
|
+
isUpdateMode: creeveyStatus.isUpdateMode,
|
56
|
+
}}
|
52
57
|
/>
|
53
58
|
);
|
54
59
|
},
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { useEffect, useState } from 'react';
|
2
|
+
import { themes, ThemeVars } from 'storybook/theming';
|
3
|
+
import { isDefined } from '../../types.js';
|
4
|
+
|
5
|
+
const CREEVEY_THEME = 'Creevey_theme';
|
6
|
+
|
7
|
+
function isTheme(theme?: string | null): theme is ThemeVars['base'] {
|
8
|
+
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
9
|
+
}
|
10
|
+
|
11
|
+
function initialTheme(): ThemeVars['base'] {
|
12
|
+
const theme = localStorage.getItem(CREEVEY_THEME);
|
13
|
+
return isTheme(theme) ? theme : 'light';
|
14
|
+
}
|
15
|
+
|
16
|
+
export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
|
17
|
+
const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
localStorage.setItem(CREEVEY_THEME, theme);
|
21
|
+
}, [theme]);
|
22
|
+
|
23
|
+
return [theme, setTheme];
|
24
|
+
}
|
package/src/creevey.ts
CHANGED
@@ -1,54 +1,108 @@
|
|
1
1
|
import cluster from 'cluster';
|
2
|
-
import
|
3
|
-
import creevey from './server/index.js';
|
4
|
-
import { Options } from './types.js';
|
5
|
-
import { emitWorkerMessage } from './server/messages.js';
|
6
|
-
import { isShuttingDown, shutdownWorkers } from './server/utils.js';
|
2
|
+
import * as v from 'valibot';
|
7
3
|
import Logger from 'loglevel';
|
4
|
+
import { cac } from 'cac';
|
5
|
+
import { Options, OptionsSchema, WorkerOptions, WorkerOptionsSchema } from './types.js';
|
6
|
+
import { version } from '../package.json';
|
8
7
|
import { logger, setRootName } from './server/logger.js';
|
8
|
+
import creevey from './server/index.js';
|
9
|
+
import './server/shutdown.js';
|
10
|
+
|
11
|
+
const workerCli = cac('worker');
|
12
|
+
workerCli
|
13
|
+
.command('worker', 'Start worker')
|
14
|
+
.option('--browser <browser>', 'Specify browser to run tests')
|
15
|
+
.option('--grid-url <url>', 'Selenium grid URL')
|
16
|
+
.option('-d, --debug', 'Enable debug mode')
|
17
|
+
.option('-c, --config <config>', 'Path to config file')
|
18
|
+
.option('-p, --port <port>', 'Port for UI server', { default: 3000 })
|
19
|
+
.option('--trace', 'Enable trace mode (more verbose than debug)')
|
20
|
+
.option('--report-dir <dir>', 'Directory for test reports')
|
21
|
+
.option('--screen-dir <dir>', 'Directory for reference images')
|
22
|
+
.option('--storybook-url <url>', 'Storybook server URL')
|
23
|
+
.option('--odiff', 'Use odiff for image comparison');
|
24
|
+
workerCli.parse();
|
9
25
|
|
10
|
-
|
11
|
-
if (isShuttingDown.current) return;
|
26
|
+
const cli = cac('creevey');
|
12
27
|
|
13
|
-
|
28
|
+
cli
|
29
|
+
.command('report [reportDir]', 'Launch web UI to review and approve test results')
|
30
|
+
.option('-c, --config <config>', 'Path to config file')
|
31
|
+
.option('-p, --port <port>', 'Port for UI server', { default: 3000 })
|
32
|
+
.option('--screen-dir <dir>', 'Directory for reference images');
|
14
33
|
|
15
|
-
|
34
|
+
// TODO Add ability to start specific tests/files/stories
|
35
|
+
cli
|
36
|
+
.command('test [options]', 'Run tests')
|
37
|
+
.option('--ui', 'Launch web UI for running tests and reviewing test results')
|
38
|
+
.option('-s, --storybook-start [cmd]', 'Start Storybook automatically')
|
39
|
+
.option('-c, --config <config>', 'Path to config file')
|
40
|
+
.option('-d, --debug', 'Enable debug mode')
|
41
|
+
.option('-p, --port <port>', 'Port for UI server', { default: 3000 })
|
42
|
+
.option('--fail-fast', 'Stop tests after first failure')
|
43
|
+
.option('--report-dir <dir>', 'Directory for test reports')
|
44
|
+
.option('--screen-dir <dir>', 'Directory for reference images')
|
45
|
+
.option('--storybook-url <url>', 'Storybook server URL')
|
46
|
+
.option('--storybook-port <port>', 'Storybook server port')
|
47
|
+
.option('--reporter <reporter>', '[DEPRECATED] Use config file instead')
|
48
|
+
.option('--odiff', 'Use odiff for image comparison')
|
49
|
+
.option('--trace', 'Enable trace mode (more verbose than debug)')
|
50
|
+
.option('--no-docker', 'Disable Docker usage');
|
51
|
+
cli.version(process.env.npm_package_version ?? version);
|
52
|
+
cli.help();
|
53
|
+
cli.parse();
|
16
54
|
|
17
|
-
|
18
|
-
|
19
|
-
|
55
|
+
if (
|
56
|
+
process.argv.includes('--help') ||
|
57
|
+
process.argv.includes('-h') ||
|
58
|
+
process.argv.includes('--version') ||
|
59
|
+
process.argv.includes('-v')
|
60
|
+
) {
|
61
|
+
process.exit(0);
|
20
62
|
}
|
21
63
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
64
|
+
const command = cluster.isWorker ? workerCli.matchedCommandName : cli.matchedCommandName;
|
65
|
+
const args = cluster.isWorker ? workerCli.args : cli.args;
|
66
|
+
let options: Options | WorkerOptions;
|
67
|
+
|
68
|
+
if (!command || (command !== 'report' && command !== 'test' && command !== 'worker')) {
|
69
|
+
console.error('Error: No known command specified\n');
|
70
|
+
cli.outputHelp();
|
71
|
+
process.exit(1);
|
72
|
+
}
|
73
|
+
|
74
|
+
try {
|
75
|
+
options = cluster.isWorker ? v.parse(WorkerOptionsSchema, workerCli.options) : v.parse(OptionsSchema, cli.options);
|
76
|
+
} catch (error: unknown) {
|
77
|
+
if (v.isValiError(error)) {
|
78
|
+
console.error('Options validation failed:');
|
79
|
+
for (const issue of error.issues) {
|
80
|
+
const path = issue.path?.map((p) => p.key).join('.');
|
81
|
+
console.error(` ${path ? `${path}: ` : ''}${issue.message}`);
|
82
|
+
}
|
83
|
+
} else {
|
84
|
+
console.error(error);
|
28
85
|
}
|
29
|
-
|
30
|
-
|
86
|
+
console.log();
|
87
|
+
cli.matchedCommand?.outputHelp();
|
88
|
+
|
89
|
+
process.exit(1);
|
90
|
+
}
|
31
91
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
alias: { port: 'p', config: 'c', debug: 'd', update: 'u' },
|
37
|
-
});
|
92
|
+
if (v.is(OptionsSchema, options) && command == 'report' && args.length > 0) {
|
93
|
+
options.reportDir = args[0];
|
94
|
+
options.ui = true;
|
95
|
+
}
|
38
96
|
|
39
|
-
|
40
|
-
if (
|
97
|
+
// Handle browser name for logging
|
98
|
+
if (v.is(WorkerOptionsSchema, options)) setRootName(options.browser);
|
41
99
|
|
42
|
-
|
43
|
-
if (cluster.isPrimary && argv.reporter) {
|
100
|
+
if (cluster.isPrimary && 'reporter' in options) {
|
44
101
|
logger().warn(`--reporter option has been removed please describe reporter in config file:
|
45
102
|
import { reporters } from 'mocha';
|
46
103
|
|
47
104
|
const config = {
|
48
|
-
reporter: reporters.${
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
50
|
-
argv.reporter
|
51
|
-
},
|
105
|
+
reporter: reporters.${options.reporter},
|
52
106
|
};
|
53
107
|
|
54
108
|
export default config;
|
@@ -56,11 +110,11 @@ if (cluster.isPrimary && argv.reporter) {
|
|
56
110
|
}
|
57
111
|
|
58
112
|
// @ts-expect-error: define log level for storybook
|
59
|
-
global.LOGLEVEL =
|
60
|
-
if (
|
113
|
+
global.LOGLEVEL = options.trace ? 'trace' : options.debug ? 'debug' : 'warn';
|
114
|
+
if (options.trace) {
|
61
115
|
logger().setDefaultLevel(Logger.levels.TRACE);
|
62
116
|
Logger.setDefaultLevel(Logger.levels.TRACE);
|
63
|
-
} else if (
|
117
|
+
} else if (options.debug) {
|
64
118
|
logger().setDefaultLevel(Logger.levels.DEBUG);
|
65
119
|
Logger.setDefaultLevel(Logger.levels.DEBUG);
|
66
120
|
} else {
|
@@ -68,4 +122,4 @@ if (argv.trace) {
|
|
68
122
|
Logger.setDefaultLevel(Logger.levels.INFO);
|
69
123
|
}
|
70
124
|
|
71
|
-
void creevey(
|
125
|
+
void creevey(command, options);
|
@@ -0,0 +1,360 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
import assert from 'assert';
|
3
|
+
import { readFileSync } from 'fs';
|
4
|
+
import type { PixelmatchOptions } from 'pixelmatch';
|
5
|
+
import type { ODiffOptions } from 'odiff-bin';
|
6
|
+
import { test, Page, BrowserContext } from '@playwright/test';
|
7
|
+
import isEqual from 'lodash/isEqual.js';
|
8
|
+
import { CreeveyStoryParams, isObject, StoriesRaw, StorybookEvents, StorybookGlobals } from '../types';
|
9
|
+
import { getOdiffAssert, getPixelmatchAssert, ImageContext } from '../server/compare';
|
10
|
+
import { appendIframePath } from '../server/webdriver';
|
11
|
+
import { waitForStorybookReady } from './helpers';
|
12
|
+
|
13
|
+
export interface TestsConfig {
|
14
|
+
/**
|
15
|
+
* Define pixelmatch diff options
|
16
|
+
* @default { threshold: 0.1, includeAA: false }
|
17
|
+
*/
|
18
|
+
diffOptions: PixelmatchOptions;
|
19
|
+
/**
|
20
|
+
* Define odiff diff options
|
21
|
+
* @default { threshold: 0.1, antialiasing: true }
|
22
|
+
*/
|
23
|
+
odiffOptions: ODiffOptions;
|
24
|
+
/**
|
25
|
+
* Define matcher for visual regression assertion
|
26
|
+
* @default 'pixelmatch'
|
27
|
+
*/
|
28
|
+
comparisonLibrary: 'pixelmatch' | 'odiff';
|
29
|
+
/**
|
30
|
+
* Enables page context reuse across tests for faster execution, though this breaks test isolation.
|
31
|
+
* @default true
|
32
|
+
*/
|
33
|
+
reusePageContext: boolean;
|
34
|
+
/**
|
35
|
+
* Enables trace recording for each test.
|
36
|
+
* @default false
|
37
|
+
*/
|
38
|
+
trace: boolean | { screenshots?: boolean; snapshots?: boolean; sources?: boolean };
|
39
|
+
}
|
40
|
+
|
41
|
+
const cacheDir = process.env.CREEVEY_CACHE_DIR;
|
42
|
+
const defaultConfig: TestsConfig = {
|
43
|
+
diffOptions: { threshold: 0.1, includeAA: false },
|
44
|
+
odiffOptions: { threshold: 0.1, antialiasing: true },
|
45
|
+
comparisonLibrary: 'pixelmatch',
|
46
|
+
reusePageContext: true,
|
47
|
+
trace: false,
|
48
|
+
};
|
49
|
+
|
50
|
+
// TODO: Use this Storybook function for building args for query params
|
51
|
+
// export const buildArgsParam = (initialArgs: Args | undefined, args: Args): string => {
|
52
|
+
|
53
|
+
// TODO: Pass globals to story
|
54
|
+
function appendStoryQueryParams(url: string, storyId: string): string {
|
55
|
+
return `${url}?args=&globals=&id=${storyId}`;
|
56
|
+
}
|
57
|
+
|
58
|
+
function assertWrapper(
|
59
|
+
assert: (actual: Buffer, imageName?: string) => Promise<string | undefined>,
|
60
|
+
): (actual: Buffer, imageName?: string) => Promise<void> {
|
61
|
+
return async function assertImage(actual, imageName) {
|
62
|
+
try {
|
63
|
+
const errorMessage = await assert(actual, imageName);
|
64
|
+
if (errorMessage) {
|
65
|
+
throw new Error(errorMessage);
|
66
|
+
}
|
67
|
+
} catch (error) {
|
68
|
+
if (error instanceof Error) {
|
69
|
+
error.stack = error.stack
|
70
|
+
?.split('\n')
|
71
|
+
.filter((line) => !line.includes('at assertImage'))
|
72
|
+
.join('\n');
|
73
|
+
}
|
74
|
+
throw error;
|
75
|
+
}
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
async function takeScreenshot(
|
80
|
+
page: Page,
|
81
|
+
storyId: string,
|
82
|
+
captureElement?: string | null,
|
83
|
+
ignoreElements?: string | string[] | null,
|
84
|
+
): Promise<Buffer> {
|
85
|
+
const ignore = ignoreElements ? (Array.isArray(ignoreElements) ? ignoreElements : [ignoreElements]) : [];
|
86
|
+
const mask = ignore.map((selector) => page.locator(selector));
|
87
|
+
|
88
|
+
if (captureElement) {
|
89
|
+
// TODO Use page.locator(captureElement) instead of page.$(captureElement)
|
90
|
+
// TODO Test `#storybook-root > *` selector, probably we don't need `> *` and use `#storybook-root >*:first-child` instead
|
91
|
+
const element = await page.$(captureElement);
|
92
|
+
if (!element) throw new Error(`Capture element '${captureElement}' not found for story '${storyId}'`);
|
93
|
+
return element.screenshot({
|
94
|
+
style: ':root { overflow: hidden !important; }',
|
95
|
+
animations: 'disabled',
|
96
|
+
mask,
|
97
|
+
});
|
98
|
+
} else {
|
99
|
+
return page.screenshot({
|
100
|
+
animations: 'disabled',
|
101
|
+
mask,
|
102
|
+
});
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
// TODO: To support parallel tests, we need to define each test suite in separate file
|
107
|
+
// TODO: How to support custom interactions for different tests
|
108
|
+
// Main function to define tests using Playwright's API
|
109
|
+
export function definePlaywrightTests(config?: Partial<TestsConfig>): void {
|
110
|
+
assert(cacheDir, 'Cache directory not found');
|
111
|
+
|
112
|
+
const stories = JSON.parse(readFileSync(path.join(cacheDir, 'stories.json'), 'utf-8')) as StoriesRaw;
|
113
|
+
let globals: StorybookGlobals = {};
|
114
|
+
let reusedContext: BrowserContext;
|
115
|
+
let reusedPage: Page;
|
116
|
+
|
117
|
+
const { diffOptions, odiffOptions, comparisonLibrary, reusePageContext, trace } = {
|
118
|
+
...defaultConfig,
|
119
|
+
...config,
|
120
|
+
};
|
121
|
+
|
122
|
+
async function updateGlobals(page: Page, storybookGlobals: unknown): Promise<void> {
|
123
|
+
if (storybookGlobals && typeof storybookGlobals === 'object' && !isEqual(globals, storybookGlobals)) {
|
124
|
+
globals = storybookGlobals as StorybookGlobals;
|
125
|
+
await page.evaluate((globals) => {
|
126
|
+
window.__STORYBOOK_ADDONS_CHANNEL__.emit(StorybookEvents.UPDATE_GLOBALS, { globals });
|
127
|
+
}, globals);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
test.describe('Creevey Tests', () => {
|
132
|
+
const imagesContext: ImageContext = {
|
133
|
+
attachments: [],
|
134
|
+
testFullPath: [],
|
135
|
+
images: {},
|
136
|
+
};
|
137
|
+
let assertImage: (actual: Buffer, imageName?: string) => Promise<void>;
|
138
|
+
|
139
|
+
test.beforeAll('Setup images context', async ({ browser }, { project }) => {
|
140
|
+
const {
|
141
|
+
snapshotDir,
|
142
|
+
outputDir,
|
143
|
+
use: { viewport },
|
144
|
+
} = project;
|
145
|
+
|
146
|
+
if (reusePageContext) {
|
147
|
+
const storybookUrl = project.use.baseURL;
|
148
|
+
|
149
|
+
assert(storybookUrl, 'Storybook URL not found');
|
150
|
+
|
151
|
+
// TODO Record video
|
152
|
+
reusedContext = await browser.newContext({ viewport, screen: viewport ?? undefined });
|
153
|
+
if (trace) {
|
154
|
+
await reusedContext.tracing.start(
|
155
|
+
typeof trace === 'object' ? trace : { screenshots: true, snapshots: true, sources: true },
|
156
|
+
);
|
157
|
+
}
|
158
|
+
reusedPage = await reusedContext.newPage();
|
159
|
+
await reusedPage.goto(appendIframePath(storybookUrl), { timeout: 60000 });
|
160
|
+
await reusedPage.waitForLoadState('networkidle');
|
161
|
+
await waitForStorybookReady(reusedPage);
|
162
|
+
}
|
163
|
+
if (comparisonLibrary === 'pixelmatch') {
|
164
|
+
const { default: pixelmatch } = await import('pixelmatch');
|
165
|
+
assertImage = assertWrapper(
|
166
|
+
getPixelmatchAssert(pixelmatch, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, diffOptions }),
|
167
|
+
);
|
168
|
+
} else {
|
169
|
+
const { compare } = await import('odiff-bin');
|
170
|
+
assertImage = assertWrapper(
|
171
|
+
getOdiffAssert(compare, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, odiffOptions }),
|
172
|
+
);
|
173
|
+
}
|
174
|
+
});
|
175
|
+
|
176
|
+
test.beforeEach('Switch story', async ({ page }, { annotations, project }) => {
|
177
|
+
const { description: storyId } = annotations.find((annotation) => annotation.type === 'storyId') ?? {};
|
178
|
+
|
179
|
+
assert(storyId, 'Cannot get storyId. It seems like inner test annotation is missing');
|
180
|
+
|
181
|
+
const story = stories[storyId];
|
182
|
+
|
183
|
+
assert(story, `Story '${storyId}' not found in stories cache`);
|
184
|
+
|
185
|
+
const { title, name, parameters } = story;
|
186
|
+
const { waitForReady: shouldWaitForReady } = (parameters.creevey ?? {}) as CreeveyStoryParams;
|
187
|
+
|
188
|
+
const storybookGlobals: unknown = project.metadata.storybookGlobals;
|
189
|
+
|
190
|
+
imagesContext.attachments = [];
|
191
|
+
imagesContext.testFullPath = [...title.split('/').map((x) => x.trim()), name, project.name];
|
192
|
+
imagesContext.images = {};
|
193
|
+
|
194
|
+
if (!reusePageContext) {
|
195
|
+
const storybookUrl = project.use.baseURL;
|
196
|
+
|
197
|
+
assert(storybookUrl, 'Storybook URL not found');
|
198
|
+
|
199
|
+
await page.goto(appendStoryQueryParams(appendIframePath(storybookUrl), storyId), { timeout: 60000 });
|
200
|
+
await page.waitForLoadState('networkidle');
|
201
|
+
await waitForStorybookReady(page);
|
202
|
+
// TODO: Pass globals to story
|
203
|
+
await updateGlobals(page, storybookGlobals);
|
204
|
+
|
205
|
+
return;
|
206
|
+
}
|
207
|
+
|
208
|
+
// 1. Update Storybook Globals
|
209
|
+
await updateGlobals(reusedPage, storybookGlobals);
|
210
|
+
|
211
|
+
// 2. Reset Mouse Position
|
212
|
+
await reusedPage.mouse.move(0, 0);
|
213
|
+
|
214
|
+
// 3. Select Story
|
215
|
+
const errorMessage = await reusedPage.evaluate<
|
216
|
+
string | null,
|
217
|
+
{ storyId: string; StorybookEvents: typeof StorybookEvents; shouldWaitForReady?: boolean }
|
218
|
+
>(
|
219
|
+
async ({ storyId, StorybookEvents, shouldWaitForReady }) => {
|
220
|
+
// TODO: DRY with withCreevey.ts
|
221
|
+
// NOTE: Copy-pasted from withCreevey.ts
|
222
|
+
const channel = window.__STORYBOOK_ADDONS_CHANNEL__;
|
223
|
+
|
224
|
+
async function sequence(fns: (() => Promise<unknown>)[]): Promise<void> {
|
225
|
+
for (const fn of fns) {
|
226
|
+
await fn();
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
const waitForReady = shouldWaitForReady
|
231
|
+
? new Promise<void>((resolve) => (window.__CREEVEY_SET_READY_FOR_CAPTURE__ = resolve))
|
232
|
+
: Promise.resolve();
|
233
|
+
|
234
|
+
let rejectCallback: (reason?: unknown) => void;
|
235
|
+
const renderErrorPromise = new Promise<void>((_resolve, reject) => (rejectCallback = reject));
|
236
|
+
|
237
|
+
function errorHandler({ title, description }: { title: string; description: string }): void {
|
238
|
+
rejectCallback({
|
239
|
+
message: title,
|
240
|
+
stack: description,
|
241
|
+
});
|
242
|
+
}
|
243
|
+
function exceptionHandler(exception: Error): void {
|
244
|
+
rejectCallback(exception);
|
245
|
+
}
|
246
|
+
function removeErrorHandlers(): void {
|
247
|
+
channel.off(StorybookEvents.STORY_ERRORED, errorHandler);
|
248
|
+
channel.off(StorybookEvents.STORY_THREW_EXCEPTION, errorHandler);
|
249
|
+
}
|
250
|
+
|
251
|
+
channel.once(StorybookEvents.STORY_ERRORED, errorHandler);
|
252
|
+
channel.once(StorybookEvents.STORY_THREW_EXCEPTION, exceptionHandler);
|
253
|
+
|
254
|
+
let resolveCallback: () => void;
|
255
|
+
const storyRenderedPromise = new Promise<void>((resolve) => (resolveCallback = resolve));
|
256
|
+
function renderHandler(): void {
|
257
|
+
resolveCallback();
|
258
|
+
}
|
259
|
+
function removeRenderHandlers(): void {
|
260
|
+
channel.off(StorybookEvents.STORY_RENDERED, renderHandler);
|
261
|
+
}
|
262
|
+
|
263
|
+
channel.once(StorybookEvents.STORY_RENDERED, renderHandler);
|
264
|
+
|
265
|
+
setTimeout(() => {
|
266
|
+
channel.emit(StorybookEvents.SET_CURRENT_STORY, { storyId });
|
267
|
+
}, 0);
|
268
|
+
|
269
|
+
try {
|
270
|
+
await Promise.race([
|
271
|
+
renderErrorPromise,
|
272
|
+
sequence([() => storyRenderedPromise, () => document.fonts.ready, () => waitForReady]),
|
273
|
+
]);
|
274
|
+
} catch (reason) {
|
275
|
+
// NOTE Event `STORY_THREW_EXCEPTION` triggered only in react and vue frameworks and return Error instance
|
276
|
+
// NOTE Event `STORY_ERRORED` return error-like object without `name` field
|
277
|
+
const errorMessage =
|
278
|
+
reason instanceof Error
|
279
|
+
? (reason.stack ?? reason.message)
|
280
|
+
: isObject(reason)
|
281
|
+
? `${reason.message as string}\n ${reason.stack as string}`
|
282
|
+
: (reason as string);
|
283
|
+
return errorMessage;
|
284
|
+
} finally {
|
285
|
+
removeErrorHandlers();
|
286
|
+
removeRenderHandlers();
|
287
|
+
}
|
288
|
+
|
289
|
+
return null;
|
290
|
+
},
|
291
|
+
{ storyId: story.id, StorybookEvents, shouldWaitForReady },
|
292
|
+
);
|
293
|
+
|
294
|
+
if (errorMessage) {
|
295
|
+
throw new Error(`Failed to select story '${story.id}': ${errorMessage}`);
|
296
|
+
}
|
297
|
+
});
|
298
|
+
|
299
|
+
test.afterEach('Save screenshot', () => {
|
300
|
+
const { name: projectName } = test.info().project;
|
301
|
+
|
302
|
+
// TODO: Use another way to handle attachments
|
303
|
+
|
304
|
+
// NOTE: Don't need to copy files for assertImage, because it's done internally
|
305
|
+
const { actual, diff, expect } = imagesContext.images[projectName] ?? {};
|
306
|
+
for (const image of imagesContext.attachments) {
|
307
|
+
switch (true) {
|
308
|
+
case image.includes('actual') && !!actual: {
|
309
|
+
test.info().attachments.push({ name: actual, path: image, contentType: 'image/png' });
|
310
|
+
// await test.info().attach(actual, { path: image });
|
311
|
+
break;
|
312
|
+
}
|
313
|
+
case image.includes('expect') && !!expect: {
|
314
|
+
test.info().attachments.push({ name: expect, path: image, contentType: 'image/png' });
|
315
|
+
// await test.info().attach(expect, { path: image });
|
316
|
+
break;
|
317
|
+
}
|
318
|
+
case image.includes('diff') && !!diff: {
|
319
|
+
test.info().attachments.push({ name: diff, path: image, contentType: 'image/png' });
|
320
|
+
// await test.info().attach(diff, { path: image });
|
321
|
+
break;
|
322
|
+
}
|
323
|
+
}
|
324
|
+
}
|
325
|
+
});
|
326
|
+
|
327
|
+
if (trace && reusePageContext) {
|
328
|
+
test.afterAll('Save trace', async (_, { project }) => {
|
329
|
+
const { outputDir, name: projectName } = project;
|
330
|
+
await reusedContext.tracing.stop({
|
331
|
+
path: `${outputDir}/traces/${projectName}-${process.pid}.zip`,
|
332
|
+
});
|
333
|
+
});
|
334
|
+
}
|
335
|
+
|
336
|
+
for (const story of Object.values(stories)) {
|
337
|
+
const { name, title, parameters } = story;
|
338
|
+
const { captureElement, ignoreElements } = (parameters.creevey ?? {}) as CreeveyStoryParams;
|
339
|
+
|
340
|
+
test.describe(title, () => {
|
341
|
+
// TODO: Support creevey.skip
|
342
|
+
test(name, { annotation: [{ type: 'storyId', description: story.id }] }, async ({ page }) => {
|
343
|
+
// 4. Take Screenshot
|
344
|
+
const screenshot = await takeScreenshot(
|
345
|
+
reusePageContext ? reusedPage : page,
|
346
|
+
story.id,
|
347
|
+
captureElement,
|
348
|
+
ignoreElements,
|
349
|
+
);
|
350
|
+
// TODO: Support this
|
351
|
+
// NOTE: Bear in mind that page.locator('#root > *') is not working
|
352
|
+
// await expect(page.locator(captureElement)).toHaveScreenshot(name);
|
353
|
+
|
354
|
+
// 5. Assert Image
|
355
|
+
await assertImage(screenshot);
|
356
|
+
});
|
357
|
+
});
|
358
|
+
}
|
359
|
+
});
|
360
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { StorybookEvents } from '../types';
|
2
|
+
import { Page } from '@playwright/test';
|
3
|
+
|
4
|
+
export async function waitForStorybookReady(page: Page, timeout = 60000): Promise<void> {
|
5
|
+
const isStorybookInitialized = await page.evaluate(
|
6
|
+
({ timeout, event }: { timeout: number; event: string }) => {
|
7
|
+
return new Promise<boolean>((resolve, reject) => {
|
8
|
+
let attempts = 0;
|
9
|
+
const maxAttempts = timeout / 100;
|
10
|
+
function check() {
|
11
|
+
if (
|
12
|
+
typeof window.__STORYBOOK_ADDONS_CHANNEL__ !== 'undefined' &&
|
13
|
+
window.__STORYBOOK_ADDONS_CHANNEL__.last(event) !== undefined
|
14
|
+
) {
|
15
|
+
resolve(true);
|
16
|
+
} else if (attempts++ < maxAttempts) {
|
17
|
+
setTimeout(check, 100);
|
18
|
+
} else {
|
19
|
+
reject(new Error('Storybook initialization timed out. Required Storybook functions not found on window.'));
|
20
|
+
}
|
21
|
+
}
|
22
|
+
check();
|
23
|
+
});
|
24
|
+
},
|
25
|
+
{ timeout, event: StorybookEvents.SET_GLOBALS },
|
26
|
+
);
|
27
|
+
|
28
|
+
if (!isStorybookInitialized) {
|
29
|
+
throw new Error('Failed to confirm Storybook API is ready after extended wait.');
|
30
|
+
}
|
31
|
+
}
|