creevey 0.10.0-beta.8 → 0.10.0-rc.0
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 +4 -4
- 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 +14 -21
- 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 +31 -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 +2 -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 +45 -15
- 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 +44 -15
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +6 -0
- package/dist/client/web/CreeveyContext.js +21 -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 +20 -10
- 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 +67 -13
- 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 -13
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -13
- 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.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-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 +25 -0
- package/dist/playwright/generator.js +243 -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 +165 -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 +144 -84
- 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 +4 -4
- package/dist/server/selenium/internal.js +56 -40
- 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 +114 -15
- 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 +41 -64
- 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 +37 -0
- package/docs/migration-0.9-to-0.10.md +144 -0
- package/docs/playwright-reporter.md +357 -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 +4 -4
- 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 +16 -19
- 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 +20 -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 +2 -1
- package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
- package/src/client/shared/components/ResultsPage.tsx +33 -10
- package/src/client/shared/creeveyClientApi.ts +19 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +30 -9
- package/src/client/web/CreeveyContext.tsx +11 -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 +4 -4
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +48 -15
- package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +12 -12
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +10 -10
- 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/KeyboardEventsContext.tsx +61 -73
- 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 +322 -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 +166 -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 +176 -103
- 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 +71 -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 +70 -53
- 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 +124 -16
- 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 +49 -79
- 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-DB8lHlJw.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/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,322 @@
|
|
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 } 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
|
+
|
36
|
+
const cacheDir = process.env.CREEVEY_CACHE_DIR;
|
37
|
+
const defaultConfig: TestsConfig = {
|
38
|
+
diffOptions: { threshold: 0.1, includeAA: false },
|
39
|
+
odiffOptions: { threshold: 0.1, antialiasing: true },
|
40
|
+
comparisonLibrary: 'pixelmatch',
|
41
|
+
reusePageContext: true,
|
42
|
+
};
|
43
|
+
|
44
|
+
// TODO: Use this Storybook function for building args for query params
|
45
|
+
// export const buildArgsParam = (initialArgs: Args | undefined, args: Args): string => {
|
46
|
+
|
47
|
+
// TODO: Pass globals to story
|
48
|
+
function appendStoryQueryParams(url: string, storyId: string): string {
|
49
|
+
return `${url}?args=&globals=&id=${storyId}`;
|
50
|
+
}
|
51
|
+
|
52
|
+
function assertWrapper(
|
53
|
+
assert: (actual: Buffer, imageName?: string) => Promise<string | undefined>,
|
54
|
+
): (actual: Buffer, imageName?: string) => Promise<void> {
|
55
|
+
return async function assertImage(actual, imageName) {
|
56
|
+
try {
|
57
|
+
const errorMessage = await assert(actual, imageName);
|
58
|
+
if (errorMessage) {
|
59
|
+
throw new Error(errorMessage);
|
60
|
+
}
|
61
|
+
} catch (error) {
|
62
|
+
if (error instanceof Error) {
|
63
|
+
error.stack = error.stack
|
64
|
+
?.split('\n')
|
65
|
+
.filter((line) => !line.includes('at assertImage'))
|
66
|
+
.join('\n');
|
67
|
+
}
|
68
|
+
throw error;
|
69
|
+
}
|
70
|
+
};
|
71
|
+
}
|
72
|
+
|
73
|
+
async function takeScreenshot(
|
74
|
+
page: Page,
|
75
|
+
storyId: string,
|
76
|
+
captureElement?: string | null,
|
77
|
+
ignoreElements?: string | string[] | null,
|
78
|
+
): Promise<Buffer> {
|
79
|
+
const ignore = ignoreElements ? (Array.isArray(ignoreElements) ? ignoreElements : [ignoreElements]) : [];
|
80
|
+
const mask = ignore.map((selector) => page.locator(selector));
|
81
|
+
|
82
|
+
if (captureElement) {
|
83
|
+
const element = await page.$(captureElement);
|
84
|
+
if (!element) throw new Error(`Capture element '${captureElement}' not found for story '${storyId}'`);
|
85
|
+
return element.screenshot({
|
86
|
+
style: ':root { overflow: hidden !important; }',
|
87
|
+
animations: 'disabled',
|
88
|
+
mask,
|
89
|
+
});
|
90
|
+
} else {
|
91
|
+
return page.screenshot({
|
92
|
+
animations: 'disabled',
|
93
|
+
mask,
|
94
|
+
});
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
// TODO: To support parallel tests, we need to define each test suite in separate file
|
99
|
+
// TODO: How to support custom interactions for different tests
|
100
|
+
// Main function to define tests using Playwright's API
|
101
|
+
export function definePlaywrightTests(config?: Partial<TestsConfig>): void {
|
102
|
+
assert(cacheDir, 'Cache directory not found');
|
103
|
+
|
104
|
+
const stories = JSON.parse(readFileSync(path.join(cacheDir, 'stories.json'), 'utf-8')) as StoriesRaw;
|
105
|
+
let globals: StorybookGlobals = {};
|
106
|
+
let reusedPage: Page;
|
107
|
+
|
108
|
+
const { diffOptions, odiffOptions, comparisonLibrary, reusePageContext } = {
|
109
|
+
...defaultConfig,
|
110
|
+
...config,
|
111
|
+
};
|
112
|
+
|
113
|
+
async function updateGlobals(page: Page, storybookGlobals: unknown): Promise<void> {
|
114
|
+
if (storybookGlobals && typeof storybookGlobals === 'object' && !isEqual(globals, storybookGlobals)) {
|
115
|
+
globals = storybookGlobals as StorybookGlobals;
|
116
|
+
await page.evaluate((globals) => {
|
117
|
+
window.__STORYBOOK_ADDONS_CHANNEL__.emit(StorybookEvents.UPDATE_GLOBALS, { globals });
|
118
|
+
}, globals);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
test.describe('Creevey Tests', () => {
|
123
|
+
const imagesContext: ImageContext = {
|
124
|
+
attachments: [],
|
125
|
+
testFullPath: [],
|
126
|
+
images: {},
|
127
|
+
};
|
128
|
+
let assertImage: (actual: Buffer, imageName?: string) => Promise<void>;
|
129
|
+
|
130
|
+
test.beforeAll('Setup images context', async ({ browser }, { project }) => {
|
131
|
+
const { snapshotDir, outputDir } = project;
|
132
|
+
if (reusePageContext) {
|
133
|
+
const storybookUrl = project.use.baseURL;
|
134
|
+
|
135
|
+
assert(storybookUrl, 'Storybook URL not found');
|
136
|
+
|
137
|
+
reusedPage = await browser.newPage();
|
138
|
+
await reusedPage.goto(appendIframePath(storybookUrl), { waitUntil: 'networkidle', timeout: 60000 });
|
139
|
+
await waitForStorybookReady(reusedPage);
|
140
|
+
}
|
141
|
+
if (comparisonLibrary === 'pixelmatch') {
|
142
|
+
const { default: pixelmatch } = await import('pixelmatch');
|
143
|
+
assertImage = assertWrapper(
|
144
|
+
getPixelmatchAssert(pixelmatch, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, diffOptions }),
|
145
|
+
);
|
146
|
+
} else {
|
147
|
+
const { compare } = await import('odiff-bin');
|
148
|
+
assertImage = assertWrapper(
|
149
|
+
getOdiffAssert(compare, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, odiffOptions }),
|
150
|
+
);
|
151
|
+
}
|
152
|
+
});
|
153
|
+
|
154
|
+
test.beforeEach('Switch story', async ({ page }, { annotations, project }) => {
|
155
|
+
const { description: storyId } = annotations.find((annotation) => annotation.type === 'storyId') ?? {};
|
156
|
+
|
157
|
+
assert(storyId, 'Cannot get storyId. It seems like inner test annotation is missing');
|
158
|
+
|
159
|
+
const story = stories[storyId];
|
160
|
+
|
161
|
+
assert(story, `Story '${storyId}' not found in stories cache`);
|
162
|
+
|
163
|
+
const { title, name, parameters } = story;
|
164
|
+
const { waitForReady: shouldWaitForReady } = (parameters.creevey ?? {}) as CreeveyStoryParams;
|
165
|
+
|
166
|
+
const storybookGlobals: unknown = project.metadata.storybookGlobals;
|
167
|
+
|
168
|
+
imagesContext.attachments = [];
|
169
|
+
imagesContext.testFullPath = [...title.split('/').map((x) => x.trim()), name, project.name];
|
170
|
+
imagesContext.images = {};
|
171
|
+
|
172
|
+
if (!reusePageContext) {
|
173
|
+
const storybookUrl = project.use.baseURL;
|
174
|
+
|
175
|
+
assert(storybookUrl, 'Storybook URL not found');
|
176
|
+
|
177
|
+
await page.goto(appendStoryQueryParams(appendIframePath(storybookUrl), storyId), {
|
178
|
+
waitUntil: 'networkidle',
|
179
|
+
timeout: 60000,
|
180
|
+
});
|
181
|
+
await waitForStorybookReady(page);
|
182
|
+
// TODO: Pass globals to story
|
183
|
+
await updateGlobals(page, storybookGlobals);
|
184
|
+
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
// 1. Update Storybook Globals
|
189
|
+
await updateGlobals(reusedPage, storybookGlobals);
|
190
|
+
|
191
|
+
// 2. Reset Mouse Position
|
192
|
+
await reusedPage.mouse.move(0, 0);
|
193
|
+
|
194
|
+
// 3. Select Story
|
195
|
+
const errorMessage = await reusedPage.evaluate<
|
196
|
+
string | null,
|
197
|
+
{ storyId: string; StorybookEvents: typeof StorybookEvents; shouldWaitForReady?: boolean }
|
198
|
+
>(
|
199
|
+
async ({ storyId, StorybookEvents, shouldWaitForReady }) => {
|
200
|
+
// TODO: DRY with withCreevey.ts
|
201
|
+
// NOTE: Copy-pasted from withCreevey.ts
|
202
|
+
const channel = window.__STORYBOOK_ADDONS_CHANNEL__;
|
203
|
+
|
204
|
+
const waitForReady = shouldWaitForReady
|
205
|
+
? new Promise<void>((resolve) => (window.__CREEVEY_SET_READY_FOR_CAPTURE__ = resolve))
|
206
|
+
: Promise.resolve();
|
207
|
+
|
208
|
+
let rejectCallback: (reason?: unknown) => void;
|
209
|
+
const renderErrorPromise = new Promise<void>((_resolve, reject) => (rejectCallback = reject));
|
210
|
+
|
211
|
+
function errorHandler({ title, description }: { title: string; description: string }): void {
|
212
|
+
rejectCallback({
|
213
|
+
message: title,
|
214
|
+
stack: description,
|
215
|
+
});
|
216
|
+
}
|
217
|
+
function exceptionHandler(exception: Error): void {
|
218
|
+
rejectCallback(exception);
|
219
|
+
}
|
220
|
+
function removeErrorHandlers(): void {
|
221
|
+
channel.off(StorybookEvents.STORY_ERRORED, errorHandler);
|
222
|
+
channel.off(StorybookEvents.STORY_THREW_EXCEPTION, errorHandler);
|
223
|
+
}
|
224
|
+
|
225
|
+
channel.once(StorybookEvents.STORY_ERRORED, errorHandler);
|
226
|
+
channel.once(StorybookEvents.STORY_THREW_EXCEPTION, exceptionHandler);
|
227
|
+
|
228
|
+
let resolveCallback: () => void;
|
229
|
+
const storyRenderedPromise = new Promise<void>((resolve) => (resolveCallback = resolve));
|
230
|
+
function renderHandler(): void {
|
231
|
+
resolveCallback();
|
232
|
+
}
|
233
|
+
function removeRenderHandlers(): void {
|
234
|
+
channel.off(StorybookEvents.STORY_RENDERED, renderHandler);
|
235
|
+
}
|
236
|
+
|
237
|
+
channel.once(StorybookEvents.STORY_RENDERED, renderHandler);
|
238
|
+
|
239
|
+
setTimeout(() => {
|
240
|
+
channel.emit(StorybookEvents.SET_CURRENT_STORY, { storyId });
|
241
|
+
}, 0);
|
242
|
+
|
243
|
+
try {
|
244
|
+
await Promise.race([renderErrorPromise, Promise.all([storyRenderedPromise, waitForReady])]);
|
245
|
+
} catch (reason) {
|
246
|
+
// NOTE Event `STORY_THREW_EXCEPTION` triggered only in react and vue frameworks and return Error instance
|
247
|
+
// NOTE Event `STORY_ERRORED` return error-like object without `name` field
|
248
|
+
const errorMessage =
|
249
|
+
reason instanceof Error
|
250
|
+
? (reason.stack ?? reason.message)
|
251
|
+
: isObject(reason)
|
252
|
+
? `${reason.message as string}\n ${reason.stack as string}`
|
253
|
+
: (reason as string);
|
254
|
+
return errorMessage;
|
255
|
+
} finally {
|
256
|
+
removeErrorHandlers();
|
257
|
+
removeRenderHandlers();
|
258
|
+
}
|
259
|
+
|
260
|
+
return null;
|
261
|
+
},
|
262
|
+
{ storyId: story.id, StorybookEvents, shouldWaitForReady },
|
263
|
+
);
|
264
|
+
|
265
|
+
if (errorMessage) {
|
266
|
+
throw new Error(`Failed to select story '${story.id}': ${errorMessage}`);
|
267
|
+
}
|
268
|
+
});
|
269
|
+
|
270
|
+
test.afterEach('Save screenshot', () => {
|
271
|
+
const { name: projectName } = test.info().project;
|
272
|
+
|
273
|
+
// TODO: Use another way to handle attachments
|
274
|
+
|
275
|
+
// NOTE: Don't need to copy files for assertImage, because it's done internally
|
276
|
+
const { actual, diff, expect } = imagesContext.images[projectName] ?? {};
|
277
|
+
for (const image of imagesContext.attachments) {
|
278
|
+
switch (true) {
|
279
|
+
case image.includes('actual') && !!actual: {
|
280
|
+
test.info().attachments.push({ name: actual, path: image, contentType: 'image/png' });
|
281
|
+
// await test.info().attach(actual, { path: image });
|
282
|
+
break;
|
283
|
+
}
|
284
|
+
case image.includes('expect') && !!expect: {
|
285
|
+
test.info().attachments.push({ name: expect, path: image, contentType: 'image/png' });
|
286
|
+
// await test.info().attach(expect, { path: image });
|
287
|
+
break;
|
288
|
+
}
|
289
|
+
case image.includes('diff') && !!diff: {
|
290
|
+
test.info().attachments.push({ name: diff, path: image, contentType: 'image/png' });
|
291
|
+
// await test.info().attach(diff, { path: image });
|
292
|
+
break;
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
});
|
297
|
+
|
298
|
+
for (const story of Object.values(stories)) {
|
299
|
+
const { name, title, parameters } = story;
|
300
|
+
const { captureElement, ignoreElements } = (parameters.creevey ?? {}) as CreeveyStoryParams;
|
301
|
+
|
302
|
+
test.describe(title, () => {
|
303
|
+
// TODO: Support creevey.skip
|
304
|
+
test(name, { annotation: [{ type: 'storyId', description: story.id }] }, async ({ page }) => {
|
305
|
+
// 4. Take Screenshot
|
306
|
+
const screenshot = await takeScreenshot(
|
307
|
+
reusePageContext ? reusedPage : page,
|
308
|
+
story.id,
|
309
|
+
captureElement,
|
310
|
+
ignoreElements,
|
311
|
+
);
|
312
|
+
// TODO: Support this
|
313
|
+
// NOTE: Bear in mind that page.locator('#root > *') is not working
|
314
|
+
// await expect(page.locator(captureElement)).toHaveScreenshot(name);
|
315
|
+
|
316
|
+
// 5. Assert Image
|
317
|
+
await assertImage(screenshot);
|
318
|
+
});
|
319
|
+
});
|
320
|
+
}
|
321
|
+
});
|
322
|
+
}
|
@@ -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
|
+
}
|