creevey 0.8.0-beta.0 → 0.8.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/CHANGELOG.md +30 -7
- package/README.md +9 -1
- package/addon/README.md +3 -0
- package/addon/package.json +4 -0
- package/docs/config.md +29 -26
- package/jest.config.js +6 -0
- package/lib/cjs/client/addon/Manager.js +122 -271
- package/lib/cjs/client/addon/components/Addon.js +17 -38
- package/lib/cjs/client/addon/components/Icons.js +11 -7
- package/lib/cjs/client/addon/components/Panel.js +17 -13
- package/lib/cjs/client/addon/components/TestSelect.js +11 -9
- package/lib/cjs/client/addon/components/Tools.js +21 -40
- package/lib/cjs/client/addon/decorator.js +1 -1
- 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 +13 -31
- package/lib/cjs/client/addon/readyForCapture.js +12 -0
- package/lib/cjs/client/addon/register.js +46 -70
- package/lib/cjs/client/addon/utils.js +6 -2
- package/lib/cjs/client/addon/withCreevey.js +221 -155
- package/lib/cjs/client/shared/components/ImagesView/BlendView.js +26 -24
- package/lib/cjs/client/shared/components/ImagesView/ImagesView.js +22 -18
- package/lib/cjs/client/shared/components/ImagesView/SideBySideView.js +44 -66
- package/lib/cjs/client/shared/components/ImagesView/SlideView.js +38 -50
- package/lib/cjs/client/shared/components/ImagesView/SwapView.js +26 -45
- package/lib/cjs/client/shared/components/ImagesView/index.js +9 -9
- package/lib/cjs/client/shared/components/PageFooter/PageFooter.js +12 -8
- package/lib/cjs/client/shared/components/PageFooter/Paging.js +14 -18
- package/lib/cjs/client/shared/components/PageHeader/ImagePreview.js +22 -18
- package/lib/cjs/client/shared/components/PageHeader/PageHeader.js +42 -67
- package/lib/cjs/client/shared/components/ResultsPage.js +39 -69
- package/lib/cjs/client/shared/creeveyClientApi.js +55 -82
- package/lib/cjs/client/shared/helpers.js +140 -211
- package/lib/cjs/client/shared/viewMode.js +5 -5
- 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/main.js +2 -38
- package/lib/cjs/client/web/main.js.LICENSE.txt +49 -0
- package/lib/cjs/creevey.js +3 -5
- package/lib/cjs/index.js +10 -15
- package/lib/cjs/server/config.js +5 -4
- package/lib/cjs/server/docker.js +3 -7
- package/lib/cjs/server/extract.js +7 -4
- package/lib/cjs/server/index.js +3 -5
- package/lib/cjs/server/loaders/babel/creevey-plugin.js +1 -3
- package/lib/cjs/server/loaders/babel/helpers.js +13 -23
- package/lib/cjs/server/loaders/babel/register.js +2 -4
- package/lib/cjs/server/loaders/webpack/compile.js +34 -51
- package/lib/cjs/server/loaders/webpack/creevey-loader.js +20 -22
- package/lib/cjs/server/loaders/webpack/dummy-hmr.js +2 -7
- package/lib/cjs/server/loaders/webpack/mdx-loader.js +2 -2
- package/lib/cjs/server/loaders/webpack/start.js +1 -1
- package/lib/cjs/server/logger.js +2 -1
- package/lib/cjs/server/master/index.js +4 -4
- package/lib/cjs/server/master/master.js +1 -0
- package/lib/cjs/server/master/pool.js +38 -47
- package/lib/cjs/server/master/runner.js +53 -66
- package/lib/cjs/server/master/server.js +78 -4
- package/lib/cjs/server/messages.js +128 -18
- package/lib/cjs/server/selenium/browser.js +129 -55
- package/lib/cjs/server/selenium/selenoid.js +5 -7
- package/lib/cjs/server/stories.js +58 -72
- package/lib/cjs/server/storybook/entry.js +7 -22
- package/lib/cjs/server/storybook/helpers.js +20 -27
- package/lib/cjs/server/storybook/providers/browser.js +74 -0
- package/lib/cjs/server/storybook/{nodejs-provider.js → providers/nodejs.js} +37 -20
- package/lib/cjs/server/update.js +1 -5
- package/lib/cjs/server/utils.js +26 -35
- package/lib/cjs/server/worker/helpers.js +2 -6
- package/lib/cjs/server/worker/reporter.js +8 -20
- package/lib/cjs/server/worker/worker.js +21 -19
- package/lib/cjs/shared/index.js +101 -0
- package/lib/cjs/shared/serializeRegExp.js +42 -0
- package/lib/cjs/types.js +11 -6
- package/lib/esm/client/addon/Manager.js +122 -271
- package/lib/esm/client/addon/components/Addon.js +15 -34
- package/lib/esm/client/addon/components/Icons.js +10 -6
- package/lib/esm/client/addon/components/Panel.js +17 -13
- package/lib/esm/client/addon/components/TestSelect.js +11 -9
- package/lib/esm/client/addon/components/Tools.js +19 -36
- package/lib/esm/client/addon/decorator.js +1 -1
- 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 +12 -26
- package/lib/esm/client/addon/readyForCapture.js +5 -0
- package/lib/esm/client/addon/register.js +42 -66
- package/lib/esm/client/addon/utils.js +3 -2
- package/lib/esm/client/addon/withCreevey.js +209 -156
- package/lib/esm/client/shared/components/ImagesView/BlendView.js +23 -20
- package/lib/esm/client/shared/components/ImagesView/ImagesView.js +21 -17
- package/lib/esm/client/shared/components/ImagesView/SideBySideView.js +42 -63
- package/lib/esm/client/shared/components/ImagesView/SlideView.js +36 -47
- package/lib/esm/client/shared/components/ImagesView/SwapView.js +24 -42
- package/lib/esm/client/shared/components/PageFooter/PageFooter.js +12 -8
- package/lib/esm/client/shared/components/PageFooter/Paging.js +14 -18
- package/lib/esm/client/shared/components/PageHeader/ImagePreview.js +22 -18
- package/lib/esm/client/shared/components/PageHeader/PageHeader.js +37 -60
- package/lib/esm/client/shared/components/ResultsPage.js +36 -64
- package/lib/esm/client/shared/creeveyClientApi.js +57 -84
- package/lib/esm/client/shared/helpers.js +124 -195
- package/lib/esm/client/shared/viewMode.js +4 -4
- package/lib/esm/creevey.js +3 -5
- package/lib/esm/index.js +2 -3
- package/lib/esm/server/config.js +4 -5
- package/lib/esm/server/docker.js +2 -2
- package/lib/esm/server/extract.js +6 -4
- package/lib/esm/server/index.js +3 -4
- package/lib/esm/server/loaders/babel/creevey-plugin.js +1 -3
- package/lib/esm/server/loaders/babel/helpers.js +12 -22
- package/lib/esm/server/loaders/babel/register.js +3 -5
- package/lib/esm/server/loaders/webpack/compile.js +35 -52
- package/lib/esm/server/loaders/webpack/creevey-loader.js +9 -10
- package/lib/esm/server/loaders/webpack/dummy-hmr.js +2 -6
- package/lib/esm/server/loaders/webpack/mdx-loader.js +2 -2
- package/lib/esm/server/loaders/webpack/start.js +1 -1
- package/lib/esm/server/master/index.js +4 -4
- package/lib/esm/server/master/master.js +1 -0
- package/lib/esm/server/master/pool.js +38 -49
- package/lib/esm/server/master/runner.js +53 -66
- package/lib/esm/server/master/server.js +76 -6
- package/lib/esm/server/messages.js +118 -14
- package/lib/esm/server/selenium/browser.js +126 -57
- package/lib/esm/server/selenium/selenoid.js +4 -6
- package/lib/esm/server/stories.js +58 -70
- package/lib/esm/server/storybook/entry.js +5 -22
- package/lib/esm/server/storybook/helpers.js +11 -20
- package/lib/esm/server/storybook/providers/browser.js +60 -0
- package/lib/esm/server/storybook/{nodejs-provider.js → providers/nodejs.js} +35 -19
- package/lib/esm/server/update.js +1 -5
- package/lib/esm/server/utils.js +18 -31
- package/lib/esm/server/worker/helpers.js +2 -6
- package/lib/esm/server/worker/reporter.js +8 -20
- package/lib/esm/server/worker/worker.js +22 -20
- package/lib/esm/shared/index.js +78 -0
- package/lib/esm/shared/serializeRegExp.js +24 -0
- package/lib/esm/types.js +3 -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 +2 -1
- package/lib/types/client/addon/preset.ie11.d.ts +10 -0
- package/lib/types/client/addon/readyForCapture.d.ts +6 -0
- package/lib/types/client/addon/utils.d.ts +1 -0
- package/lib/types/client/addon/withCreevey.d.ts +13 -2
- 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 +6 -6
- 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 +0 -1
- 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/master/master.d.ts +1 -0
- package/lib/types/server/master/pool.d.ts +1 -0
- package/lib/types/server/master/server.d.ts +1 -1
- package/lib/types/server/messages.d.ts +17 -6
- package/lib/types/server/selenium/browser.d.ts +5 -2
- package/lib/types/server/stories.d.ts +2 -2
- 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 +4 -0
- package/lib/types/server/storybook/providers/nodejs.d.ts +9 -0
- package/lib/types/server/utils.d.ts +5 -1
- package/lib/types/server/worker/helpers.d.ts +2 -1
- package/lib/types/shared/index.d.ts +7 -0
- package/lib/types/shared/serializeRegExp.d.ts +9 -0
- package/lib/types/types.d.ts +32 -5
- package/package.json +120 -103
- package/preset/ie11.js +5 -0
- package/{preset.js → preset/index.js} +2 -2
- package/types/mdx.d.ts +3 -2
- package/types/mocha.d.ts +1 -0
- package/lib/cjs/client/web/1.js +0 -13
- package/lib/cjs/client/web/2.js +0 -1
- package/lib/cjs/shared.js +0 -35
- package/lib/esm/shared.js +0 -22
- package/lib/types/server/storybook/nodejs-provider.d.ts +0 -5
- package/lib/types/shared.d.ts +0 -4
@@ -9,20 +9,21 @@ import { Builder, By, Capabilities, Origin } from 'selenium-webdriver';
|
|
9
9
|
import { PageLoadStrategy } from 'selenium-webdriver/lib/capabilities';
|
10
10
|
import { isDefined, noop } from '../../types';
|
11
11
|
import { colors, logger } from '../logger';
|
12
|
-
import { subscribeOn } from '../messages';
|
13
|
-
import { importStorybookCoreEvents
|
12
|
+
import { emitStoriesMessage, subscribeOn } from '../messages';
|
13
|
+
import { importStorybookCoreEvents } from '../storybook/helpers';
|
14
14
|
import { isShuttingDown, LOCALHOST_REGEXP, runSequence } from '../utils';
|
15
15
|
const DOCKER_INTERNAL = 'host.docker.internal';
|
16
16
|
let browserLogger = logger;
|
17
|
+
let browserName = '';
|
18
|
+
let browser = null;
|
19
|
+
let creeveyServerHost = null;
|
17
20
|
|
18
21
|
function getSessionData(grid, sessionId = '') {
|
19
22
|
const gridUrl = new URL(grid);
|
20
23
|
gridUrl.pathname = `/host/${sessionId}`;
|
21
24
|
return new Promise((resolve, reject) => (gridUrl.protocol == 'https:' ? https : http).get(gridUrl.toString(), res => {
|
22
25
|
if (res.statusCode !== 200) {
|
23
|
-
|
24
|
-
|
25
|
-
return reject(new Error(`Couldn't get session data for ${sessionId}. Status code: ${(_res$statusCode = res.statusCode) !== null && _res$statusCode !== void 0 ? _res$statusCode : 'Unknown'}`));
|
26
|
+
return reject(new Error(`Couldn't get session data for ${sessionId}. Status code: ${res.statusCode ?? 'Unknown'}`));
|
26
27
|
}
|
27
28
|
|
28
29
|
let data = '';
|
@@ -32,17 +33,19 @@ function getSessionData(grid, sessionId = '') {
|
|
32
33
|
try {
|
33
34
|
resolve(JSON.parse(data));
|
34
35
|
} catch (error) {
|
35
|
-
|
36
|
-
|
37
|
-
reject(new Error(`Couldn't get session data for ${sessionId}. ${error instanceof Error ? (_error$stack = error.stack) !== null && _error$stack !== void 0 ? _error$stack : error.message : error}`));
|
36
|
+
reject(new Error(`Couldn't get session data for ${sessionId}. ${error instanceof Error ? error.stack ?? error.message : error}`));
|
38
37
|
}
|
39
38
|
});
|
40
39
|
}));
|
41
40
|
}
|
42
41
|
|
42
|
+
function getAddresses() {
|
43
|
+
return [DOCKER_INTERNAL].concat(...Object.values(networkInterfaces()).filter(isDefined).map(network => network.filter(info => info.family == 'IPv4').map(info => info.address)));
|
44
|
+
}
|
45
|
+
|
43
46
|
async function resolveStorybookUrl(storybookUrl, checkUrl) {
|
44
47
|
browserLogger.debug('Resolving storybook url');
|
45
|
-
const addresses =
|
48
|
+
const addresses = getAddresses();
|
46
49
|
|
47
50
|
for (const ip of addresses) {
|
48
51
|
const resolvedUrl = storybookUrl.replace(LOCALHOST_REGEXP, ip);
|
@@ -96,17 +99,6 @@ function getUrlChecker(browser) {
|
|
96
99
|
}
|
97
100
|
|
98
101
|
async function waitForStorybook(browser) {
|
99
|
-
// NOTE: Storybook 5.x doesn't have the `last` method
|
100
|
-
if (isStorybookVersionLessThan(6)) {
|
101
|
-
browserLogger.debug('Waiting for `load` event to make sure that storybook is initiated');
|
102
|
-
return browser.executeAsyncScript(function (callback) {
|
103
|
-
if (document.readyState == 'complete') return callback();
|
104
|
-
window.addEventListener('load', function () {
|
105
|
-
callback();
|
106
|
-
});
|
107
|
-
});
|
108
|
-
}
|
109
|
-
|
110
102
|
browserLogger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
111
103
|
let wait = true;
|
112
104
|
let isTimeout = false;
|
@@ -129,11 +121,11 @@ async function waitForStorybook(browser) {
|
|
129
121
|
}
|
130
122
|
|
131
123
|
async function resetMousePosition(browser) {
|
132
|
-
var
|
124
|
+
var _await$browser$getCap, _await$browser$getCap2;
|
133
125
|
|
134
126
|
browserLogger.debug('Resetting mouse position to the top-left corner');
|
135
127
|
const browserName = (await browser.getCapabilities()).getBrowserName();
|
136
|
-
const [browserVersion] = (
|
128
|
+
const [browserVersion] = ((_await$browser$getCap = (await browser.getCapabilities()).getBrowserVersion()) === null || _await$browser$getCap === void 0 ? void 0 : _await$browser$getCap.split('.')) ?? ((_await$browser$getCap2 = (await browser.getCapabilities()).get('version')) === null || _await$browser$getCap2 === void 0 ? void 0 : _await$browser$getCap2.split('.')) ?? []; // NOTE Reset mouse position to support keweb selenium grid browser versions
|
137
129
|
|
138
130
|
if (browserName == 'chrome' && browserVersion == '70') {
|
139
131
|
const {
|
@@ -217,10 +209,10 @@ const getScrollBarWidth = (() => {
|
|
217
209
|
|
218
210
|
|
219
211
|
async function hasScrollBar(browser) {
|
220
|
-
var _await$browser$
|
212
|
+
var _await$browser$getCap3;
|
221
213
|
|
222
214
|
const browserName = (await browser.getCapabilities()).getBrowserName();
|
223
|
-
const [browserVersion] = (
|
215
|
+
const [browserVersion] = ((_await$browser$getCap3 = (await browser.getCapabilities()).getBrowserVersion()) === null || _await$browser$getCap3 === void 0 ? void 0 : _await$browser$getCap3.split('.')) ?? [];
|
224
216
|
return browserName != 'Safari' && // NOTE This need to work with keweb selenium grid
|
225
217
|
!(browserName == 'firefox' && browserVersion == '61');
|
226
218
|
}
|
@@ -271,7 +263,8 @@ async function takeCompositeScreenshot(browser, windowRect, elementRect) {
|
|
271
263
|
const scrollOffset = isFitVertically || isScreenshotWithoutScrollBar ? 0 : scrollBarWidth;
|
272
264
|
const i = (y * compositeImage.width + x) * 4;
|
273
265
|
const j = // NOTE compositeImage(x, y) => image(x, y)
|
274
|
-
(y % viewportHeight * (viewportWidth + scrollOffset) + x % viewportWidth) * 4 + (
|
266
|
+
(y % viewportHeight * (viewportWidth + scrollOffset) + x % viewportWidth) * 4 + ( // NOTE Offset for last row/col image
|
267
|
+
isLastRow ? yOffset * (viewportWidth + scrollOffset) * 4 : 0) + (isLastCol ? xOffset * 4 : 0);
|
275
268
|
const image = images[row * cols + col];
|
276
269
|
compositeImage.data[i + 0] = image.data[j + 0];
|
277
270
|
compositeImage.data[i + 1] = image.data[j + 1];
|
@@ -283,7 +276,7 @@ async function takeCompositeScreenshot(browser, windowRect, elementRect) {
|
|
283
276
|
return PNG.sync.write(compositeImage).toString('base64');
|
284
277
|
}
|
285
278
|
|
286
|
-
async function takeScreenshot(browser, captureElement, ignoreElements) {
|
279
|
+
export async function takeScreenshot(browser, captureElement, ignoreElements) {
|
287
280
|
let screenshot;
|
288
281
|
const ignoreStyles = await insertIgnoreStyles(browser, ignoreElements);
|
289
282
|
|
@@ -320,7 +313,7 @@ async function takeScreenshot(browser, captureElement, ignoreElements) {
|
|
320
313
|
const {
|
321
314
|
elementRect,
|
322
315
|
windowRect
|
323
|
-
} = rects
|
316
|
+
} = rects ?? {};
|
324
317
|
if (!elementRect || !windowRect) throw new Error(`Couldn't find element with selector: '${captureElement}'`);
|
325
318
|
const isFitIntoViewport = elementRect.width + elementRect.left <= windowRect.width && elementRect.height + elementRect.top <= windowRect.height;
|
326
319
|
if (isFitIntoViewport) browserLogger.debug(`Capturing ${chalk.cyan(captureElement)}`);else browserLogger.debug(`Capturing composite screenshot image of ${chalk.cyan(captureElement)}`);
|
@@ -341,22 +334,19 @@ async function selectStory(browser, {
|
|
341
334
|
name
|
342
335
|
}, waitForReady = false) {
|
343
336
|
browserLogger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
344
|
-
const
|
337
|
+
const result = await browser.executeAsyncScript(function (id, kind, name, shouldWaitForReady, callback) {
|
345
338
|
if (typeof window.__CREEVEY_SELECT_STORY__ == 'undefined') {
|
346
|
-
return callback("Creevey can't switch story. This may happened if forget to add `creevey` addon to your storybook config, or storybook not loaded in browser due syntax error.");
|
339
|
+
return callback(["Creevey can't switch story. This may happened if forget to add `creevey` addon to your storybook config, or storybook not loaded in browser due syntax error."]);
|
347
340
|
}
|
348
341
|
|
349
|
-
window.__CREEVEY_SELECT_STORY__(id, kind, name, shouldWaitForReady, callback);
|
342
|
+
void window.__CREEVEY_SELECT_STORY__(id, kind, name, shouldWaitForReady, callback);
|
350
343
|
}, id, kind, name, waitForReady);
|
344
|
+
const [errorMessage, isCaptureCalled = false] = result ?? [];
|
351
345
|
if (errorMessage) throw new Error(errorMessage);
|
346
|
+
return isCaptureCalled;
|
352
347
|
}
|
353
348
|
|
354
349
|
export async function updateStorybookGlobals(browser, globals) {
|
355
|
-
if (isStorybookVersionLessThan(6)) {
|
356
|
-
browserLogger.warn('Globals are not supported by Storybook versions less than 6');
|
357
|
-
return;
|
358
|
-
}
|
359
|
-
|
360
350
|
browserLogger.debug('Applying storybook globals');
|
361
351
|
await browser.executeScript(function (globals) {
|
362
352
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
@@ -388,7 +378,44 @@ async function openStorybookPage(browser, storybookUrl, resolver) {
|
|
388
378
|
}
|
389
379
|
}
|
390
380
|
|
391
|
-
|
381
|
+
async function resolveCreeveyHost(browser, port) {
|
382
|
+
if (creeveyServerHost != null) return creeveyServerHost;
|
383
|
+
const addresses = getAddresses();
|
384
|
+
creeveyServerHost = await browser.executeAsyncScript(function (hosts, port, callback) {
|
385
|
+
void Promise.all(hosts.map(function (host) {
|
386
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
387
|
+
return fetch('http://' + host + ':' + port + '/ping').then(function (response) {
|
388
|
+
return response.text();
|
389
|
+
}).then(function (pong) {
|
390
|
+
return pong == 'pong' ? host : null;
|
391
|
+
}).catch(function () {
|
392
|
+
return null;
|
393
|
+
});
|
394
|
+
})).then(function (hosts) {
|
395
|
+
callback(hosts.find(function (host) {
|
396
|
+
return host != null;
|
397
|
+
}));
|
398
|
+
});
|
399
|
+
}, addresses, port);
|
400
|
+
if (creeveyServerHost == null) throw new Error("Can't reach creevey server from a browser");
|
401
|
+
return creeveyServerHost;
|
402
|
+
}
|
403
|
+
|
404
|
+
export async function loadStoriesFromBrowser(port) {
|
405
|
+
if (!browser) throw new Error("Can't get stories from browser if webdriver isn't connected");
|
406
|
+
const host = await resolveCreeveyHost(browser, port);
|
407
|
+
const stories = await browser.executeAsyncScript(function (creeveyHost, creeveyPort, callback) {
|
408
|
+
window.__CREEVEY_SERVER_HOST__ = creeveyHost;
|
409
|
+
window.__CREEVEY_SERVER_PORT__ = creeveyPort;
|
410
|
+
void window.__CREEVEY_GET_STORIES__().then(callback);
|
411
|
+
}, host, port);
|
412
|
+
if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
|
413
|
+
return stories;
|
414
|
+
}
|
415
|
+
export async function getBrowser(config, name) {
|
416
|
+
if (browser) return browser;
|
417
|
+
browserName = name;
|
418
|
+
const browserConfig = config.browsers[browserName];
|
392
419
|
const {
|
393
420
|
gridUrl = config.gridUrl,
|
394
421
|
storybookUrl: address = config.storybookUrl,
|
@@ -398,14 +425,11 @@ export async function getBrowser(config, browserConfig) {
|
|
398
425
|
...userCapabilities
|
399
426
|
} = browserConfig;
|
400
427
|
void limit;
|
401
|
-
const
|
402
|
-
browserName
|
403
|
-
} = userCapabilities;
|
404
|
-
const realAddress = address;
|
405
|
-
let browser = null; // TODO Define some capabilities explicitly and define typings
|
428
|
+
const realAddress = address; // TODO Define some capabilities explicitly and define typings
|
406
429
|
|
407
|
-
const capabilities = new Capabilities(userCapabilities
|
408
|
-
|
430
|
+
const capabilities = new Capabilities({ ...userCapabilities,
|
431
|
+
pageLoadStrategy: PageLoadStrategy.NONE
|
432
|
+
});
|
409
433
|
subscribeOn('shutdown', () => {
|
410
434
|
var _browser;
|
411
435
|
|
@@ -420,7 +444,7 @@ export async function getBrowser(config, browserConfig) {
|
|
420
444
|
const url = new URL(gridUrl);
|
421
445
|
url.username = url.username ? '********' : '';
|
422
446
|
url.password = url.password ? '********' : '';
|
423
|
-
browserLogger.debug(`(${
|
447
|
+
browserLogger.debug(`(${name}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
424
448
|
browser = await new Builder().usingServer(gridUrl).withCapabilities(capabilities).build();
|
425
449
|
const sessionId = (_await$browser$getSes = await browser.getSession()) === null || _await$browser$getSes === void 0 ? void 0 : _await$browser$getSes.getId();
|
426
450
|
let browserHost = '';
|
@@ -434,12 +458,12 @@ export async function getBrowser(config, browserConfig) {
|
|
434
458
|
/* noop */
|
435
459
|
}
|
436
460
|
|
437
|
-
browserLogger.debug(`(${
|
461
|
+
browserLogger.debug(`(${name}) Connected successful with ${[chalk.green(browserHost), chalk.magenta(sessionId)].filter(Boolean).join(':')}`);
|
438
462
|
browserLogger = getLogger(sessionId);
|
439
463
|
prefix.apply(browserLogger, {
|
440
464
|
format(level) {
|
441
465
|
const levelColor = colors[level.toUpperCase()];
|
442
|
-
return `[${
|
466
|
+
return `[${name}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
|
443
467
|
}
|
444
468
|
|
445
469
|
});
|
@@ -452,17 +476,18 @@ export async function getBrowser(config, browserConfig) {
|
|
452
476
|
});
|
453
477
|
}, () => viewport && browser && resizeViewport(browser, viewport), () => browser && openStorybookPage(browser, realAddress, config.resolveStorybookUrl), () => browser && waitForStorybook(browser)], () => !isShuttingDown.current);
|
454
478
|
} catch (originalError) {
|
455
|
-
var
|
479
|
+
var _browser4;
|
456
480
|
|
457
481
|
if (isShuttingDown.current) {
|
458
482
|
var _browser3;
|
459
483
|
|
460
484
|
(_browser3 = browser) === null || _browser3 === void 0 ? void 0 : _browser3.quit().catch(noop);
|
485
|
+
browser = null;
|
461
486
|
return null;
|
462
487
|
}
|
463
488
|
|
464
489
|
if (originalError instanceof Error && originalError.name == 'ResolveUrlError') throw originalError;
|
465
|
-
const error = new Error(`Can't load storybook root page by URL ${(
|
490
|
+
const error = new Error(`Can't load storybook root page by URL ${(await ((_browser4 = browser) === null || _browser4 === void 0 ? void 0 : _browser4.getCurrentUrl())) ?? realAddress}`);
|
466
491
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
467
492
|
throw error;
|
468
493
|
}
|
@@ -471,6 +496,9 @@ export async function getBrowser(config, browserConfig) {
|
|
471
496
|
await updateStorybookGlobals(browser, _storybookGlobals);
|
472
497
|
}
|
473
498
|
|
499
|
+
await browser.executeScript(function (workerId) {
|
500
|
+
window.__CREEVEY_WORKER_ID__ = workerId;
|
501
|
+
}, process.pid);
|
474
502
|
return browser;
|
475
503
|
}
|
476
504
|
|
@@ -486,12 +514,22 @@ async function updateStoryArgs(browser, story, updatedArgs) {
|
|
486
514
|
}, story.id, updatedArgs, Events.UPDATE_STORY_ARGS, Events.STORY_RENDERED);
|
487
515
|
}
|
488
516
|
|
517
|
+
export async function closeBrowser() {
|
518
|
+
if (!browser) return;
|
519
|
+
|
520
|
+
try {
|
521
|
+
await browser.quit();
|
522
|
+
} finally {
|
523
|
+
browser = null;
|
524
|
+
}
|
525
|
+
}
|
489
526
|
export async function switchStory() {
|
490
|
-
var _this$currentTest, _this$currentTest$ctx
|
527
|
+
var _this$currentTest, _this$currentTest$ctx;
|
491
528
|
|
492
529
|
let testOrSuite = this.currentTest;
|
493
530
|
if (!testOrSuite) throw new Error("Can't switch story, because test context doesn't have 'currentTest' field");
|
494
531
|
this.testScope.length = 0;
|
532
|
+
this.screenshots.length = 0;
|
495
533
|
this.testScope.push(this.browserName);
|
496
534
|
|
497
535
|
while ((_testOrSuite = testOrSuite) !== null && _testOrSuite !== void 0 && _testOrSuite.title) {
|
@@ -513,15 +551,8 @@ export async function switchStory() {
|
|
513
551
|
captureElement = '#root',
|
514
552
|
waitForReady,
|
515
553
|
ignoreElements
|
516
|
-
} =
|
554
|
+
} = parameters.creevey ?? {};
|
517
555
|
browserLogger.debug(`Switching to story ${chalk.cyan(kind)}/${chalk.cyan(name)} by id ${chalk.magenta(id)}`);
|
518
|
-
await resetMousePosition(this.browser);
|
519
|
-
await selectStory(this.browser, {
|
520
|
-
id,
|
521
|
-
kind,
|
522
|
-
name
|
523
|
-
}, waitForReady);
|
524
|
-
browserLogger.debug(`Story ${chalk.magenta(id)} ready for capturing`);
|
525
556
|
if (captureElement) Object.defineProperty(this, 'captureElement', {
|
526
557
|
enumerable: true,
|
527
558
|
configurable: true,
|
@@ -533,6 +564,44 @@ export async function switchStory() {
|
|
533
564
|
this.updateStoryArgs = updatedArgs => updateStoryArgs(this.browser, story, updatedArgs);
|
534
565
|
|
535
566
|
this.testScope.reverse();
|
567
|
+
let storyPlayResolver;
|
568
|
+
let waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
|
569
|
+
const unsubscribe = subscribeOn('stories', message => {
|
570
|
+
if (message.type != 'capture') return;
|
571
|
+
const {
|
572
|
+
payload = {},
|
573
|
+
payload: {
|
574
|
+
imageName
|
575
|
+
} = {}
|
576
|
+
} = message;
|
577
|
+
void takeScreenshot(this.browser, payload.captureElement ?? captureElement, payload.ignoreElements ?? ignoreElements).then(screenshot => {
|
578
|
+
this.screenshots.push({
|
579
|
+
imageName,
|
580
|
+
screenshot
|
581
|
+
});
|
582
|
+
void this.browser.executeAsyncScript(function (callback) {
|
583
|
+
window.__CREEVEY_HAS_PLAY_COMPLETED_YET__(callback);
|
584
|
+
}).then(isCompleted => storyPlayResolver(isCompleted));
|
585
|
+
emitStoriesMessage({
|
586
|
+
type: 'capture'
|
587
|
+
});
|
588
|
+
});
|
589
|
+
});
|
590
|
+
await resetMousePosition(this.browser);
|
591
|
+
const isCaptureCalled = await selectStory(this.browser, {
|
592
|
+
id,
|
593
|
+
kind,
|
594
|
+
name
|
595
|
+
}, waitForReady);
|
596
|
+
|
597
|
+
if (isCaptureCalled) {
|
598
|
+
while (!(await waitForComplete)) {
|
599
|
+
waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
|
600
|
+
}
|
601
|
+
}
|
602
|
+
|
603
|
+
unsubscribe();
|
604
|
+
browserLogger.debug(`Story ${chalk.magenta(id)} ready for capturing`);
|
536
605
|
}
|
537
606
|
|
538
607
|
async function insertIgnoreStyles(browser, ignoreElements) {
|
@@ -5,7 +5,7 @@ import { downloadBinary, getCreeveyCache } from '../utils';
|
|
5
5
|
import { pullImages, runImage } from '../docker';
|
6
6
|
import { Octokit } from '@octokit/core';
|
7
7
|
import { subscribeOn } from '../messages';
|
8
|
-
import
|
8
|
+
import cluster from 'cluster';
|
9
9
|
import { chmod, exec } from 'shelljs';
|
10
10
|
const mkdirAsync = promisify(mkdir);
|
11
11
|
const writeFileAsync = promisify(writeFile);
|
@@ -41,8 +41,6 @@ async function createSelenoidConfig(browsers, {
|
|
41
41
|
}
|
42
42
|
|
43
43
|
async function downloadSelenoidBinary(destination) {
|
44
|
-
var _assets$find;
|
45
|
-
|
46
44
|
const platformNameMapping = {
|
47
45
|
darwin: 'selenoid_darwin_amd64',
|
48
46
|
linux: 'selenoid_linux_amd64',
|
@@ -59,9 +57,9 @@ async function downloadSelenoidBinary(destination) {
|
|
59
57
|
const {
|
60
58
|
browser_download_url: downloadUrl,
|
61
59
|
size: binarySize
|
62
|
-
} =
|
60
|
+
} = assets.find(({
|
63
61
|
name
|
64
|
-
}) => platformNameMapping[process.platform] == name)
|
62
|
+
}) => platformNameMapping[process.platform] == name) ?? {};
|
65
63
|
if (existsSync(destination) && lstatSync(destination).size == binarySize) return;
|
66
64
|
|
67
65
|
if (!downloadUrl) {
|
@@ -73,7 +71,7 @@ async function downloadSelenoidBinary(destination) {
|
|
73
71
|
|
74
72
|
export async function startSelenoidStandalone(config, debug) {
|
75
73
|
config.gridUrl = 'http://localhost:4444/wd/hub';
|
76
|
-
if (isWorker) return;
|
74
|
+
if (cluster.isWorker) return;
|
77
75
|
const browsers = Object.values(config.browsers).filter(browser => !browser.gridUrl);
|
78
76
|
const selenoidConfigDir = await createSelenoidConfig(browsers, {
|
79
77
|
useDocker: false
|
@@ -4,15 +4,16 @@ import { createHash } from 'crypto';
|
|
4
4
|
import { mapValues, pick } from 'lodash';
|
5
5
|
import { isDefined, isFunction, isObject } from '../types';
|
6
6
|
import { shouldSkip, removeProps } from './utils';
|
7
|
-
import { isStorybookVersionGreaterThan, isStorybookVersionLessThan } from './storybook/helpers';
|
8
|
-
import { denormalizeStoryParameters } from '../shared';
|
9
7
|
|
10
8
|
function storyTestFabric(delay, testFn) {
|
11
9
|
return async function storyTest() {
|
12
|
-
var _testFn$call;
|
13
|
-
|
14
10
|
delay ? await new Promise(resolve => setTimeout(resolve, delay)) : void 0;
|
15
|
-
await (
|
11
|
+
await (testFn ? testFn.call(this) : this.screenshots.length > 0 ? this.expect(this.screenshots.reduce((screenshots, {
|
12
|
+
imageName,
|
13
|
+
screenshot
|
14
|
+
}, index) => ({ ...screenshots,
|
15
|
+
[imageName ?? `screenshot_${index}`]: screenshot
|
16
|
+
}), {})).to.matchImages() : this.expect(await this.takeScreenshot()).to.matchImage());
|
16
17
|
};
|
17
18
|
}
|
18
19
|
|
@@ -38,42 +39,38 @@ function createCreeveyTest(browser, storyMeta, skipOptions, testName) {
|
|
38
39
|
};
|
39
40
|
}
|
40
41
|
|
41
|
-
function convertStories(
|
42
|
+
function convertStories(browserName, stories) {
|
42
43
|
const tests = {};
|
43
44
|
(Array.isArray(stories) ? stories : Object.values(stories)).forEach(storyMeta => {
|
44
45
|
// TODO Skip docsOnly stories for now
|
45
46
|
if (storyMeta.parameters.docsOnly) return;
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
};
|
66
|
-
return;
|
67
|
-
}
|
47
|
+
const {
|
48
|
+
delay: delayParam,
|
49
|
+
tests: storyTests,
|
50
|
+
skip
|
51
|
+
} = storyMeta.parameters.creevey ?? {};
|
52
|
+
const delay = typeof delayParam == 'number' ? delayParam : delayParam !== null && delayParam !== void 0 && delayParam.for.includes(browserName) ? delayParam.ms : 0; // typeof tests === "undefined" => rootSuite -> kindSuite -> storyTest -> [browsers.png]
|
53
|
+
// typeof tests === "function" => rootSuite -> kindSuite -> storyTest -> browser -> [images.png]
|
54
|
+
// typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> [browsers.png]
|
55
|
+
// typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> browser -> [images.png]
|
56
|
+
|
57
|
+
if (!storyTests) {
|
58
|
+
const test = createCreeveyTest(browserName, storyMeta, skip);
|
59
|
+
tests[test.id] = { ...test,
|
60
|
+
storyId: storyMeta.id,
|
61
|
+
story: storyMeta,
|
62
|
+
fn: storyTestFabric(delay)
|
63
|
+
};
|
64
|
+
return;
|
65
|
+
}
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
});
|
67
|
+
Object.entries(storyTests).forEach(([testName, testFn]) => {
|
68
|
+
const test = createCreeveyTest(browserName, storyMeta, skip, testName);
|
69
|
+
tests[test.id] = { ...test,
|
70
|
+
storyId: storyMeta.id,
|
71
|
+
story: storyMeta,
|
72
|
+
fn: storyTestFabric(delay, testFn)
|
73
|
+
};
|
77
74
|
});
|
78
75
|
});
|
79
76
|
return tests;
|
@@ -81,22 +78,24 @@ function convertStories(browsers, stories) {
|
|
81
78
|
|
82
79
|
export async function loadTestsFromStories(browsers, provider, update) {
|
83
80
|
const testIdsByFiles = new Map();
|
84
|
-
const
|
81
|
+
const stories = await provider(storiesByFiles => {
|
85
82
|
const testsDiff = {};
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
83
|
+
const tests = {};
|
84
|
+
browsers.forEach(browser => {
|
85
|
+
Array.from(storiesByFiles.entries()).forEach(([filename, stories]) => {
|
86
|
+
var _testIdsByFiles$get;
|
87
|
+
|
88
|
+
Object.assign(tests, convertStories(browser, stories));
|
89
|
+
const changed = Object.keys(tests);
|
90
|
+
const removed = ((_testIdsByFiles$get = testIdsByFiles.get(filename)) === null || _testIdsByFiles$get === void 0 ? void 0 : _testIdsByFiles$get.filter(testId => !tests[testId])) ?? [];
|
91
|
+
if (changed.length == 0) testIdsByFiles.delete(filename);else testIdsByFiles.set(filename, changed);
|
92
|
+
Object.assign(testsDiff, tests);
|
93
|
+
removed.forEach(testId => testsDiff[testId] = undefined);
|
94
|
+
});
|
95
95
|
});
|
96
96
|
update === null || update === void 0 ? void 0 : update(testsDiff);
|
97
97
|
});
|
98
|
-
const
|
99
|
-
const tests = convertStories(browsers, stories);
|
98
|
+
const tests = browsers.reduce((tests, browser) => Object.assign(tests, convertStories(browser, stories)), {});
|
100
99
|
Object.values(tests).filter(isDefined).forEach(({
|
101
100
|
id,
|
102
101
|
story: {
|
@@ -104,35 +103,24 @@ export async function loadTestsFromStories(browsers, provider, update) {
|
|
104
103
|
fileName
|
105
104
|
}
|
106
105
|
}
|
107
|
-
}) =>
|
108
|
-
|
109
|
-
|
110
|
-
return (// TODO Don't use filename as a key, due possible collisions if two require.context with same structure of modules are defined
|
111
|
-
testIdsByFiles.set(fileName, [...((_testIdsByFiles$get2 = testIdsByFiles.get(fileName)) !== null && _testIdsByFiles$get2 !== void 0 ? _testIdsByFiles$get2 : []), id])
|
112
|
-
);
|
113
|
-
});
|
106
|
+
}) => // TODO Don't use filename as a key, due possible collisions if two require.context with same structure of modules are defined
|
107
|
+
testIdsByFiles.set(fileName, [...(testIdsByFiles.get(fileName) ?? []), id]));
|
114
108
|
return tests;
|
115
109
|
}
|
116
110
|
export function saveStoriesJson(storiesData, extract) {
|
117
|
-
|
118
|
-
|
119
|
-
const outputDir = typeof extract == 'boolean' ? 'storybook-static' : extract;
|
120
|
-
|
121
|
-
if (!isStorybookVersionLessThan(6)) {
|
122
|
-
// NOTE Copy-pasted from Storybook's `getStoriesJsonData` method
|
123
|
-
const allowed = ['fileName', 'docsOnly', 'framework', '__id', '__isArgsStory'];
|
124
|
-
storiesData.globalParameters = pick(storiesData.globalParameters, allowed); // @ts-expect-error ignore error
|
111
|
+
const outputDir = typeof extract == 'boolean' ? 'storybook-static' : extract; // NOTE Copy-pasted from Storybook's `getStoriesJsonData` method
|
125
112
|
|
126
|
-
|
113
|
+
const allowed = ['fileName', 'docsOnly', 'framework', '__id', '__isArgsStory'];
|
114
|
+
storiesData.globalParameters = pick(storiesData.globalParameters, allowed); // @ts-expect-error ignore error
|
127
115
|
|
128
|
-
|
129
|
-
parameters: pick(v.parameters, allowed)
|
130
|
-
}));
|
131
|
-
} // TODO Fix args stories
|
116
|
+
storiesData.kindParameters = mapValues(storiesData.kindParameters, v => pick(v, allowed)); // @ts-expect-error ignore error
|
132
117
|
|
118
|
+
storiesData.stories = mapValues(storiesData.stories, v => ({ ...pick(v, ['id', 'name', 'kind', 'story']),
|
119
|
+
parameters: pick(v.parameters, allowed)
|
120
|
+
})); // TODO Fix args stories
|
133
121
|
|
134
|
-
removeProps(storiesData
|
135
|
-
Object.values((
|
122
|
+
removeProps(storiesData ?? {}, ['stories', () => true, 'parameters', '__isArgsStory']);
|
123
|
+
Object.values((storiesData === null || storiesData === void 0 ? void 0 : storiesData.stories) ?? {}).forEach(story => isObject(story) && 'parameters' in story && isObject(story.parameters) && delete story.parameters.__isArgsStory);
|
136
124
|
mkdirSync(outputDir, {
|
137
125
|
recursive: true
|
138
126
|
});
|
@@ -1,16 +1,12 @@
|
|
1
|
-
var _api$channel, _api$context;
|
2
|
-
|
3
1
|
import { addons } from '@storybook/addons';
|
4
|
-
import { getStorybookFramework,
|
2
|
+
import { getStorybookFramework, resolveFromStorybook } from './helpers';
|
5
3
|
const framework = getStorybookFramework(); // eslint-disable-next-line @typescript-eslint/no-var-requires
|
6
4
|
|
7
|
-
const core = require(resolveFromStorybook('@storybook/core'));
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
9
|
-
|
5
|
+
const core = require(resolveFromStorybook('@storybook/core-client'));
|
10
6
|
|
11
|
-
const start =
|
7
|
+
const start = core.start;
|
12
8
|
const api = start(() => void 0);
|
13
|
-
export const channel =
|
9
|
+
export const channel = addons.getChannel();
|
14
10
|
export const clientApi = api.clientApi;
|
15
11
|
export const forceReRender = api.forceReRender;
|
16
12
|
export const storiesOf = (kind, m) => {
|
@@ -19,21 +15,8 @@ export const storiesOf = (kind, m) => {
|
|
19
15
|
});
|
20
16
|
};
|
21
17
|
export const configure = (...args) => {
|
22
|
-
|
23
|
-
//NOTE: Storybook <= 5.1 pass args as is
|
24
|
-
//@ts-expect-error: ignore it
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
26
|
-
return api.configApi.configure(...args);
|
27
|
-
}
|
28
|
-
|
29
|
-
if (isStorybookVersionLessThan(6)) {
|
30
|
-
//NOTE: Storybook <= 5.3 pass `framework` as last argument
|
31
|
-
//@ts-expect-error: ignore it
|
32
|
-
return api.configure(...args, framework);
|
33
|
-
} //NOTE Storybook 6.x pass `framework` as first argument
|
18
|
+
//NOTE Storybook 6.x pass `framework` as first argument
|
34
19
|
//@ts-expect-error: ignore it
|
35
|
-
|
36
|
-
|
37
20
|
return api.configure(framework, ...args);
|
38
21
|
};
|
39
22
|
export const addDecorator = clientApi.addDecorator;
|