creevey 0.9.0-beta.1 → 0.9.0-beta.10
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/CHANGELOG.md +51 -0
- package/README.md +9 -1
- package/addon/README.md +3 -0
- package/addon/package.json +5 -0
- package/docs/config.md +29 -26
- package/jest.config.js +6 -0
- package/lib/cjs/cli.js +5 -0
- package/lib/cjs/client/addon/Manager.js +264 -0
- package/lib/cjs/client/addon/components/Addon.js +55 -0
- package/lib/cjs/client/addon/components/Icons.js +46 -0
- package/lib/cjs/client/addon/components/Panel.js +72 -0
- package/lib/cjs/client/addon/components/TestSelect.js +65 -0
- package/lib/cjs/client/addon/components/Tools.js +95 -0
- package/lib/cjs/client/addon/decorator.js +11 -0
- package/lib/cjs/client/addon/index.js +31 -0
- package/lib/cjs/client/addon/preset.ie11.js +74 -0
- package/lib/cjs/client/addon/preset.js +17 -0
- package/lib/cjs/client/addon/preset.sb7.js +19 -0
- package/lib/cjs/client/addon/preview.js +14 -0
- package/lib/cjs/client/addon/readyForCapture.js +12 -0
- package/lib/cjs/client/addon/register.js +72 -0
- package/lib/cjs/client/addon/utils.js +42 -0
- package/lib/cjs/client/addon/withCreevey.js +351 -0
- package/lib/cjs/client/shared/components/ImagesView/BlendView.js +87 -0
- package/lib/cjs/client/shared/components/ImagesView/ImagesView.js +92 -0
- package/lib/cjs/client/shared/components/ImagesView/SideBySideView.js +154 -0
- package/lib/cjs/client/shared/components/ImagesView/SlideView.js +166 -0
- package/lib/cjs/client/shared/components/ImagesView/SwapView.js +91 -0
- package/lib/cjs/client/shared/components/ImagesView/index.js +45 -0
- package/lib/cjs/client/shared/components/PageFooter/PageFooter.js +50 -0
- package/lib/cjs/client/shared/components/PageFooter/Paging.js +94 -0
- package/lib/cjs/client/shared/components/PageHeader/ImagePreview.js +82 -0
- package/lib/cjs/client/shared/components/PageHeader/PageHeader.js +119 -0
- package/lib/cjs/client/shared/components/ResultsPage.js +143 -0
- package/lib/cjs/client/shared/creeveyClientApi.js +76 -0
- package/lib/cjs/client/shared/helpers.js +411 -0
- package/lib/cjs/client/shared/viewMode.js +17 -0
- package/lib/cjs/client/web/142.js +2 -0
- package/lib/cjs/client/web/142.js.LICENSE.txt +12 -0
- package/lib/cjs/client/web/32.js +1 -0
- package/lib/cjs/client/web/551.js +1 -0
- package/lib/cjs/client/web/566.js +2 -0
- package/lib/cjs/client/web/566.js.LICENSE.txt +31 -0
- package/lib/cjs/client/web/691.js +2 -0
- package/lib/cjs/client/web/691.js.LICENSE.txt +8 -0
- package/lib/cjs/client/web/725.js +1 -0
- package/lib/cjs/client/web/index.html +19 -0
- package/lib/cjs/client/web/main.js +2 -38
- package/lib/cjs/client/web/main.js.LICENSE.txt +49 -0
- package/lib/cjs/creevey.js +69 -0
- package/lib/cjs/index.js +62 -0
- package/lib/cjs/server/config.js +94 -0
- package/lib/cjs/server/docker.js +146 -0
- package/lib/cjs/server/extract.js +46 -0
- package/lib/cjs/server/index.js +83 -0
- package/lib/cjs/server/loaders/babel/creevey-plugin.js +86 -0
- package/lib/cjs/server/loaders/babel/helpers.js +469 -0
- package/lib/cjs/server/loaders/babel/register.js +124 -0
- package/lib/cjs/server/loaders/hooks/mdx.js +30 -0
- package/lib/cjs/server/loaders/hooks/svelte.js +65 -0
- package/lib/cjs/server/loaders/webpack/compile.js +269 -0
- package/lib/cjs/server/loaders/webpack/creevey-loader.js +172 -0
- package/lib/cjs/server/loaders/webpack/dummy-hmr.js +39 -0
- package/lib/cjs/server/loaders/webpack/mdx-loader.js +72 -0
- package/lib/cjs/server/loaders/webpack/start.js +41 -0
- package/lib/cjs/server/logger.js +48 -0
- package/lib/cjs/server/master/api.js +71 -0
- package/lib/cjs/server/master/index.js +146 -0
- package/lib/cjs/server/master/master.js +57 -0
- package/lib/cjs/server/master/pool.js +197 -0
- package/lib/cjs/server/master/runner.js +281 -0
- package/lib/cjs/server/master/server.js +131 -0
- package/lib/cjs/server/messages.js +264 -0
- package/lib/cjs/server/selenium/browser.js +668 -0
- package/lib/cjs/server/selenium/index.js +31 -0
- package/lib/cjs/server/selenium/selenoid.js +172 -0
- package/lib/cjs/server/stories.js +153 -0
- package/lib/cjs/server/storybook/entry.js +53 -0
- package/lib/cjs/server/storybook/helpers.js +158 -0
- package/lib/cjs/server/storybook/providers/browser.js +74 -0
- package/lib/cjs/server/storybook/providers/hybrid.js +82 -0
- package/lib/cjs/server/storybook/providers/nodejs.js +239 -0
- package/lib/cjs/server/testsFiles/parser.js +72 -0
- package/lib/cjs/server/testsFiles/register.js +44 -0
- package/lib/cjs/server/update.js +79 -0
- package/lib/cjs/server/utils.js +176 -0
- package/lib/cjs/server/worker/chai-image.js +142 -0
- package/lib/cjs/server/worker/helpers.js +69 -0
- package/lib/cjs/server/worker/index.js +15 -0
- package/lib/cjs/server/worker/reporter.js +108 -0
- package/lib/cjs/server/worker/worker.js +268 -0
- package/lib/cjs/shared/index.js +101 -0
- package/lib/cjs/shared/serializeRegExp.js +42 -0
- package/lib/cjs/types.js +75 -0
- package/lib/esm/cli.js +4 -0
- package/lib/esm/client/addon/Manager.js +248 -0
- package/lib/esm/client/addon/components/Addon.js +39 -0
- package/lib/esm/client/addon/components/Icons.js +31 -0
- package/lib/esm/client/addon/components/Panel.js +53 -0
- package/lib/esm/client/addon/components/TestSelect.js +51 -0
- package/lib/esm/client/addon/components/Tools.js +74 -0
- package/lib/esm/client/addon/decorator.js +2 -0
- package/lib/esm/client/addon/index.js +2 -0
- package/lib/esm/client/addon/preset.ie11.js +59 -0
- package/lib/esm/client/addon/preset.js +8 -0
- package/lib/esm/client/addon/preset.sb7.js +8 -0
- package/lib/esm/client/addon/preview.js +5 -0
- package/lib/esm/client/addon/readyForCapture.js +5 -0
- package/lib/esm/client/addon/register.js +51 -0
- package/lib/esm/client/addon/utils.js +32 -0
- package/lib/esm/client/addon/withCreevey.js +325 -0
- package/lib/esm/client/shared/components/ImagesView/BlendView.js +67 -0
- package/lib/esm/client/shared/components/ImagesView/ImagesView.js +69 -0
- package/lib/esm/client/shared/components/ImagesView/SideBySideView.js +131 -0
- package/lib/esm/client/shared/components/ImagesView/SlideView.js +143 -0
- package/lib/esm/client/shared/components/ImagesView/SwapView.js +71 -0
- package/lib/esm/client/shared/components/ImagesView/index.js +5 -0
- package/lib/esm/client/shared/components/PageFooter/PageFooter.js +36 -0
- package/lib/esm/client/shared/components/PageFooter/Paging.js +80 -0
- package/lib/esm/client/shared/components/PageHeader/ImagePreview.js +68 -0
- package/lib/esm/client/shared/components/PageHeader/PageHeader.js +97 -0
- package/lib/esm/client/shared/components/ResultsPage.js +115 -0
- package/lib/esm/client/shared/creeveyClientApi.js +67 -0
- package/lib/esm/client/shared/helpers.js +353 -0
- package/lib/esm/client/shared/viewMode.js +6 -0
- package/lib/esm/creevey.js +54 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/server/config.js +71 -0
- package/lib/esm/server/docker.js +123 -0
- package/lib/esm/server/extract.js +32 -0
- package/lib/esm/server/index.js +64 -0
- package/lib/esm/server/loaders/babel/creevey-plugin.js +72 -0
- package/lib/esm/server/loaders/babel/helpers.js +452 -0
- package/lib/esm/server/loaders/babel/register.js +103 -0
- package/lib/esm/server/loaders/hooks/mdx.js +15 -0
- package/lib/esm/server/loaders/hooks/svelte.js +49 -0
- package/lib/esm/server/loaders/webpack/compile.js +246 -0
- package/lib/esm/server/loaders/webpack/creevey-loader.js +152 -0
- package/lib/esm/server/loaders/webpack/dummy-hmr.js +32 -0
- package/lib/esm/server/loaders/webpack/mdx-loader.js +58 -0
- package/lib/esm/server/loaders/webpack/start.js +27 -0
- package/lib/esm/server/logger.js +20 -0
- package/lib/esm/server/master/api.js +60 -0
- package/lib/esm/server/master/index.js +125 -0
- package/lib/esm/server/master/master.js +38 -0
- package/lib/esm/server/master/pool.js +176 -0
- package/lib/esm/server/master/runner.js +259 -0
- package/lib/esm/server/master/server.js +107 -0
- package/lib/esm/server/messages.js +232 -0
- package/lib/esm/server/selenium/browser.js +635 -0
- package/lib/esm/server/selenium/index.js +2 -0
- package/lib/esm/server/selenium/selenoid.js +149 -0
- package/lib/esm/server/stories.js +135 -0
- package/lib/esm/server/storybook/entry.js +27 -0
- package/lib/esm/server/storybook/helpers.js +97 -0
- package/lib/esm/server/storybook/providers/browser.js +58 -0
- package/lib/esm/server/storybook/providers/hybrid.js +60 -0
- package/lib/esm/server/storybook/providers/nodejs.js +216 -0
- package/lib/esm/server/testsFiles/parser.js +50 -0
- package/lib/esm/server/testsFiles/register.js +31 -0
- package/lib/esm/server/update.js +61 -0
- package/lib/esm/server/utils.js +133 -0
- package/lib/esm/server/worker/chai-image.js +130 -0
- package/lib/esm/server/worker/helpers.js +60 -0
- package/lib/esm/server/worker/index.js +1 -0
- package/lib/esm/server/worker/reporter.js +86 -0
- package/lib/esm/server/worker/worker.js +238 -0
- package/lib/esm/shared/index.js +78 -0
- package/lib/esm/shared/serializeRegExp.js +24 -0
- package/lib/esm/types.js +44 -0
- package/lib/types/client/addon/Manager.d.ts +2 -2
- package/lib/types/client/addon/components/TestSelect.d.ts +0 -1
- package/lib/types/client/addon/index.d.ts +2 -0
- package/lib/types/client/addon/preset.d.ts +0 -22
- package/lib/types/client/addon/preset.ie11.d.ts +10 -0
- package/lib/types/client/addon/preset.sb7.d.ts +2 -0
- package/lib/types/client/addon/preview.d.ts +4 -0
- package/lib/types/client/addon/utils.d.ts +1 -0
- package/lib/types/client/addon/withCreevey.d.ts +1 -1
- package/lib/types/client/shared/components/ImagesView/BlendView.d.ts +1 -1
- package/lib/types/client/shared/components/ImagesView/ImagesView.d.ts +0 -1
- package/lib/types/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
- package/lib/types/client/shared/components/ImagesView/SlideView.d.ts +1 -1
- package/lib/types/client/shared/components/ImagesView/SwapView.d.ts +1 -1
- package/lib/types/client/shared/components/PageFooter/PageFooter.d.ts +0 -1
- package/lib/types/client/shared/components/PageFooter/Paging.d.ts +0 -1
- package/lib/types/client/shared/components/PageHeader/ImagePreview.d.ts +1 -1
- package/lib/types/client/shared/components/PageHeader/PageHeader.d.ts +0 -1
- package/lib/types/client/shared/components/ResultsPage.d.ts +1 -1
- package/lib/types/client/web/CreeveyApp.d.ts +0 -1
- package/lib/types/client/web/CreeveyLoader.d.ts +1 -2
- package/lib/types/client/web/CreeveyView/SideBar/Checkbox.d.ts +1 -1
- package/lib/types/client/web/CreeveyView/SideBar/SideBarHeader.d.ts +0 -1
- package/lib/types/client/web/CreeveyView/SideBar/SuiteLink.d.ts +4 -4
- package/lib/types/client/web/CreeveyView/SideBar/TestLink.d.ts +0 -1
- package/lib/types/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +1 -1
- package/lib/types/client/web/CreeveyView/SideBar/TestsStatus.d.ts +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/server/loaders/babel/register.d.ts +1 -1
- package/lib/types/server/loaders/webpack/creevey-loader.d.ts +4 -2
- package/lib/types/server/logger.d.ts +6 -2
- package/lib/types/server/messages.d.ts +14 -12
- package/lib/types/server/selenium/browser.d.ts +5 -3
- package/lib/types/server/storybook/entry.d.ts +2 -3
- package/lib/types/server/storybook/helpers.d.ts +1 -1
- package/lib/types/server/storybook/providers/browser.d.ts +2 -4
- package/lib/types/server/storybook/providers/hybrid.d.ts +2 -4
- package/lib/types/server/storybook/providers/nodejs.d.ts +3 -3
- package/lib/types/server/utils.d.ts +5 -1
- package/lib/types/{shared.d.ts → shared/index.d.ts} +1 -10
- package/lib/types/shared/serializeRegExp.d.ts +9 -0
- package/lib/types/types.d.ts +4 -7
- package/package.json +117 -103
- package/preset/ie11.js +5 -0
- package/{preset.js → preset/index.js} +2 -2
- package/preset/sb7.js +5 -0
- package/types/global.d.ts +5 -0
- package/types/mdx.d.ts +3 -2
- package/lib/cjs/client/web/1.js +0 -13
- package/lib/cjs/client/web/2.js +0 -1
- package/storybook-static/stories.json +0 -21
@@ -0,0 +1,353 @@
|
|
1
|
+
import { themes } from '@storybook/theming';
|
2
|
+
import { parse, stringify } from 'qs';
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
4
|
+
import { isTest, isDefined } from '../../types';
|
5
|
+
const statusUpdatesMap = new Map([[undefined, /(unknown|success|failed|pending|running)/], ['unknown', /(success|failed|pending|running)/], ['success', /(failed|pending|running)/], ['failed', /(pending|running)/], ['pending', /running/]]);
|
6
|
+
|
7
|
+
function makeEmptySuiteNode() {
|
8
|
+
let path = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
9
|
+
return {
|
10
|
+
path,
|
11
|
+
skip: true,
|
12
|
+
opened: false,
|
13
|
+
checked: true,
|
14
|
+
indeterminate: false,
|
15
|
+
children: {}
|
16
|
+
};
|
17
|
+
}
|
18
|
+
|
19
|
+
export function calcStatus(oldStatus, newStatus) {
|
20
|
+
var _statusUpdatesMap$get;
|
21
|
+
|
22
|
+
return newStatus && (_statusUpdatesMap$get = statusUpdatesMap.get(oldStatus)) !== null && _statusUpdatesMap$get !== void 0 && _statusUpdatesMap$get.test(newStatus) ? newStatus : oldStatus;
|
23
|
+
}
|
24
|
+
export function getTestPath(test) {
|
25
|
+
const {
|
26
|
+
browser,
|
27
|
+
testName,
|
28
|
+
storyPath
|
29
|
+
} = test;
|
30
|
+
return [...storyPath, testName, browser].filter(isDefined);
|
31
|
+
}
|
32
|
+
export function getSuiteByPath(suite, path) {
|
33
|
+
return path.reduce((suiteOrTest, pathToken) => isTest(suiteOrTest) ? suiteOrTest : suiteOrTest === null || suiteOrTest === void 0 ? void 0 : suiteOrTest.children[pathToken], suite);
|
34
|
+
}
|
35
|
+
export function getTestByPath(suite, path) {
|
36
|
+
var _getSuiteByPath;
|
37
|
+
|
38
|
+
const test = (_getSuiteByPath = getSuiteByPath(suite, path)) !== null && _getSuiteByPath !== void 0 ? _getSuiteByPath : suite;
|
39
|
+
return isTest(test) ? test : null;
|
40
|
+
}
|
41
|
+
export function getTestsByStoryId(suite, storyId) {
|
42
|
+
return Object.values(suite.children).filter(isDefined).flatMap(suiteOrTest => {
|
43
|
+
if (isTest(suiteOrTest)) return suiteOrTest.storyId === storyId ? suiteOrTest : [];
|
44
|
+
return getTestsByStoryId(suiteOrTest, storyId);
|
45
|
+
}).filter(isDefined);
|
46
|
+
}
|
47
|
+
|
48
|
+
function checkTests(suiteOrTest, checked) {
|
49
|
+
suiteOrTest.checked = checked;
|
50
|
+
|
51
|
+
if (!isTest(suiteOrTest)) {
|
52
|
+
suiteOrTest.indeterminate = false;
|
53
|
+
Object.values(suiteOrTest.children).filter(isDefined).forEach(child => checkTests(child, checked));
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
function updateChecked(suite) {
|
58
|
+
const children = Object.values(suite.children).filter(isDefined).filter(child => !child.skip);
|
59
|
+
const checkedEvery = children.every(test => test.checked);
|
60
|
+
const checkedSome = children.some(test => test.checked);
|
61
|
+
const indeterminate = children.some(test => isTest(test) ? false : test.indeterminate) || !checkedEvery && checkedSome;
|
62
|
+
const checked = indeterminate || suite.checked == checkedEvery ? suite.checked : checkedEvery;
|
63
|
+
suite.checked = checked;
|
64
|
+
suite.indeterminate = indeterminate;
|
65
|
+
}
|
66
|
+
|
67
|
+
export function checkSuite(suite, path, checked) {
|
68
|
+
const subSuite = getSuiteByPath(suite, path);
|
69
|
+
if (subSuite) checkTests(subSuite, checked);
|
70
|
+
path.slice(0, -1).map((_, index, tokens) => tokens.slice(0, tokens.length - index)).forEach(parentPath => {
|
71
|
+
const parentSuite = getSuiteByPath(suite, parentPath);
|
72
|
+
if (isTest(parentSuite)) return;
|
73
|
+
if (parentSuite) updateChecked(parentSuite);
|
74
|
+
});
|
75
|
+
updateChecked(suite);
|
76
|
+
}
|
77
|
+
export function treeifyTests(testsById) {
|
78
|
+
const rootSuite = makeEmptySuiteNode();
|
79
|
+
rootSuite.opened = true;
|
80
|
+
Object.values(testsById).forEach(test => {
|
81
|
+
if (!test) return;
|
82
|
+
const [browser, ...testPath] = getTestPath(test).reverse();
|
83
|
+
const lastSuite = testPath.reverse().reduce((suite, token) => {
|
84
|
+
const subSuite = suite.children[token] || makeEmptySuiteNode([...suite.path, token]);
|
85
|
+
subSuite.status = calcStatus(subSuite.status, test.status);
|
86
|
+
if (!test.skip) subSuite.skip = false;
|
87
|
+
if (!subSuite.skip) suite.skip = false;
|
88
|
+
suite.children[token] = subSuite;
|
89
|
+
suite.status = calcStatus(suite.status, subSuite.status);
|
90
|
+
|
91
|
+
if (isTest(subSuite)) {
|
92
|
+
throw new Error("Suite and Test should not have same path '".concat(JSON.stringify(getTestPath(subSuite)), "'"));
|
93
|
+
}
|
94
|
+
|
95
|
+
return subSuite;
|
96
|
+
}, rootSuite);
|
97
|
+
lastSuite.children[browser] = { ...test,
|
98
|
+
checked: true
|
99
|
+
};
|
100
|
+
});
|
101
|
+
return rootSuite;
|
102
|
+
}
|
103
|
+
export function getCheckedTests(suite) {
|
104
|
+
return Object.values(suite.children).filter(isDefined).flatMap(suiteOrTest => {
|
105
|
+
if (isTest(suiteOrTest)) return suiteOrTest.checked ? suiteOrTest : [];
|
106
|
+
if (!suiteOrTest.checked && !suiteOrTest.indeterminate) return [];
|
107
|
+
return getCheckedTests(suiteOrTest);
|
108
|
+
});
|
109
|
+
}
|
110
|
+
export function updateTestStatus(suite, path, update) {
|
111
|
+
var _suite$children$title;
|
112
|
+
|
113
|
+
const title = path.shift();
|
114
|
+
if (!title) return;
|
115
|
+
const suiteOrTest = (_suite$children$title = suite.children[title]) !== null && _suite$children$title !== void 0 ? _suite$children$title : suite.children[title] = { ...(path.length == 0 ? update : makeEmptySuiteNode([...suite.path, title])),
|
116
|
+
checked: suite.checked
|
117
|
+
};
|
118
|
+
|
119
|
+
if (isTest(suiteOrTest)) {
|
120
|
+
const test = suiteOrTest;
|
121
|
+
const {
|
122
|
+
skip,
|
123
|
+
status,
|
124
|
+
results,
|
125
|
+
approved
|
126
|
+
} = update;
|
127
|
+
if (isDefined(skip)) test.skip = skip;
|
128
|
+
if (isDefined(status)) test.status = status;
|
129
|
+
if (isDefined(results)) test.results ? test.results.push(...results) : test.results = results;
|
130
|
+
if (isDefined(approved)) Object.entries(approved).forEach(_ref => {
|
131
|
+
let [image, retry] = _ref;
|
132
|
+
return retry !== undefined && ((test.approved = test.approved || {})[image] = retry);
|
133
|
+
});
|
134
|
+
} else {
|
135
|
+
const subSuite = suiteOrTest;
|
136
|
+
updateTestStatus(subSuite, path, update);
|
137
|
+
}
|
138
|
+
|
139
|
+
suite.skip = Object.values(suite.children).filter(isDefined).map(_ref2 => {
|
140
|
+
let {
|
141
|
+
skip
|
142
|
+
} = _ref2;
|
143
|
+
return skip;
|
144
|
+
}).every(Boolean);
|
145
|
+
suite.status = Object.values(suite.children).filter(isDefined).map(_ref3 => {
|
146
|
+
let {
|
147
|
+
status
|
148
|
+
} = _ref3;
|
149
|
+
return status;
|
150
|
+
}).reduce(calcStatus);
|
151
|
+
}
|
152
|
+
export function removeTests(suite, path) {
|
153
|
+
var _suiteOrTest$children;
|
154
|
+
|
155
|
+
const title = path.shift();
|
156
|
+
if (!title) return;
|
157
|
+
const suiteOrTest = suite.children[title];
|
158
|
+
if (suiteOrTest && !isTest(suiteOrTest)) removeTests(suiteOrTest, path);
|
159
|
+
if (isTest(suiteOrTest) || Object.keys((_suiteOrTest$children = suiteOrTest === null || suiteOrTest === void 0 ? void 0 : suiteOrTest.children) !== null && _suiteOrTest$children !== void 0 ? _suiteOrTest$children : {}).length == 0) delete suite.children[title];
|
160
|
+
if (Object.keys(suite.children).length == 0) return;
|
161
|
+
updateChecked(suite);
|
162
|
+
suite.skip = Object.values(suite.children).filter(isDefined).map(_ref4 => {
|
163
|
+
let {
|
164
|
+
skip
|
165
|
+
} = _ref4;
|
166
|
+
return skip;
|
167
|
+
}).every(Boolean);
|
168
|
+
suite.status = Object.values(suite.children).filter(isDefined).map(_ref5 => {
|
169
|
+
let {
|
170
|
+
status
|
171
|
+
} = _ref5;
|
172
|
+
return status;
|
173
|
+
}).reduce(calcStatus);
|
174
|
+
}
|
175
|
+
export function filterTests(suite, filter) {
|
176
|
+
const {
|
177
|
+
status,
|
178
|
+
subStrings
|
179
|
+
} = filter;
|
180
|
+
if (!status && !subStrings.length) return suite;
|
181
|
+
const filteredSuite = { ...suite,
|
182
|
+
children: {}
|
183
|
+
};
|
184
|
+
Object.entries(suite.children).forEach(_ref6 => {
|
185
|
+
let [title, suiteOrTest] = _ref6;
|
186
|
+
if (!suiteOrTest || suiteOrTest.skip) return;
|
187
|
+
|
188
|
+
if (!status && subStrings.some(subString => title.toLowerCase().includes(subString))) {
|
189
|
+
filteredSuite.children[title] = suiteOrTest;
|
190
|
+
} else if (isTest(suiteOrTest)) {
|
191
|
+
if (status && suiteOrTest.status && ['pending', 'running', status].includes(suiteOrTest.status)) filteredSuite.children[title] = suiteOrTest;
|
192
|
+
} else {
|
193
|
+
const filteredSubSuite = filterTests(suiteOrTest, filter);
|
194
|
+
if (Object.keys(filteredSubSuite.children).length == 0) return;
|
195
|
+
filteredSuite.children[title] = filteredSubSuite;
|
196
|
+
}
|
197
|
+
});
|
198
|
+
return filteredSuite;
|
199
|
+
}
|
200
|
+
export function openSuite(suite, path, opened) {
|
201
|
+
const subSuite = path.reduce((suiteOrTest, pathToken) => {
|
202
|
+
if (suiteOrTest && !isTest(suiteOrTest)) {
|
203
|
+
if (opened) suiteOrTest.opened = opened;
|
204
|
+
return suiteOrTest.children[pathToken];
|
205
|
+
}
|
206
|
+
}, suite);
|
207
|
+
if (subSuite && !isTest(subSuite)) subSuite.opened = opened;
|
208
|
+
}
|
209
|
+
export function flattenSuite(suite) {
|
210
|
+
if (!suite.opened) return [];
|
211
|
+
return Object.entries(suite.children).flatMap(_ref7 => {
|
212
|
+
let [title, subSuite] = _ref7;
|
213
|
+
return subSuite ? [{
|
214
|
+
title,
|
215
|
+
suite: subSuite
|
216
|
+
}, ...(isTest(subSuite) ? [] : flattenSuite(subSuite))] : [];
|
217
|
+
});
|
218
|
+
}
|
219
|
+
export function countTestsStatus(suite) {
|
220
|
+
let successCount = 0;
|
221
|
+
let failedCount = 0;
|
222
|
+
let skippedCount = 0;
|
223
|
+
let pendingCount = 0;
|
224
|
+
const cases = Object.values(suite.children).filter(isDefined);
|
225
|
+
let suiteOrTest;
|
226
|
+
|
227
|
+
while (suiteOrTest = cases.pop()) {
|
228
|
+
if (isTest(suiteOrTest)) {
|
229
|
+
if (suiteOrTest.skip) skippedCount++;
|
230
|
+
if (suiteOrTest.status === 'success') successCount++;
|
231
|
+
if (suiteOrTest.status === 'failed') failedCount++;
|
232
|
+
if (suiteOrTest.status === 'pending') pendingCount++;
|
233
|
+
} else {
|
234
|
+
cases.push(...Object.values(suiteOrTest.children).filter(isDefined));
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
return {
|
239
|
+
successCount,
|
240
|
+
failedCount,
|
241
|
+
skippedCount,
|
242
|
+
pendingCount
|
243
|
+
};
|
244
|
+
}
|
245
|
+
export function getConnectionUrl() {
|
246
|
+
return [typeof __CREEVEY_SERVER_HOST__ == 'undefined' ? window.location.hostname : __CREEVEY_SERVER_HOST__, typeof __CREEVEY_SERVER_PORT__ == 'undefined' ? window.location.port : __CREEVEY_SERVER_PORT__].filter(Boolean).join(':');
|
247
|
+
}
|
248
|
+
export function getImageUrl(path, imageName) {
|
249
|
+
// path => [kind, story, test, browser]
|
250
|
+
const browser = path.slice(-1)[0];
|
251
|
+
const imagesUrl = window.location.host ? "".concat(window.location.protocol, "//").concat(getConnectionUrl()).concat(window.location.pathname == '/' ? '/report' : window.location.pathname.split('/').slice(0, -1).join('/'), "/").concat(encodeURI(path.slice(0, -1).join('/'))) : encodeURI(path.slice(0, -1).join('/'));
|
252
|
+
return imageName == browser ? imagesUrl : "".concat(imagesUrl, "/").concat(encodeURI(browser));
|
253
|
+
}
|
254
|
+
export function getBorderSize(element) {
|
255
|
+
// NOTE Firefox returns empty string for `borderWidth` prop
|
256
|
+
const borderSize = parseFloat(getComputedStyle(element).borderTopWidth);
|
257
|
+
return Number.isNaN(borderSize) ? 0 : borderSize;
|
258
|
+
}
|
259
|
+
export function useLoadImages(s1, s2, s3) {
|
260
|
+
const [loaded, setLoaded] = useState(false);
|
261
|
+
useEffect(() => {
|
262
|
+
setLoaded(false);
|
263
|
+
void Promise.all([s1, s2, s3].map(url => new Promise(resolve => {
|
264
|
+
const image = document.createElement('img');
|
265
|
+
image.src = url;
|
266
|
+
image.onload = resolve;
|
267
|
+
image.onerror = resolve;
|
268
|
+
}))).then(() => setLoaded(true));
|
269
|
+
}, [s1, s2, s3]);
|
270
|
+
return loaded;
|
271
|
+
}
|
272
|
+
/**
|
273
|
+
* Uses the ResizeObserver API to observe changes within the given HTML Element DOM Rect.
|
274
|
+
*
|
275
|
+
* @returns dimensions of element's content box (which means without paddings and border width)
|
276
|
+
*/
|
277
|
+
|
278
|
+
export function useResizeObserver(elementRef, onResize) {
|
279
|
+
let debounceTimeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 16;
|
280
|
+
const observerRef = useRef(null);
|
281
|
+
useEffect(() => {
|
282
|
+
if (!elementRef.current) return;
|
283
|
+
observerRef.current = new ResizeObserver(onResize);
|
284
|
+
observerRef.current.observe(elementRef.current);
|
285
|
+
return () => {
|
286
|
+
var _observerRef$current;
|
287
|
+
|
288
|
+
return (_observerRef$current = observerRef.current) === null || _observerRef$current === void 0 ? void 0 : _observerRef$current.disconnect();
|
289
|
+
};
|
290
|
+
}, [debounceTimeout, elementRef, onResize]);
|
291
|
+
}
|
292
|
+
export function useApplyScale(imageRef, scale, dependency) {
|
293
|
+
useLayoutEffect(() => {
|
294
|
+
if (!imageRef.current) return;
|
295
|
+
const image = imageRef.current;
|
296
|
+
const borderSize = getBorderSize(image);
|
297
|
+
image.style.height = "".concat(image.naturalHeight * scale + borderSize * 2, "px");
|
298
|
+
}, [imageRef, scale, dependency]);
|
299
|
+
}
|
300
|
+
export function useCalcScale(diffImageRef, loaded) {
|
301
|
+
const [scale, setScale] = useState(1);
|
302
|
+
const calcScale = useCallback(() => {
|
303
|
+
const diffImage = diffImageRef.current;
|
304
|
+
if (!diffImage || !loaded) return setScale(1);
|
305
|
+
const borderSize = getBorderSize(diffImage);
|
306
|
+
const ratio = (diffImage.getBoundingClientRect().width - borderSize * 2) / diffImage.naturalWidth;
|
307
|
+
setScale(Math.min(1, ratio));
|
308
|
+
}, [diffImageRef, loaded]);
|
309
|
+
useResizeObserver(diffImageRef, calcScale);
|
310
|
+
useLayoutEffect(calcScale, [calcScale]);
|
311
|
+
return scale;
|
312
|
+
}
|
313
|
+
const CREEVEY_THEME = 'Creevey_theme';
|
314
|
+
|
315
|
+
function isTheme(theme) {
|
316
|
+
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
317
|
+
}
|
318
|
+
|
319
|
+
function initialTheme() {
|
320
|
+
const theme = localStorage.getItem(CREEVEY_THEME);
|
321
|
+
return isTheme(theme) ? theme : 'light';
|
322
|
+
}
|
323
|
+
|
324
|
+
export function useTheme() {
|
325
|
+
const [theme, setTheme] = useState(initialTheme());
|
326
|
+
useEffect(() => {
|
327
|
+
localStorage.setItem(CREEVEY_THEME, theme);
|
328
|
+
}, [theme]);
|
329
|
+
return [theme, setTheme];
|
330
|
+
}
|
331
|
+
export function setSearchParams(testPath) {
|
332
|
+
const pageUrl = "?".concat(stringify({
|
333
|
+
testPath
|
334
|
+
}));
|
335
|
+
window.history.pushState({
|
336
|
+
testPath
|
337
|
+
}, '', pageUrl);
|
338
|
+
}
|
339
|
+
export function getTestPathFromSearch() {
|
340
|
+
const {
|
341
|
+
testPath
|
342
|
+
} = parse(window.location.search.slice(1)); //@ts-expect-error: This expression is not callable.
|
343
|
+
|
344
|
+
if (Array.isArray(testPath) && testPath.every(token => typeof token == 'string')) {
|
345
|
+
return testPath;
|
346
|
+
}
|
347
|
+
|
348
|
+
return [];
|
349
|
+
}
|
350
|
+
export function useForceUpdate() {
|
351
|
+
const [, update] = useState({});
|
352
|
+
return useCallback(() => update({}), []);
|
353
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
export const VIEW_MODE_KEY = 'Creevey_view_mode';
|
2
|
+
export const viewModes = ['side-by-side', 'swap', 'slide', 'blend'];
|
3
|
+
export const getViewMode = () => {
|
4
|
+
const item = localStorage.getItem(VIEW_MODE_KEY);
|
5
|
+
return item && viewModes.includes(item) ? item : 'side-by-side';
|
6
|
+
};
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import cluster from 'cluster';
|
2
|
+
import minimist from 'minimist';
|
3
|
+
import creevey from './server';
|
4
|
+
import { noop } from './types';
|
5
|
+
import { emitWorkerMessage } from './server/messages';
|
6
|
+
import { isShuttingDown, shutdown, shutdownWorkers } from './server/utils';
|
7
|
+
import { setDefaultLevel, levels } from 'loglevel';
|
8
|
+
import { logger } from './server/logger';
|
9
|
+
|
10
|
+
function shutdownOnException(reason) {
|
11
|
+
if (isShuttingDown.current) return;
|
12
|
+
const error = reason instanceof Error ? reason.stack ?? reason.message : reason;
|
13
|
+
logger.error(error);
|
14
|
+
process.exitCode = -1;
|
15
|
+
if (cluster.isWorker) emitWorkerMessage({
|
16
|
+
type: 'error',
|
17
|
+
payload: {
|
18
|
+
error
|
19
|
+
}
|
20
|
+
});
|
21
|
+
if (cluster.isPrimary && !isShuttingDown.current) void shutdownWorkers();
|
22
|
+
}
|
23
|
+
|
24
|
+
process.on('uncaughtException', shutdownOnException);
|
25
|
+
process.on('unhandledRejection', shutdownOnException);
|
26
|
+
if (cluster.isWorker) process.on('SIGINT', noop);
|
27
|
+
if (cluster.isPrimary) process.on('SIGINT', shutdown);
|
28
|
+
const argv = minimist(process.argv.slice(2), {
|
29
|
+
string: ['browser', 'config', 'reporter', 'reportDir', 'screenDir'],
|
30
|
+
boolean: ['debug', 'ui', 'saveReport', 'webpack', 'tests'],
|
31
|
+
default: {
|
32
|
+
port: 3000,
|
33
|
+
saveReport: true
|
34
|
+
},
|
35
|
+
alias: {
|
36
|
+
port: 'p',
|
37
|
+
config: 'c',
|
38
|
+
debug: 'd',
|
39
|
+
update: 'u',
|
40
|
+
extract: 'e'
|
41
|
+
}
|
42
|
+
}); // @ts-expect-error: define log level for storybook
|
43
|
+
|
44
|
+
global.LOGLEVEL = argv.debug ? 'debug' : 'warn';
|
45
|
+
|
46
|
+
if (argv.debug) {
|
47
|
+
logger.setDefaultLevel(levels.DEBUG);
|
48
|
+
setDefaultLevel(levels.DEBUG);
|
49
|
+
} else {
|
50
|
+
logger.setDefaultLevel(levels.INFO);
|
51
|
+
setDefaultLevel(levels.INFO);
|
52
|
+
}
|
53
|
+
|
54
|
+
void creevey(argv);
|
package/lib/esm/index.js
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
export * from './types';
|
2
|
+
export { loadStories as browserStoriesProvider } from './server/storybook/providers/browser';
|
3
|
+
export { loadStories as nodejsStoriesProvider } from './server/storybook/providers/nodejs';
|
4
|
+
export { loadStories as hybridStoriesProvider } from './server/storybook/providers/hybrid';
|
5
|
+
export * from './server/testsFiles/parser';
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { isCSFv3Enabled, storybookDirRef } from './storybook/helpers';
|
4
|
+
import { loadStories as nodejsStoriesProvider } from './storybook/providers/nodejs';
|
5
|
+
import { loadStories as browserStoriesProvider } from './storybook/providers/browser';
|
6
|
+
import { isDefined } from '../types';
|
7
|
+
export const defaultBrowser = 'chrome';
|
8
|
+
export const defaultConfig = {
|
9
|
+
useDocker: true,
|
10
|
+
useWebpackToExtractTests: false,
|
11
|
+
dockerImage: 'aerokube/selenoid:latest-release',
|
12
|
+
dockerImagePlatform: '',
|
13
|
+
pullImages: true,
|
14
|
+
failFast: false,
|
15
|
+
storybookUrl: 'http://localhost:6006',
|
16
|
+
screenDir: path.resolve('images'),
|
17
|
+
reportDir: path.resolve('report'),
|
18
|
+
storybookDir: path.resolve('.storybook'),
|
19
|
+
maxRetries: 0,
|
20
|
+
diffOptions: {
|
21
|
+
threshold: 0,
|
22
|
+
includeAA: true
|
23
|
+
},
|
24
|
+
browsers: {
|
25
|
+
[defaultBrowser]: true
|
26
|
+
},
|
27
|
+
hooks: {},
|
28
|
+
babelOptions: _ => _,
|
29
|
+
testsRegex: /\.creevey\.(t|j)s$/
|
30
|
+
};
|
31
|
+
|
32
|
+
function normalizeBrowserConfig(name, config) {
|
33
|
+
if (typeof config == 'boolean') return {
|
34
|
+
browserName: name
|
35
|
+
};
|
36
|
+
if (typeof config == 'string') return {
|
37
|
+
browserName: config
|
38
|
+
};
|
39
|
+
return config;
|
40
|
+
}
|
41
|
+
|
42
|
+
function resolveConfigPath(configPath) {
|
43
|
+
const rootDir = process.cwd();
|
44
|
+
const configDir = path.resolve('.creevey');
|
45
|
+
|
46
|
+
if (isDefined(configPath)) {
|
47
|
+
configPath = path.resolve(configPath);
|
48
|
+
} else if (fs.existsSync(configDir)) {
|
49
|
+
configPath = path.join(configDir, 'config'); // TODO We already find file with extension, why not use it?
|
50
|
+
} else if (fs.readdirSync(rootDir).find(filename => filename.startsWith('creevey.config'))) {
|
51
|
+
configPath = path.join(rootDir, 'creevey.config');
|
52
|
+
}
|
53
|
+
|
54
|
+
return configPath;
|
55
|
+
}
|
56
|
+
|
57
|
+
export async function readConfig(options) {
|
58
|
+
const configPath = resolveConfigPath(options.config);
|
59
|
+
const userConfig = { ...defaultConfig
|
60
|
+
};
|
61
|
+
if (isDefined(configPath)) Object.assign(userConfig, (await import(configPath)).default);
|
62
|
+
storybookDirRef.current = userConfig.storybookDir;
|
63
|
+
if (!userConfig.storiesProvider) userConfig.storiesProvider = (await isCSFv3Enabled()) ? browserStoriesProvider : nodejsStoriesProvider;
|
64
|
+
if (options.failFast != undefined) userConfig.failFast = Boolean(options.failFast);
|
65
|
+
if (options.reportDir) userConfig.reportDir = path.resolve(options.reportDir);
|
66
|
+
if (options.screenDir) userConfig.screenDir = path.resolve(options.screenDir); // NOTE: Hack to pass typescript checking
|
67
|
+
|
68
|
+
const config = userConfig;
|
69
|
+
Object.entries(config.browsers).forEach(([browser, browserConfig]) => config.browsers[browser] = normalizeBrowserConfig(browser, browserConfig));
|
70
|
+
return config;
|
71
|
+
}
|
@@ -0,0 +1,123 @@
|
|
1
|
+
import cluster from 'cluster';
|
2
|
+
import { isDockerMessage } from '../types';
|
3
|
+
import { subscribeOn, sendDockerMessage, emitDockerMessage } from './messages';
|
4
|
+
import { isInsideDocker, LOCALHOST_REGEXP } from './utils';
|
5
|
+
import Dockerode from 'dockerode';
|
6
|
+
import { Writable } from 'stream';
|
7
|
+
import ora from 'ora';
|
8
|
+
import { logger } from './logger';
|
9
|
+
const docker = new Dockerode();
|
10
|
+
|
11
|
+
class DevNull extends Writable {
|
12
|
+
_write(_chunk, _encoding, callback) {
|
13
|
+
setImmediate(callback);
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
17
|
+
|
18
|
+
export async function pullImages(images, {
|
19
|
+
auth,
|
20
|
+
platform
|
21
|
+
} = {}) {
|
22
|
+
const args = {};
|
23
|
+
if (auth) args.authconfig = auth;
|
24
|
+
if (platform) args.platform = platform;
|
25
|
+
logger.info('Pull docker images');
|
26
|
+
|
27
|
+
for (const image of images) {
|
28
|
+
await new Promise((resolve, reject) => {
|
29
|
+
const spinner = ora(`${image}: Pull start`).start(); // eslint-disable-next-line @typescript-eslint/no-floating-promises
|
30
|
+
|
31
|
+
docker.pull(image, args, function (pullError, stream) {
|
32
|
+
if (pullError) {
|
33
|
+
spinner.fail();
|
34
|
+
return reject(pullError);
|
35
|
+
} // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
36
|
+
|
37
|
+
|
38
|
+
docker.modem.followProgress(stream, onFinished, onProgress);
|
39
|
+
|
40
|
+
function onFinished(error) {
|
41
|
+
if (error) {
|
42
|
+
spinner.fail();
|
43
|
+
return reject(error);
|
44
|
+
}
|
45
|
+
|
46
|
+
spinner.succeed(`${image}: Pull complete`);
|
47
|
+
resolve();
|
48
|
+
}
|
49
|
+
|
50
|
+
function onProgress(event) {
|
51
|
+
if (!/^[a-z0-9]{12}$/i.test(event.id)) return;
|
52
|
+
spinner.text = `${image}: [${event.id}] ${event.status} ${event.progress ? `${event.progress}` : ''}`;
|
53
|
+
}
|
54
|
+
});
|
55
|
+
});
|
56
|
+
}
|
57
|
+
}
|
58
|
+
export async function runImage(image, args, options, debug) {
|
59
|
+
await Promise.all((await docker.listContainers({
|
60
|
+
all: true,
|
61
|
+
filters: {
|
62
|
+
ancestor: [image]
|
63
|
+
}
|
64
|
+
})).map(async info => {
|
65
|
+
const container = docker.getContainer(info.Id);
|
66
|
+
|
67
|
+
try {
|
68
|
+
await container.stop();
|
69
|
+
} catch (_) {
|
70
|
+
/* noop */
|
71
|
+
}
|
72
|
+
|
73
|
+
await container.remove();
|
74
|
+
}));
|
75
|
+
const hub = docker.run(image, args, debug ? process.stdout : new DevNull(), options, error => {
|
76
|
+
if (error) throw error;
|
77
|
+
});
|
78
|
+
return new Promise(resolve => {
|
79
|
+
hub.once('container', container => {
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
81
|
+
subscribeOn('shutdown', async () => {
|
82
|
+
try {
|
83
|
+
await container.stop();
|
84
|
+
await container.remove();
|
85
|
+
} catch (error) {
|
86
|
+
/* noop */
|
87
|
+
}
|
88
|
+
});
|
89
|
+
});
|
90
|
+
hub.once('start', container => void container.inspect().then(info => resolve(info.NetworkSettings.Networks.bridge.IPAddress)));
|
91
|
+
});
|
92
|
+
}
|
93
|
+
export default async function (config, browser, startContainer) {
|
94
|
+
if (cluster.isPrimary) {
|
95
|
+
const host = await startContainer();
|
96
|
+
let gridUrl = 'http://localhost:4444/wd/hub';
|
97
|
+
gridUrl = isInsideDocker ? gridUrl.replace(LOCALHOST_REGEXP, host) : gridUrl;
|
98
|
+
cluster.on('message', (worker, message) => {
|
99
|
+
if (!isDockerMessage(message)) return;
|
100
|
+
const dockerMessage = message;
|
101
|
+
if (dockerMessage.type != 'start') return;
|
102
|
+
sendDockerMessage(worker, {
|
103
|
+
type: 'success',
|
104
|
+
payload: {
|
105
|
+
gridUrl
|
106
|
+
}
|
107
|
+
});
|
108
|
+
});
|
109
|
+
} else {
|
110
|
+
if (browser && config.browsers[browser].gridUrl) return Promise.resolve();
|
111
|
+
return new Promise(resolve => {
|
112
|
+
subscribeOn('docker', message => {
|
113
|
+
if (message.type == 'success') {
|
114
|
+
config.gridUrl = message.payload.gridUrl;
|
115
|
+
resolve();
|
116
|
+
}
|
117
|
+
});
|
118
|
+
emitDockerMessage({
|
119
|
+
type: 'start'
|
120
|
+
});
|
121
|
+
});
|
122
|
+
}
|
123
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { subscribeOn } from './messages';
|
2
|
+
import { loadTestsFromStories, saveStoriesJson, saveTestsJson } from './stories';
|
3
|
+
import { extractStoriesData } from './storybook/providers/nodejs';
|
4
|
+
export default async function extract(config, options) {
|
5
|
+
if (config.useWebpackToExtractTests && process.env.__CREEVEY_ENV__ != 'test') {
|
6
|
+
await new Promise((resolve, reject) => {
|
7
|
+
subscribeOn('webpack', message => {
|
8
|
+
switch (message.type) {
|
9
|
+
case 'success':
|
10
|
+
return resolve();
|
11
|
+
|
12
|
+
case 'fail':
|
13
|
+
return reject();
|
14
|
+
}
|
15
|
+
});
|
16
|
+
void (async () => (await import('./loaders/webpack/compile')).default(config, options))();
|
17
|
+
});
|
18
|
+
}
|
19
|
+
|
20
|
+
const tests = await loadTestsFromStories(Object.keys(config.browsers), async () => {
|
21
|
+
const data = await extractStoriesData(config, {
|
22
|
+
watch: false,
|
23
|
+
debug: options.debug
|
24
|
+
});
|
25
|
+
const stories = data.stories;
|
26
|
+
if (options.extract) saveStoriesJson(data, options.extract);
|
27
|
+
return stories;
|
28
|
+
});
|
29
|
+
if (options.tests) saveTestsJson(tests); // eslint-disable-next-line no-process-exit
|
30
|
+
|
31
|
+
process.exit(0);
|
32
|
+
}
|