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
@@ -0,0 +1,260 @@
|
|
1
|
+
import path from 'path';
|
2
|
+
import { Stats } from 'fs';
|
3
|
+
import assert from 'assert';
|
4
|
+
import { PNG } from 'pngjs';
|
5
|
+
import type { ODiffOptions } from 'odiff-bin';
|
6
|
+
import type { PixelmatchOptions } from 'pixelmatch';
|
7
|
+
import { mkdir, readdir, readFile, stat, writeFile } from 'fs/promises';
|
8
|
+
import { Images } from '../types';
|
9
|
+
|
10
|
+
export interface ImageContext {
|
11
|
+
attachments: string[];
|
12
|
+
testFullPath: string[];
|
13
|
+
images: Partial<Record<string, Images>>;
|
14
|
+
}
|
15
|
+
|
16
|
+
interface ImagePaths {
|
17
|
+
imageName: string;
|
18
|
+
actualImageName: string;
|
19
|
+
expectImageName: string;
|
20
|
+
diffImageName: string;
|
21
|
+
expectImageDir: string;
|
22
|
+
reportImageDir: string;
|
23
|
+
}
|
24
|
+
|
25
|
+
async function getStat(filePath: string): Promise<Stats | null> {
|
26
|
+
try {
|
27
|
+
return await stat(filePath);
|
28
|
+
} catch (error) {
|
29
|
+
if (typeof error == 'object' && error && (error as { code?: unknown }).code === 'ENOENT') {
|
30
|
+
return null;
|
31
|
+
}
|
32
|
+
throw error;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
async function getLastImageNumber(imageDir: string, imageName: string): Promise<number> {
|
37
|
+
const actualImagesRegexp = new RegExp(`${imageName}-actual-(\\d+)\\.png`);
|
38
|
+
|
39
|
+
try {
|
40
|
+
return (
|
41
|
+
(await readdir(imageDir))
|
42
|
+
.map((filename) => filename.replace(actualImagesRegexp, '$1'))
|
43
|
+
.map(Number)
|
44
|
+
.filter((x) => !isNaN(x))
|
45
|
+
.sort((a, b) => b - a)[0] ?? 0
|
46
|
+
);
|
47
|
+
} catch (_error) {
|
48
|
+
return 0;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
async function readExpected(expectImageDir: string, imageName: string): Promise<Buffer> {
|
53
|
+
const expected = await readFile(path.join(expectImageDir, `${imageName}.png`));
|
54
|
+
|
55
|
+
return expected;
|
56
|
+
}
|
57
|
+
|
58
|
+
async function saveImages(imageDir: string, images: { name: string; data: Buffer }[]): Promise<string[]> {
|
59
|
+
const files: string[] = [];
|
60
|
+
await mkdir(imageDir, { recursive: true });
|
61
|
+
for (const { name, data } of images) {
|
62
|
+
const filePath = path.join(imageDir, name);
|
63
|
+
await writeFile(filePath, data);
|
64
|
+
files.push(filePath);
|
65
|
+
}
|
66
|
+
return files;
|
67
|
+
}
|
68
|
+
|
69
|
+
async function getImagePaths(
|
70
|
+
config: { screenDir: string; reportDir: string },
|
71
|
+
testFullPath: string[],
|
72
|
+
assertImageName?: string,
|
73
|
+
): Promise<ImagePaths> {
|
74
|
+
const testPath = [...testFullPath];
|
75
|
+
const imageName = assertImageName ?? testPath.pop();
|
76
|
+
|
77
|
+
assert(typeof imageName === 'string', `Can't get image name from empty test scope`);
|
78
|
+
|
79
|
+
const expectImageDir = path.join(config.screenDir, ...testPath);
|
80
|
+
const reportImageDir = path.join(config.reportDir, ...testPath);
|
81
|
+
const imageNumber = (await getLastImageNumber(reportImageDir, imageName)) + 1;
|
82
|
+
const actualImageName = `${imageName}-actual-${imageNumber}.png`;
|
83
|
+
const expectImageName = `${imageName}-expect-${imageNumber}.png`;
|
84
|
+
const diffImageName = `${imageName}-diff-${imageNumber}.png`;
|
85
|
+
|
86
|
+
return { imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir };
|
87
|
+
}
|
88
|
+
|
89
|
+
async function getExpected(
|
90
|
+
ctx: ImageContext,
|
91
|
+
{ imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir }: ImagePaths,
|
92
|
+
): Promise<{
|
93
|
+
expected: Buffer | null;
|
94
|
+
onCompare: (actual: Buffer, expect?: Buffer, diff?: Buffer) => Promise<void>;
|
95
|
+
}> {
|
96
|
+
const onCompare = async (actual: Buffer, expect?: Buffer, diff?: Buffer): Promise<void> => {
|
97
|
+
const imagesMeta: { name: string; data: Buffer }[] = [];
|
98
|
+
const image = (ctx.images[imageName] = ctx.images[imageName] ?? { actual: actualImageName });
|
99
|
+
|
100
|
+
imagesMeta.push({ name: image.actual, data: actual });
|
101
|
+
|
102
|
+
if (diff && expect) {
|
103
|
+
image.expect = expectImageName;
|
104
|
+
image.diff = diffImageName;
|
105
|
+
imagesMeta.push({ name: image.expect, data: expect });
|
106
|
+
imagesMeta.push({ name: image.diff, data: diff });
|
107
|
+
}
|
108
|
+
ctx.attachments = await saveImages(reportImageDir, imagesMeta);
|
109
|
+
};
|
110
|
+
|
111
|
+
const expectImageStat = await getStat(path.join(expectImageDir, `${imageName}.png`));
|
112
|
+
if (!expectImageStat) return { expected: null, onCompare };
|
113
|
+
|
114
|
+
const expected = await readExpected(expectImageDir, imageName);
|
115
|
+
|
116
|
+
return { expected, onCompare };
|
117
|
+
}
|
118
|
+
|
119
|
+
async function getOdiffExpected(
|
120
|
+
ctx: ImageContext,
|
121
|
+
actual: Buffer,
|
122
|
+
{ imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir }: ImagePaths,
|
123
|
+
): Promise<{ actual: string; expect: string; diff: string }> {
|
124
|
+
const expected = await readExpected(expectImageDir, imageName);
|
125
|
+
|
126
|
+
const image = (ctx.images[imageName] = ctx.images[imageName] ?? { actual: actualImageName });
|
127
|
+
image.expect = expectImageName;
|
128
|
+
image.diff = diffImageName;
|
129
|
+
|
130
|
+
const imagesMeta = [
|
131
|
+
{ name: image.actual, data: actual },
|
132
|
+
{ name: expectImageName, data: expected },
|
133
|
+
];
|
134
|
+
|
135
|
+
ctx.attachments = await saveImages(reportImageDir, imagesMeta);
|
136
|
+
|
137
|
+
return {
|
138
|
+
actual: path.join(reportImageDir, actualImageName),
|
139
|
+
expect: path.join(reportImageDir, expectImageName),
|
140
|
+
diff: path.join(reportImageDir, diffImageName),
|
141
|
+
};
|
142
|
+
}
|
143
|
+
|
144
|
+
function normalizeImageSize(image: PNG, width: number, height: number): Buffer {
|
145
|
+
const normalizedImage = Buffer.alloc(4 * width * height);
|
146
|
+
|
147
|
+
for (let y = 0; y < height; y++) {
|
148
|
+
for (let x = 0; x < width; x++) {
|
149
|
+
const i = (y * width + x) * 4;
|
150
|
+
if (x < image.width && y < image.height) {
|
151
|
+
const j = (y * image.width + x) * 4;
|
152
|
+
normalizedImage[i + 0] = image.data[j + 0];
|
153
|
+
normalizedImage[i + 1] = image.data[j + 1];
|
154
|
+
normalizedImage[i + 2] = image.data[j + 2];
|
155
|
+
normalizedImage[i + 3] = image.data[j + 3];
|
156
|
+
} else {
|
157
|
+
normalizedImage[i + 0] = 0;
|
158
|
+
normalizedImage[i + 1] = 0;
|
159
|
+
normalizedImage[i + 2] = 0;
|
160
|
+
normalizedImage[i + 3] = 0;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
return normalizedImage;
|
165
|
+
}
|
166
|
+
|
167
|
+
function hasDiffPixels(diff: Buffer): boolean {
|
168
|
+
for (let i = 0; i < diff.length; i += 4) {
|
169
|
+
if (diff[i + 0] == 255 && diff[i + 1] == 0 && diff[i + 2] == 0 && diff[i + 3] == 255) return true;
|
170
|
+
}
|
171
|
+
return false;
|
172
|
+
}
|
173
|
+
|
174
|
+
function compareImages(
|
175
|
+
expect: Buffer,
|
176
|
+
actual: Buffer,
|
177
|
+
pixelmatch: typeof import('pixelmatch'),
|
178
|
+
diffOptions: PixelmatchOptions,
|
179
|
+
): { isEqual: boolean; diff: Buffer } {
|
180
|
+
const expectImage = PNG.sync.read(expect);
|
181
|
+
const actualImage = PNG.sync.read(actual);
|
182
|
+
|
183
|
+
const width = Math.max(actualImage.width, expectImage.width);
|
184
|
+
const height = Math.max(actualImage.height, expectImage.height);
|
185
|
+
|
186
|
+
const diffImage = new PNG({ width, height });
|
187
|
+
|
188
|
+
let actualImageData = actualImage.data;
|
189
|
+
if (actualImage.width < width || actualImage.height < height) {
|
190
|
+
actualImageData = normalizeImageSize(actualImage, width, height);
|
191
|
+
}
|
192
|
+
|
193
|
+
let expectImageData = expectImage.data;
|
194
|
+
if (expectImage.width < width || expectImage.height < height) {
|
195
|
+
expectImageData = normalizeImageSize(expectImage, width, height);
|
196
|
+
}
|
197
|
+
|
198
|
+
pixelmatch(expectImageData, actualImageData, diffImage.data, width, height, diffOptions);
|
199
|
+
|
200
|
+
return {
|
201
|
+
isEqual: !hasDiffPixels(diffImage.data),
|
202
|
+
diff: PNG.sync.write(diffImage),
|
203
|
+
};
|
204
|
+
}
|
205
|
+
|
206
|
+
export function getPixelmatchAssert(
|
207
|
+
pixelmatch: typeof import('pixelmatch'),
|
208
|
+
ctx: ImageContext,
|
209
|
+
config: { screenDir: string; reportDir: string; diffOptions: PixelmatchOptions },
|
210
|
+
) {
|
211
|
+
return async function assertImagePixelmatch(actual: Buffer, imageName?: string): Promise<string | undefined> {
|
212
|
+
const { expected, onCompare } = await getExpected(ctx, await getImagePaths(config, ctx.testFullPath, imageName));
|
213
|
+
|
214
|
+
if (expected == null) {
|
215
|
+
await onCompare(actual);
|
216
|
+
return imageName ? `Expected image '${imageName}' does not exists` : 'Expected image does not exists';
|
217
|
+
}
|
218
|
+
|
219
|
+
if (actual.equals(expected)) {
|
220
|
+
await onCompare(actual);
|
221
|
+
return;
|
222
|
+
}
|
223
|
+
|
224
|
+
const { isEqual, diff } = compareImages(expected, actual, pixelmatch, config.diffOptions);
|
225
|
+
|
226
|
+
if (isEqual) {
|
227
|
+
await onCompare(actual);
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
|
231
|
+
await onCompare(actual, expected, diff);
|
232
|
+
|
233
|
+
return imageName ? `Expected image '${imageName}' to match` : 'Expected image to match';
|
234
|
+
};
|
235
|
+
}
|
236
|
+
|
237
|
+
export function getOdiffAssert(
|
238
|
+
compare: (typeof import('odiff-bin'))['compare'],
|
239
|
+
ctx: ImageContext,
|
240
|
+
config: { screenDir: string; reportDir: string; odiffOptions?: ODiffOptions },
|
241
|
+
) {
|
242
|
+
const diffOptions = {
|
243
|
+
...config.odiffOptions,
|
244
|
+
noFailOnFsErrors: true,
|
245
|
+
};
|
246
|
+
return async function assertImage(image: Buffer, imageName?: string): Promise<string | undefined> {
|
247
|
+
const { actual, expect, diff } = await getOdiffExpected(
|
248
|
+
ctx,
|
249
|
+
image,
|
250
|
+
await getImagePaths(config, ctx.testFullPath, imageName),
|
251
|
+
);
|
252
|
+
const result = await compare(actual, expect, diff, diffOptions);
|
253
|
+
if (!result.match) {
|
254
|
+
if (result.reason == 'file-not-exists') {
|
255
|
+
return imageName ? `Expected image '${imageName}' does not exists` : 'Expected image does not exists';
|
256
|
+
}
|
257
|
+
return imageName ? `Expected image '${imageName}' to match` : 'Expected image to match';
|
258
|
+
}
|
259
|
+
};
|
260
|
+
}
|
package/src/server/config.ts
CHANGED
@@ -1,34 +1,46 @@
|
|
1
1
|
import fs from 'fs';
|
2
2
|
import path from 'path';
|
3
|
+
import cluster from 'cluster';
|
3
4
|
import { pathToFileURL } from 'url';
|
4
|
-
import
|
5
|
-
import {
|
5
|
+
import * as v from 'valibot';
|
6
|
+
import { loadStories as hybridStoriesProvider } from './providers/hybrid.js';
|
7
|
+
import {
|
8
|
+
Config,
|
9
|
+
BrowserConfig,
|
10
|
+
BrowserConfigObject,
|
11
|
+
Options,
|
12
|
+
isDefined,
|
13
|
+
WorkerOptions,
|
14
|
+
OptionsSchema,
|
15
|
+
} from '../types.js';
|
6
16
|
import { configExt, loadThroughTSX } from './utils.js';
|
7
|
-
import { CreeveyReporter
|
17
|
+
import { CreeveyReporter } from './reporters/creevey.js';
|
18
|
+
import { TeamcityReporter } from './reporters/teamcity.js';
|
8
19
|
import { logger } from './logger.js';
|
9
20
|
|
10
21
|
export const defaultBrowser = 'chrome';
|
11
22
|
|
12
|
-
export const defaultConfig: Omit<Config, 'gridUrl' | '
|
23
|
+
export const defaultConfig: Omit<Config, 'gridUrl' | 'tsConfig' | 'webdriver'> = {
|
13
24
|
disableTelemetry: false,
|
14
25
|
useWorkerQueue: false,
|
15
26
|
useDocker: true,
|
16
|
-
dockerImage: 'aerokube/selenoid:latest
|
27
|
+
dockerImage: 'aerokube/selenoid:latest', // TODO What about playwright?
|
17
28
|
dockerImagePlatform: '',
|
18
29
|
pullImages: true,
|
19
30
|
failFast: false,
|
20
31
|
storybookUrl: 'http://localhost:6006',
|
21
32
|
screenDir: path.resolve('images'),
|
22
33
|
reportDir: path.resolve('report'),
|
34
|
+
testsDir: path.resolve('src'),
|
23
35
|
reporter: process.env.TEAMCITY_VERSION ? TeamcityReporter : CreeveyReporter,
|
24
|
-
storiesProvider:
|
36
|
+
storiesProvider: hybridStoriesProvider,
|
25
37
|
maxRetries: 0,
|
26
38
|
testTimeout: 30000,
|
27
39
|
diffOptions: { threshold: 0.1, includeAA: false },
|
28
40
|
odiffOptions: { threshold: 0.1, antialiasing: true },
|
29
41
|
browsers: { [defaultBrowser]: true },
|
30
42
|
hooks: {},
|
31
|
-
testsRegex: /\.creevey\.(t|j)s$/,
|
43
|
+
testsRegex: /\.creevey\.(m|c)?(t|j)s$/,
|
32
44
|
};
|
33
45
|
|
34
46
|
function normalizeBrowserConfig(name: string, config: BrowserConfig): BrowserConfigObject {
|
@@ -57,7 +69,7 @@ function resolveConfigPath(configPath?: string): string | undefined {
|
|
57
69
|
return configPath;
|
58
70
|
}
|
59
71
|
|
60
|
-
export async function readConfig(options: Options): Promise<Config> {
|
72
|
+
export async function readConfig(options: Options | WorkerOptions): Promise<Config> {
|
61
73
|
const configPath = resolveConfigPath(options.config);
|
62
74
|
const userConfig: typeof defaultConfig & Partial<Pick<Config, 'gridUrl' | 'storiesProvider'>> = { ...defaultConfig };
|
63
75
|
|
@@ -80,14 +92,45 @@ export async function readConfig(options: Options): Promise<Config> {
|
|
80
92
|
configData.webdriver = SeleniumWebdriver;
|
81
93
|
}
|
82
94
|
|
95
|
+
for (const key in configData) {
|
96
|
+
const configKey = key as keyof typeof configData;
|
97
|
+
if (configData[configKey] === undefined) {
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
99
|
+
delete configData[configKey];
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
83
103
|
Object.assign(userConfig, configData);
|
84
104
|
}
|
85
105
|
|
86
|
-
if (
|
106
|
+
if (userConfig.resolveStorybookUrl && !options.storybookUrl) {
|
107
|
+
userConfig.storybookUrl = await userConfig.resolveStorybookUrl();
|
108
|
+
}
|
109
|
+
|
87
110
|
if (options.reportDir) userConfig.reportDir = path.resolve(options.reportDir);
|
88
111
|
if (options.screenDir) userConfig.screenDir = path.resolve(options.screenDir);
|
89
112
|
if (options.storybookUrl) userConfig.storybookUrl = options.storybookUrl;
|
90
113
|
|
114
|
+
if (v.is(OptionsSchema, options)) {
|
115
|
+
if (options.docker === false) userConfig.useDocker = false;
|
116
|
+
if (options.failFast != undefined) userConfig.failFast = Boolean(options.failFast);
|
117
|
+
if (cluster.isPrimary) {
|
118
|
+
if (options.storybookPort) {
|
119
|
+
const url = new URL(userConfig.storybookUrl);
|
120
|
+
url.port = `${options.storybookPort}`;
|
121
|
+
userConfig.storybookUrl = url.toString();
|
122
|
+
}
|
123
|
+
if (typeof options.storybookStart === 'string') userConfig.storybookAutorunCmd = options.storybookStart;
|
124
|
+
else if (options.storybookStart) {
|
125
|
+
const { default: getPort } = await import('get-port');
|
126
|
+
const url = new URL(userConfig.storybookUrl);
|
127
|
+
const port = await getPort({ port: Number(url.port) });
|
128
|
+
url.port = `${port}`;
|
129
|
+
userConfig.storybookUrl = url.toString();
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
91
134
|
// NOTE: Hack to pass typescript checking
|
92
135
|
const config = userConfig as Config;
|
93
136
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import type { Config, Options } from '../types';
|
2
|
+
import { waitOnUrl } from './utils.js';
|
3
|
+
import { logger } from './logger.js';
|
4
|
+
|
5
|
+
const RESPONSE_CHECK_TIMEOUT_MS = 10000;
|
6
|
+
const RESPONSE_CHECK_INTERVAL_MS = 200;
|
7
|
+
|
8
|
+
export function getStorybookUrl({ storybookUrl }: Config, { storybookStart }: Options): [string, string | undefined] {
|
9
|
+
if (storybookStart) {
|
10
|
+
const url = new URL(storybookUrl);
|
11
|
+
url.hostname = 'localhost';
|
12
|
+
return [url.toString(), storybookUrl];
|
13
|
+
}
|
14
|
+
return [storybookUrl, undefined];
|
15
|
+
}
|
16
|
+
|
17
|
+
export async function checkIsStorybookConnected(url: string) {
|
18
|
+
try {
|
19
|
+
await waitOnUrl(url, RESPONSE_CHECK_TIMEOUT_MS, RESPONSE_CHECK_INTERVAL_MS);
|
20
|
+
return true;
|
21
|
+
} catch (reason: unknown) {
|
22
|
+
const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
|
23
|
+
logger().error(error);
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
}
|
package/src/server/docker.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import tar from 'tar-stream';
|
2
|
+
import Logger from 'loglevel';
|
2
3
|
import { Writable } from 'stream';
|
3
4
|
import Dockerode, { Container } from 'dockerode';
|
4
5
|
import { DockerAuth } from '../types.js';
|
5
|
-
import { subscribeOn } from './messages.js';
|
6
6
|
import { logger } from './logger.js';
|
7
|
+
import { setWorkerContainer } from './worker/context.js';
|
7
8
|
|
8
9
|
const docker = new Dockerode();
|
9
10
|
|
@@ -50,17 +51,45 @@ export async function pullImages(
|
|
50
51
|
function onProgress(event: { id: string; status: string; progress?: string }): void {
|
51
52
|
if (!/^[a-z0-9]{12}$/i.test(event.id)) return;
|
52
53
|
|
53
|
-
spinner.text = `${image}: [${event.id}] ${event.status} ${event.progress
|
54
|
+
spinner.text = `${image}: [${event.id}] ${event.status} ${event.progress ?? ''}`;
|
54
55
|
}
|
55
56
|
});
|
56
57
|
});
|
57
58
|
}
|
58
59
|
}
|
59
60
|
|
60
|
-
export async function buildImage(imageName: string, dockerfile: string): Promise<void> {
|
61
|
+
export async function buildImage(imageName: string, version: string, dockerfile: string): Promise<void> {
|
61
62
|
const images = await docker.listImages({ filters: { label: [`creevey=${imageName}`] } });
|
62
63
|
|
63
|
-
|
64
|
+
const containers = await docker.listContainers({ all: true, filters: { label: [`creevey=${imageName}`] } });
|
65
|
+
if (containers.length > 0) {
|
66
|
+
await Promise.all(
|
67
|
+
containers.map(async (info) => {
|
68
|
+
const container = docker.getContainer(info.Id);
|
69
|
+
try {
|
70
|
+
await container.remove({ force: true });
|
71
|
+
} catch {
|
72
|
+
/* noop */
|
73
|
+
}
|
74
|
+
}),
|
75
|
+
);
|
76
|
+
}
|
77
|
+
|
78
|
+
const oldImages = images.filter((info) => info.Labels.version !== version);
|
79
|
+
if (oldImages.length > 0) {
|
80
|
+
await Promise.all(
|
81
|
+
oldImages.map(async (info) => {
|
82
|
+
const image = docker.getImage(info.Id);
|
83
|
+
try {
|
84
|
+
await image.remove({ force: true });
|
85
|
+
} catch {
|
86
|
+
/* noop */
|
87
|
+
}
|
88
|
+
}),
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
if (oldImages.length !== images.length) {
|
64
93
|
logger().info(`Image ${imageName} already exists`);
|
65
94
|
return;
|
66
95
|
}
|
@@ -70,15 +99,19 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
70
99
|
pack.finalize();
|
71
100
|
|
72
101
|
const { default: yoctoSpinner } = await import('yocto-spinner');
|
73
|
-
const spinner = yoctoSpinner({ text: `${imageName}: Build start` })
|
102
|
+
const spinner = yoctoSpinner({ text: `${imageName}: Build start` });
|
103
|
+
if (logger().getLevel() > Logger.levels.DEBUG) {
|
104
|
+
spinner.start();
|
105
|
+
}
|
106
|
+
let isFailed = false;
|
74
107
|
await new Promise<void>((resolve, reject) => {
|
75
|
-
|
76
|
-
// @ts-expect-error Type incompatibility AsyncIterator and AsyncIterableIterator
|
108
|
+
docker.buildImage(
|
77
109
|
pack,
|
78
|
-
|
110
|
+
// TODO Support buildkit decode grpc (version: '2')
|
111
|
+
{ t: imageName, labels: { creevey: imageName, version }, version: '1' },
|
79
112
|
(buildError: Error | null, stream) => {
|
80
113
|
if (buildError || !stream) {
|
81
|
-
spinner.error(buildError?.message);
|
114
|
+
// spinner.error(buildError?.message);
|
82
115
|
reject(buildError ?? new Error('Unknown error'));
|
83
116
|
return;
|
84
117
|
}
|
@@ -86,6 +119,8 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
86
119
|
docker.modem.followProgress(stream, onFinished, onProgress);
|
87
120
|
|
88
121
|
function onFinished(error: Error | null): void {
|
122
|
+
if (isFailed) return;
|
123
|
+
|
89
124
|
if (error) {
|
90
125
|
spinner.error(error.message);
|
91
126
|
reject(error);
|
@@ -95,10 +130,23 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
95
130
|
resolve();
|
96
131
|
}
|
97
132
|
|
98
|
-
function onProgress(
|
99
|
-
|
100
|
-
|
101
|
-
|
133
|
+
function onProgress(
|
134
|
+
event:
|
135
|
+
| { stream: string }
|
136
|
+
| { errorDetail: { code: number; message: string }; error: string }
|
137
|
+
| { id: string; aux: string }, // NOTE: Only with `version: '2'`
|
138
|
+
): void {
|
139
|
+
if ('stream' in event) {
|
140
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
141
|
+
logger().debug(event.stream.trim());
|
142
|
+
} else {
|
143
|
+
spinner.text = `${imageName}: [Build] - ${event.stream}`;
|
144
|
+
}
|
145
|
+
} else if ('errorDetail' in event) {
|
146
|
+
isFailed = true;
|
147
|
+
spinner.error(event.error);
|
148
|
+
reject(new Error(event.error));
|
149
|
+
}
|
102
150
|
}
|
103
151
|
},
|
104
152
|
);
|
@@ -111,33 +159,13 @@ export async function runImage(
|
|
111
159
|
options: Record<string, unknown>,
|
112
160
|
debug: boolean,
|
113
161
|
): Promise<string> {
|
114
|
-
await Promise.all(
|
115
|
-
(await docker.listContainers({ all: true, filters: { ancestor: [image] } })).map(async (info) => {
|
116
|
-
const container = docker.getContainer(info.Id);
|
117
|
-
try {
|
118
|
-
await container.stop();
|
119
|
-
} catch {
|
120
|
-
/* noop */
|
121
|
-
}
|
122
|
-
await container.remove();
|
123
|
-
}),
|
124
|
-
);
|
125
|
-
|
126
162
|
const hub = docker.run(image, args, debug ? process.stdout : new DevNull(), options, (error) => {
|
127
163
|
if (error) throw error;
|
128
164
|
});
|
129
165
|
|
130
166
|
return new Promise((resolve) => {
|
131
167
|
hub.once('container', (container: Container) => {
|
132
|
-
|
133
|
-
subscribeOn('shutdown', async () => {
|
134
|
-
try {
|
135
|
-
await container.stop();
|
136
|
-
await container.remove();
|
137
|
-
} catch {
|
138
|
-
/* noop */
|
139
|
-
}
|
140
|
-
});
|
168
|
+
setWorkerContainer(container);
|
141
169
|
});
|
142
170
|
hub.once(
|
143
171
|
'start',
|