creevey 0.8.0-beta.0 → 0.9.0-beta.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 +5 -9
- package/lib/cjs/client/addon/Manager.js +0 -1
- package/lib/cjs/client/addon/preset.js +1 -0
- package/lib/cjs/client/addon/readyForCapture.js +12 -0
- package/lib/cjs/client/addon/withCreevey.js +313 -41
- package/lib/cjs/client/shared/components/ImagesView/BlendView.js +3 -3
- package/lib/cjs/client/shared/components/ImagesView/SideBySideView.js +3 -3
- package/lib/cjs/client/shared/components/ImagesView/SlideView.js +4 -3
- package/lib/cjs/client/shared/components/ImagesView/SwapView.js +3 -3
- package/lib/cjs/client/shared/helpers.js +1 -1
- package/lib/cjs/client/web/main.js +6 -6
- package/lib/cjs/index.js +27 -9
- package/lib/cjs/server/config.js +7 -3
- package/lib/cjs/server/extract.js +11 -4
- package/lib/cjs/server/index.js +2 -4
- package/lib/cjs/server/master/index.js +3 -9
- package/lib/cjs/server/master/master.js +1 -0
- package/lib/cjs/server/master/pool.js +29 -29
- package/lib/cjs/server/master/server.js +75 -3
- package/lib/cjs/server/messages.js +124 -12
- package/lib/cjs/server/parser.js +85 -0
- package/lib/cjs/server/selenium/browser.js +119 -21
- package/lib/cjs/server/stories.js +49 -46
- package/lib/cjs/server/storybook/providers/browser.js +78 -0
- package/lib/cjs/server/storybook/providers/hybrid.js +79 -0
- package/lib/cjs/server/storybook/{nodejs-provider.js → providers/nodejs.js} +32 -13
- package/lib/cjs/server/utils.js +7 -0
- package/lib/cjs/server/worker/helpers.js +2 -6
- package/lib/cjs/server/worker/worker.js +15 -3
- package/lib/cjs/shared.js +78 -6
- package/lib/cjs/types.js +5 -0
- package/lib/esm/client/addon/Manager.js +0 -1
- package/lib/esm/client/addon/preset.js +1 -0
- package/lib/esm/client/addon/readyForCapture.js +5 -0
- package/lib/esm/client/addon/withCreevey.js +303 -41
- package/lib/esm/client/shared/components/ImagesView/BlendView.js +2 -3
- package/lib/esm/client/shared/components/ImagesView/SideBySideView.js +2 -3
- package/lib/esm/client/shared/components/ImagesView/SlideView.js +3 -3
- package/lib/esm/client/shared/components/ImagesView/SwapView.js +2 -3
- package/lib/esm/client/shared/helpers.js +1 -1
- package/lib/esm/index.js +6 -3
- package/lib/esm/server/config.js +7 -5
- package/lib/esm/server/extract.js +8 -4
- package/lib/esm/server/index.js +2 -3
- package/lib/esm/server/master/index.js +4 -10
- package/lib/esm/server/master/master.js +1 -0
- package/lib/esm/server/master/pool.js +31 -31
- package/lib/esm/server/master/server.js +73 -5
- package/lib/esm/server/messages.js +118 -12
- package/lib/esm/server/parser.js +63 -0
- package/lib/esm/server/selenium/browser.js +116 -23
- package/lib/esm/server/stories.js +50 -46
- package/lib/esm/server/storybook/providers/browser.js +61 -0
- package/lib/esm/server/storybook/providers/hybrid.js +63 -0
- package/lib/esm/server/storybook/{nodejs-provider.js → providers/nodejs.js} +30 -13
- package/lib/esm/server/utils.js +6 -1
- package/lib/esm/server/worker/helpers.js +2 -6
- package/lib/esm/server/worker/worker.js +16 -4
- package/lib/esm/shared.js +59 -5
- package/lib/esm/types.js +3 -0
- package/lib/types/cli.d.ts +1 -1
- package/lib/types/client/addon/Manager.d.ts +37 -37
- package/lib/types/client/addon/components/Addon.d.ts +8 -8
- package/lib/types/client/addon/components/Icons.d.ts +7 -7
- package/lib/types/client/addon/components/Panel.d.ts +9 -9
- package/lib/types/client/addon/components/TestSelect.d.ts +9 -9
- package/lib/types/client/addon/components/Tools.d.ts +6 -6
- package/lib/types/client/addon/decorator.d.ts +1 -1
- package/lib/types/client/addon/preset.d.ts +24 -22
- package/lib/types/client/addon/readyForCapture.d.ts +6 -0
- package/lib/types/client/addon/register.d.ts +3 -3
- package/lib/types/client/addon/utils.d.ts +2 -2
- package/lib/types/client/addon/withCreevey.d.ts +24 -13
- package/lib/types/client/shared/components/ImagesView/BlendView.d.ts +3 -3
- package/lib/types/client/shared/components/ImagesView/ImagesView.d.ts +25 -25
- package/lib/types/client/shared/components/ImagesView/SideBySideView.d.ts +3 -3
- package/lib/types/client/shared/components/ImagesView/SlideView.d.ts +3 -3
- package/lib/types/client/shared/components/ImagesView/SwapView.d.ts +3 -3
- package/lib/types/client/shared/components/ImagesView/index.d.ts +5 -5
- package/lib/types/client/shared/components/PageFooter/PageFooter.d.ts +9 -9
- package/lib/types/client/shared/components/PageFooter/Paging.d.ts +8 -8
- package/lib/types/client/shared/components/PageHeader/ImagePreview.d.ts +12 -12
- package/lib/types/client/shared/components/PageHeader/PageHeader.d.ts +17 -17
- package/lib/types/client/shared/components/ResultsPage.d.ts +18 -18
- package/lib/types/client/shared/creeveyClientApi.d.ts +9 -9
- package/lib/types/client/shared/helpers.d.ts +46 -46
- package/lib/types/client/shared/viewMode.d.ts +4 -4
- package/lib/types/client/web/CreeveyApp.d.ts +12 -12
- package/lib/types/client/web/CreeveyContext.d.ts +11 -11
- package/lib/types/client/web/CreeveyLoader.d.ts +3 -3
- package/lib/types/client/web/CreeveyView/SideBar/Checkbox.d.ts +19 -19
- package/lib/types/client/web/CreeveyView/SideBar/Search.d.ts +6 -6
- package/lib/types/client/web/CreeveyView/SideBar/SideBar.d.ts +14 -14
- package/lib/types/client/web/CreeveyView/SideBar/SideBarHeader.d.ts +13 -13
- package/lib/types/client/web/CreeveyView/SideBar/SuiteLink.d.ts +33 -33
- package/lib/types/client/web/CreeveyView/SideBar/TestLink.d.ts +8 -8
- package/lib/types/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +10 -10
- package/lib/types/client/web/CreeveyView/SideBar/TestsStatus.d.ts +9 -9
- package/lib/types/client/web/CreeveyView/SideBar/Toggle.d.ts +6 -6
- package/lib/types/client/web/CreeveyView/SideBar/index.d.ts +1 -1
- package/lib/types/client/web/KeyboardEventsContext.d.ts +13 -13
- package/lib/types/client/web/index.d.ts +4 -4
- package/lib/types/creevey.d.ts +1 -1
- package/lib/types/index.d.ts +2 -1
- package/lib/types/server/config.d.ts +4 -4
- package/lib/types/server/docker.d.ts +7 -7
- package/lib/types/server/extract.d.ts +2 -2
- package/lib/types/server/index.d.ts +2 -2
- package/lib/types/server/loaders/babel/creevey-plugin.d.ts +1 -1
- package/lib/types/server/loaders/babel/helpers.d.ts +19 -19
- package/lib/types/server/loaders/babel/register.d.ts +5 -5
- package/lib/types/server/loaders/hooks/mdx.d.ts +1 -1
- package/lib/types/server/loaders/hooks/svelte.d.ts +1 -1
- package/lib/types/server/loaders/webpack/compile.d.ts +2 -2
- package/lib/types/server/loaders/webpack/creevey-loader.d.ts +2 -2
- package/lib/types/server/loaders/webpack/dummy-hmr.d.ts +10 -10
- package/lib/types/server/loaders/webpack/mdx-loader.d.ts +6 -6
- package/lib/types/server/loaders/webpack/start.d.ts +1 -1
- package/lib/types/server/logger.d.ts +6 -6
- package/lib/types/server/master/api.d.ts +7 -7
- package/lib/types/server/master/index.d.ts +3 -3
- package/lib/types/server/master/master.d.ts +7 -6
- package/lib/types/server/master/pool.d.ts +31 -30
- package/lib/types/server/master/runner.d.ts +26 -26
- package/lib/types/server/master/server.d.ts +2 -2
- package/lib/types/server/messages.d.ts +28 -18
- package/lib/types/server/parser.d.ts +12 -0
- package/lib/types/server/selenium/browser.d.ts +17 -14
- package/lib/types/server/selenium/index.d.ts +2 -2
- package/lib/types/server/selenium/selenoid.d.ts +3 -3
- package/lib/types/server/stories.d.ts +8 -8
- package/lib/types/server/storybook/entry.d.ts +18 -18
- package/lib/types/server/storybook/helpers.d.ts +24 -24
- package/lib/types/server/storybook/providers/browser.d.ts +4 -0
- package/lib/types/server/storybook/providers/hybrid.d.ts +4 -0
- package/lib/types/server/storybook/providers/nodejs.d.ts +9 -0
- package/lib/types/server/update.d.ts +2 -2
- package/lib/types/server/utils.d.ts +20 -19
- package/lib/types/server/worker/chai-image.d.ts +6 -6
- package/lib/types/server/worker/helpers.d.ts +8 -7
- package/lib/types/server/worker/index.d.ts +1 -1
- package/lib/types/server/worker/reporter.d.ts +8 -8
- package/lib/types/server/worker/worker.d.ts +4 -4
- package/lib/types/shared.d.ts +16 -4
- package/lib/types/types.d.ts +488 -459
- package/package.json +12 -6
- package/storybook-static/stories.json +21 -0
- package/types/mocha.d.ts +1 -0
- package/lib/types/server/storybook/nodejs-provider.d.ts +0 -5
@@ -9,11 +9,14 @@ 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';
|
12
|
+
import { emitStoriesMessage, subscribeOn } from '../messages';
|
13
13
|
import { importStorybookCoreEvents, isStorybookVersionLessThan } 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);
|
@@ -40,9 +43,13 @@ function getSessionData(grid, sessionId = '') {
|
|
40
43
|
}));
|
41
44
|
}
|
42
45
|
|
46
|
+
function getAddresses() {
|
47
|
+
return [DOCKER_INTERNAL].concat(...Object.values(networkInterfaces()).filter(isDefined).map(network => network.filter(info => info.family == 'IPv4').map(info => info.address)));
|
48
|
+
}
|
49
|
+
|
43
50
|
async function resolveStorybookUrl(storybookUrl, checkUrl) {
|
44
51
|
browserLogger.debug('Resolving storybook url');
|
45
|
-
const addresses =
|
52
|
+
const addresses = getAddresses();
|
46
53
|
|
47
54
|
for (const ip of addresses) {
|
48
55
|
const resolvedUrl = storybookUrl.replace(LOCALHOST_REGEXP, ip);
|
@@ -283,7 +290,7 @@ async function takeCompositeScreenshot(browser, windowRect, elementRect) {
|
|
283
290
|
return PNG.sync.write(compositeImage).toString('base64');
|
284
291
|
}
|
285
292
|
|
286
|
-
async function takeScreenshot(browser, captureElement, ignoreElements) {
|
293
|
+
export async function takeScreenshot(browser, captureElement, ignoreElements) {
|
287
294
|
let screenshot;
|
288
295
|
const ignoreStyles = await insertIgnoreStyles(browser, ignoreElements);
|
289
296
|
|
@@ -341,14 +348,16 @@ async function selectStory(browser, {
|
|
341
348
|
name
|
342
349
|
}, waitForReady = false) {
|
343
350
|
browserLogger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
|
344
|
-
const
|
351
|
+
const result = await browser.executeAsyncScript(function (id, kind, name, shouldWaitForReady, callback) {
|
345
352
|
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.");
|
353
|
+
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
354
|
}
|
348
355
|
|
349
356
|
window.__CREEVEY_SELECT_STORY__(id, kind, name, shouldWaitForReady, callback);
|
350
357
|
}, id, kind, name, waitForReady);
|
358
|
+
const [errorMessage, isCaptureCalled = false] = result || [];
|
351
359
|
if (errorMessage) throw new Error(errorMessage);
|
360
|
+
return isCaptureCalled;
|
352
361
|
}
|
353
362
|
|
354
363
|
export async function updateStorybookGlobals(browser, globals) {
|
@@ -388,7 +397,47 @@ async function openStorybookPage(browser, storybookUrl, resolver) {
|
|
388
397
|
}
|
389
398
|
}
|
390
399
|
|
391
|
-
|
400
|
+
async function resolveCreeveyHost(browser, port) {
|
401
|
+
if (creeveyServerHost != null) return creeveyServerHost;
|
402
|
+
const addresses = getAddresses();
|
403
|
+
creeveyServerHost = await browser.executeAsyncScript(function (hosts, port, callback) {
|
404
|
+
void Promise.all(hosts.map(function (host) {
|
405
|
+
return new Promise(function (resolve, reject) {
|
406
|
+
setTimeout(reject, 10000); // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
407
|
+
|
408
|
+
fetch('http://' + host + ':' + port + '/ping').then(resolve).catch(reject);
|
409
|
+
}).then(function (response) {
|
410
|
+
return response.text();
|
411
|
+
}).then(function (pong) {
|
412
|
+
return pong == 'pong' ? host : null;
|
413
|
+
}).catch(function () {
|
414
|
+
return null;
|
415
|
+
});
|
416
|
+
})).then(function (hosts) {
|
417
|
+
callback(hosts.find(function (host) {
|
418
|
+
return host != null;
|
419
|
+
}));
|
420
|
+
});
|
421
|
+
}, addresses, port);
|
422
|
+
if (creeveyServerHost == null) throw new Error("Can't reach creevey server from a browser");
|
423
|
+
return creeveyServerHost;
|
424
|
+
}
|
425
|
+
|
426
|
+
export async function loadStoriesFromBrowser(port) {
|
427
|
+
if (!browser) throw new Error("Can't get stories from browser if webdriver isn't connected");
|
428
|
+
const host = await resolveCreeveyHost(browser, port);
|
429
|
+
const stories = await browser.executeAsyncScript(function (creeveyHost, creeveyPort, callback) {
|
430
|
+
window.__CREEVEY_SERVER_HOST__ = creeveyHost;
|
431
|
+
window.__CREEVEY_SERVER_PORT__ = creeveyPort;
|
432
|
+
void window.__CREEVEY_GET_STORIES__().then(callback);
|
433
|
+
}, host, port);
|
434
|
+
if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
|
435
|
+
return stories;
|
436
|
+
}
|
437
|
+
export async function getBrowser(config, name) {
|
438
|
+
if (browser) return browser;
|
439
|
+
browserName = name;
|
440
|
+
const browserConfig = config.browsers[browserName];
|
392
441
|
const {
|
393
442
|
gridUrl = config.gridUrl,
|
394
443
|
storybookUrl: address = config.storybookUrl,
|
@@ -398,14 +447,11 @@ export async function getBrowser(config, browserConfig) {
|
|
398
447
|
...userCapabilities
|
399
448
|
} = browserConfig;
|
400
449
|
void limit;
|
401
|
-
const
|
402
|
-
browserName
|
403
|
-
} = userCapabilities;
|
404
|
-
const realAddress = address;
|
405
|
-
let browser = null; // TODO Define some capabilities explicitly and define typings
|
450
|
+
const realAddress = address; // TODO Define some capabilities explicitly and define typings
|
406
451
|
|
407
|
-
const capabilities = new Capabilities(userCapabilities
|
408
|
-
|
452
|
+
const capabilities = new Capabilities({ ...userCapabilities,
|
453
|
+
pageLoadStrategy: PageLoadStrategy.NONE
|
454
|
+
});
|
409
455
|
subscribeOn('shutdown', () => {
|
410
456
|
var _browser;
|
411
457
|
|
@@ -420,7 +466,7 @@ export async function getBrowser(config, browserConfig) {
|
|
420
466
|
const url = new URL(gridUrl);
|
421
467
|
url.username = url.username ? '********' : '';
|
422
468
|
url.password = url.password ? '********' : '';
|
423
|
-
browserLogger.debug(`(${
|
469
|
+
browserLogger.debug(`(${name}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
424
470
|
browser = await new Builder().usingServer(gridUrl).withCapabilities(capabilities).build();
|
425
471
|
const sessionId = (_await$browser$getSes = await browser.getSession()) === null || _await$browser$getSes === void 0 ? void 0 : _await$browser$getSes.getId();
|
426
472
|
let browserHost = '';
|
@@ -434,12 +480,12 @@ export async function getBrowser(config, browserConfig) {
|
|
434
480
|
/* noop */
|
435
481
|
}
|
436
482
|
|
437
|
-
browserLogger.debug(`(${
|
483
|
+
browserLogger.debug(`(${name}) Connected successful with ${[chalk.green(browserHost), chalk.magenta(sessionId)].filter(Boolean).join(':')}`);
|
438
484
|
browserLogger = getLogger(sessionId);
|
439
485
|
prefix.apply(browserLogger, {
|
440
486
|
format(level) {
|
441
487
|
const levelColor = colors[level.toUpperCase()];
|
442
|
-
return `[${
|
488
|
+
return `[${name}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
|
443
489
|
}
|
444
490
|
|
445
491
|
});
|
@@ -458,6 +504,7 @@ export async function getBrowser(config, browserConfig) {
|
|
458
504
|
var _browser3;
|
459
505
|
|
460
506
|
(_browser3 = browser) === null || _browser3 === void 0 ? void 0 : _browser3.quit().catch(noop);
|
507
|
+
browser = null;
|
461
508
|
return null;
|
462
509
|
}
|
463
510
|
|
@@ -471,6 +518,9 @@ export async function getBrowser(config, browserConfig) {
|
|
471
518
|
await updateStorybookGlobals(browser, _storybookGlobals);
|
472
519
|
}
|
473
520
|
|
521
|
+
await browser.executeScript(function (workerId) {
|
522
|
+
window.__CREEVEY_WORKER_ID__ = workerId;
|
523
|
+
}, process.pid);
|
474
524
|
return browser;
|
475
525
|
}
|
476
526
|
|
@@ -486,12 +536,22 @@ async function updateStoryArgs(browser, story, updatedArgs) {
|
|
486
536
|
}, story.id, updatedArgs, Events.UPDATE_STORY_ARGS, Events.STORY_RENDERED);
|
487
537
|
}
|
488
538
|
|
539
|
+
export async function closeBrowser() {
|
540
|
+
if (!browser) return;
|
541
|
+
|
542
|
+
try {
|
543
|
+
await browser.quit();
|
544
|
+
} finally {
|
545
|
+
browser = null;
|
546
|
+
}
|
547
|
+
}
|
489
548
|
export async function switchStory() {
|
490
549
|
var _this$currentTest, _this$currentTest$ctx, _parameters$creevey;
|
491
550
|
|
492
551
|
let testOrSuite = this.currentTest;
|
493
552
|
if (!testOrSuite) throw new Error("Can't switch story, because test context doesn't have 'currentTest' field");
|
494
553
|
this.testScope.length = 0;
|
554
|
+
this.screenshots.length = 0;
|
495
555
|
this.testScope.push(this.browserName);
|
496
556
|
|
497
557
|
while ((_testOrSuite = testOrSuite) !== null && _testOrSuite !== void 0 && _testOrSuite.title) {
|
@@ -515,13 +575,6 @@ export async function switchStory() {
|
|
515
575
|
ignoreElements
|
516
576
|
} = (_parameters$creevey = parameters.creevey) !== null && _parameters$creevey !== void 0 ? _parameters$creevey : {};
|
517
577
|
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
578
|
if (captureElement) Object.defineProperty(this, 'captureElement', {
|
526
579
|
enumerable: true,
|
527
580
|
configurable: true,
|
@@ -533,6 +586,46 @@ export async function switchStory() {
|
|
533
586
|
this.updateStoryArgs = updatedArgs => updateStoryArgs(this.browser, story, updatedArgs);
|
534
587
|
|
535
588
|
this.testScope.reverse();
|
589
|
+
let storyPlayResolver;
|
590
|
+
let waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
|
591
|
+
const unsubscribe = subscribeOn('stories', message => {
|
592
|
+
var _payload$captureEleme, _payload$ignoreElemen;
|
593
|
+
|
594
|
+
if (message.type != 'capture') return;
|
595
|
+
const {
|
596
|
+
payload = {},
|
597
|
+
payload: {
|
598
|
+
imageName
|
599
|
+
} = {}
|
600
|
+
} = message;
|
601
|
+
void takeScreenshot(this.browser, (_payload$captureEleme = payload.captureElement) !== null && _payload$captureEleme !== void 0 ? _payload$captureEleme : captureElement, (_payload$ignoreElemen = payload.ignoreElements) !== null && _payload$ignoreElemen !== void 0 ? _payload$ignoreElemen : ignoreElements).then(screenshot => {
|
602
|
+
this.screenshots.push({
|
603
|
+
imageName,
|
604
|
+
screenshot
|
605
|
+
});
|
606
|
+
void this.browser.executeAsyncScript(function (callback) {
|
607
|
+
window.__CREEVEY_HAS_PLAY_COMPLETED_YET__(callback);
|
608
|
+
}).then(isCompleted => storyPlayResolver(isCompleted));
|
609
|
+
emitStoriesMessage({
|
610
|
+
type: 'capture'
|
611
|
+
});
|
612
|
+
});
|
613
|
+
});
|
614
|
+
await resetMousePosition(this.browser);
|
615
|
+
const isCaptureCalled = await selectStory(this.browser, {
|
616
|
+
id,
|
617
|
+
kind,
|
618
|
+
name
|
619
|
+
}, waitForReady);
|
620
|
+
|
621
|
+
if (isCaptureCalled) {
|
622
|
+
while (!(await waitForComplete)) {
|
623
|
+
waitForComplete = new Promise(resolve => storyPlayResolver = resolve);
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
unsubscribe();
|
628
|
+
browserLogger.debug(`Story ${chalk.magenta(id)} ready for capturing`);
|
536
629
|
}
|
537
630
|
|
538
631
|
async function insertIgnoreStyles(browser, ignoreElements) {
|
@@ -4,15 +4,19 @@ 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 {
|
8
|
-
import { denormalizeStoryParameters } from '../shared';
|
7
|
+
import { isStorybookVersionLessThan } from './storybook/helpers';
|
9
8
|
|
10
9
|
function storyTestFabric(delay, testFn) {
|
11
10
|
return async function storyTest() {
|
12
11
|
var _testFn$call;
|
13
12
|
|
14
13
|
delay ? await new Promise(resolve => setTimeout(resolve, delay)) : void 0;
|
15
|
-
await ((_testFn$call = testFn === null || testFn === void 0 ? void 0 : testFn.call(this)) !== null && _testFn$call !== void 0 ? _testFn$call : this.
|
14
|
+
await ((_testFn$call = testFn === null || testFn === void 0 ? void 0 : testFn.call(this)) !== null && _testFn$call !== void 0 ? _testFn$call : this.screenshots.length > 0 ? this.expect(this.screenshots.reduce((screenshots, {
|
15
|
+
imageName,
|
16
|
+
screenshot
|
17
|
+
}, index) => ({ ...screenshots,
|
18
|
+
[imageName !== null && imageName !== void 0 ? imageName : `screenshot_${index}`]: screenshot
|
19
|
+
}), {})).to.matchImages() : this.expect(await this.takeScreenshot()).to.matchImage());
|
16
20
|
};
|
17
21
|
}
|
18
22
|
|
@@ -38,42 +42,40 @@ function createCreeveyTest(browser, storyMeta, skipOptions, testName) {
|
|
38
42
|
};
|
39
43
|
}
|
40
44
|
|
41
|
-
function convertStories(
|
45
|
+
function convertStories(browserName, stories) {
|
42
46
|
const tests = {};
|
43
47
|
(Array.isArray(stories) ? stories : Object.values(stories)).forEach(storyMeta => {
|
48
|
+
var _storyMeta$parameters;
|
49
|
+
|
44
50
|
// TODO Skip docsOnly stories for now
|
45
51
|
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
|
-
}
|
52
|
+
const {
|
53
|
+
delay: delayParam,
|
54
|
+
tests: storyTests,
|
55
|
+
skip
|
56
|
+
} = (_storyMeta$parameters = storyMeta.parameters.creevey) !== null && _storyMeta$parameters !== void 0 ? _storyMeta$parameters : {};
|
57
|
+
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]
|
58
|
+
// typeof tests === "function" => rootSuite -> kindSuite -> storyTest -> browser -> [images.png]
|
59
|
+
// typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> [browsers.png]
|
60
|
+
// typeof tests === "object" => rootSuite -> kindSuite -> storySuite -> test -> browser -> [images.png]
|
61
|
+
|
62
|
+
if (!storyTests) {
|
63
|
+
const test = createCreeveyTest(browserName, storyMeta, skip);
|
64
|
+
tests[test.id] = { ...test,
|
65
|
+
storyId: storyMeta.id,
|
66
|
+
story: storyMeta,
|
67
|
+
fn: storyTestFabric(delay)
|
68
|
+
};
|
69
|
+
return;
|
70
|
+
}
|
68
71
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
});
|
72
|
+
Object.entries(storyTests).forEach(([testName, testFn]) => {
|
73
|
+
const test = createCreeveyTest(browserName, storyMeta, skip, testName);
|
74
|
+
tests[test.id] = { ...test,
|
75
|
+
storyId: storyMeta.id,
|
76
|
+
story: storyMeta,
|
77
|
+
fn: storyTestFabric(delay, testFn)
|
78
|
+
};
|
77
79
|
});
|
78
80
|
});
|
79
81
|
return tests;
|
@@ -81,22 +83,24 @@ function convertStories(browsers, stories) {
|
|
81
83
|
|
82
84
|
export async function loadTestsFromStories(browsers, provider, update) {
|
83
85
|
const testIdsByFiles = new Map();
|
84
|
-
const
|
86
|
+
const stories = await provider(storiesByFiles => {
|
85
87
|
const testsDiff = {};
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
88
|
+
const tests = {};
|
89
|
+
browsers.forEach(browser => {
|
90
|
+
Array.from(storiesByFiles.entries()).forEach(([filename, stories]) => {
|
91
|
+
var _testIdsByFiles$get$f, _testIdsByFiles$get;
|
92
|
+
|
93
|
+
Object.assign(tests, convertStories(browser, stories));
|
94
|
+
const changed = Object.keys(tests);
|
95
|
+
const removed = (_testIdsByFiles$get$f = (_testIdsByFiles$get = testIdsByFiles.get(filename)) === null || _testIdsByFiles$get === void 0 ? void 0 : _testIdsByFiles$get.filter(testId => !tests[testId])) !== null && _testIdsByFiles$get$f !== void 0 ? _testIdsByFiles$get$f : [];
|
96
|
+
if (changed.length == 0) testIdsByFiles.delete(filename);else testIdsByFiles.set(filename, changed);
|
97
|
+
Object.assign(testsDiff, tests);
|
98
|
+
removed.forEach(testId => testsDiff[testId] = undefined);
|
99
|
+
});
|
95
100
|
});
|
96
101
|
update === null || update === void 0 ? void 0 : update(testsDiff);
|
97
102
|
});
|
98
|
-
const
|
99
|
-
const tests = convertStories(browsers, stories);
|
103
|
+
const tests = browsers.reduce((tests, browser) => Object.assign(tests, convertStories(browser, stories)), {});
|
100
104
|
Object.values(tests).filter(isDefined).forEach(({
|
101
105
|
id,
|
102
106
|
story: {
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import cluster, { isMaster } from 'cluster';
|
2
|
+
import { loadStoriesFromBrowser } from '../../selenium';
|
3
|
+
import { emitStoriesMessage, sendStoriesMessage, subscribeOn, subscribeOnWorker } from '../../messages';
|
4
|
+
import { deserializeRawStories } from '../../../shared';
|
5
|
+
import { isDefined } from '../../../types';
|
6
|
+
import { logger } from '../../logger';
|
7
|
+
export async function loadStories(_config, {
|
8
|
+
port
|
9
|
+
}, storiesListener) {
|
10
|
+
if (isMaster) {
|
11
|
+
return new Promise(resolve => {
|
12
|
+
const worker = Object.values(cluster.workers).filter(isDefined).find(worker => worker.isConnected());
|
13
|
+
|
14
|
+
if (worker) {
|
15
|
+
const unsubscribe = subscribeOnWorker(worker, 'stories', message => {
|
16
|
+
if (message.type == 'set') {
|
17
|
+
const {
|
18
|
+
stories,
|
19
|
+
oldTests
|
20
|
+
} = message.payload;
|
21
|
+
if (oldTests.length > 0) logger.warn(`If you use browser stories provider of CSFv3 Storybook feature\n` + `Creevey will not load tests defined in story parameters from following stories:\n` + oldTests.join('\n'));
|
22
|
+
unsubscribe();
|
23
|
+
resolve(stories);
|
24
|
+
}
|
25
|
+
});
|
26
|
+
sendStoriesMessage(worker, {
|
27
|
+
type: 'get'
|
28
|
+
});
|
29
|
+
}
|
30
|
+
|
31
|
+
subscribeOn('stories', message => {
|
32
|
+
// TODO updates only one browser :(
|
33
|
+
if (message.type == 'update') storiesListener(new Map(message.payload));
|
34
|
+
});
|
35
|
+
});
|
36
|
+
} else {
|
37
|
+
subscribeOn('stories', message => {
|
38
|
+
if (message.type == 'get') emitStoriesMessage({
|
39
|
+
type: 'set',
|
40
|
+
payload: {
|
41
|
+
stories,
|
42
|
+
oldTests: storiesWithOldTests
|
43
|
+
}
|
44
|
+
});
|
45
|
+
if (message.type == 'update') storiesListener(new Map(message.payload));
|
46
|
+
});
|
47
|
+
const stories = deserializeRawStories(await loadStoriesFromBrowser(port));
|
48
|
+
const storiesWithOldTests = [];
|
49
|
+
Object.values(stories).forEach(story => {
|
50
|
+
var _parameters, _parameters$creevey;
|
51
|
+
|
52
|
+
if ((_parameters = story.parameters) !== null && _parameters !== void 0 && (_parameters$creevey = _parameters.creevey) !== null && _parameters$creevey !== void 0 && _parameters$creevey.tests) {
|
53
|
+
var _parameters2, _parameters2$creevey;
|
54
|
+
|
55
|
+
(_parameters2 = story.parameters) === null || _parameters2 === void 0 ? true : (_parameters2$creevey = _parameters2.creevey) === null || _parameters2$creevey === void 0 ? true : delete _parameters2$creevey.tests;
|
56
|
+
storiesWithOldTests.push(`${story.kind}/${story.name}`);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
return stories;
|
60
|
+
}
|
61
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import chokidar from 'chokidar';
|
2
|
+
import { loadStories as browserProvider } from './browser';
|
3
|
+
import { logger } from '../../logger';
|
4
|
+
import parse from '../../parser';
|
5
|
+
import { readDirRecursive } from '../../../server/utils';
|
6
|
+
import { combineParameters } from '../../../shared';
|
7
|
+
export async function loadStories(_config, {
|
8
|
+
port
|
9
|
+
}, storiesListener) {
|
10
|
+
let creeveyParamsByStoryId = {};
|
11
|
+
|
12
|
+
const mergeParamsFromTestsToStory = (story, creeveyParams) => {
|
13
|
+
if (story.parameters) {
|
14
|
+
story.parameters.creevey = combineParameters(story.parameters.creevey || {}, creeveyParams);
|
15
|
+
}
|
16
|
+
};
|
17
|
+
|
18
|
+
const stories = await browserProvider(_config, {
|
19
|
+
port
|
20
|
+
}, updatedStoriesByFiles => {
|
21
|
+
Array.from(updatedStoriesByFiles.entries()).forEach(([, storiesArray]) => {
|
22
|
+
storiesArray.forEach(story => {
|
23
|
+
const creeveyParams = creeveyParamsByStoryId[story.id];
|
24
|
+
|
25
|
+
if (creeveyParams) {
|
26
|
+
mergeParamsFromTestsToStory(story, creeveyParams);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
});
|
30
|
+
storiesListener(updatedStoriesByFiles);
|
31
|
+
}); // TODO fix test files hot reloading
|
32
|
+
|
33
|
+
creeveyParamsByStoryId = await parseParams(_config
|
34
|
+
/*, (data) => console.log(data) */
|
35
|
+
);
|
36
|
+
Object.entries(stories).forEach(([storyId, story]) => {
|
37
|
+
mergeParamsFromTestsToStory(story, creeveyParamsByStoryId[storyId]);
|
38
|
+
});
|
39
|
+
return stories;
|
40
|
+
}
|
41
|
+
|
42
|
+
function parseParams(config, listener) {
|
43
|
+
if (!config.testDir) {
|
44
|
+
return Promise.resolve({});
|
45
|
+
}
|
46
|
+
|
47
|
+
const testFiles = readDirRecursive(config.testDir).filter(file => {
|
48
|
+
var _config$testRegex;
|
49
|
+
|
50
|
+
return (_config$testRegex = config.testRegex) === null || _config$testRegex === void 0 ? void 0 : _config$testRegex.test(file);
|
51
|
+
});
|
52
|
+
|
53
|
+
if (listener) {
|
54
|
+
chokidar.watch(testFiles).on('change', filePath => {
|
55
|
+
logger.debug(`changed: ${filePath}`); // doesn't work, always returns {} due modules caching
|
56
|
+
// see https://github.com/nodejs/modules/issues/307
|
57
|
+
|
58
|
+
void parse(testFiles).then(data => listener(data));
|
59
|
+
});
|
60
|
+
}
|
61
|
+
|
62
|
+
return parse(testFiles);
|
63
|
+
}
|
@@ -2,15 +2,15 @@
|
|
2
2
|
import path from 'path';
|
3
3
|
import { isWorker, isMaster } from 'cluster';
|
4
4
|
import chokidar from 'chokidar';
|
5
|
-
import { noop } from '
|
6
|
-
import { getCreeveyCache } from '
|
7
|
-
import { subscribeOn } from '
|
8
|
-
import { importStorybookClientLogger, importStorybookConfig, importStorybookCoreCommon, importStorybookCoreEvents, isStorybookVersionGreaterThan, isStorybookVersionLessThan } from '
|
9
|
-
import { logger } from '
|
10
|
-
import { denormalizeStoryParameters } from '
|
5
|
+
import { noop } from '../../../types';
|
6
|
+
import { getCreeveyCache } from '../../utils';
|
7
|
+
import { subscribeOn } from '../../messages';
|
8
|
+
import { importStorybookClientLogger, importStorybookConfig, importStorybookCoreCommon, importStorybookCoreEvents, isStorybookVersionGreaterThan, isStorybookVersionLessThan } from '../helpers';
|
9
|
+
import { logger } from '../../logger';
|
10
|
+
import { denormalizeStoryParameters } from '../../../shared';
|
11
11
|
|
12
12
|
async function initStorybookEnvironment() {
|
13
|
-
// @ts-
|
13
|
+
// @ts-expect-error There is no @types/global-jsdom package
|
14
14
|
(await import('global-jsdom')).default(undefined, {
|
15
15
|
url: 'http://localhost'
|
16
16
|
}); // NOTE Cutoff `jsdom` part from userAgent, because storybook check enviroment and create events channel if runs in browser
|
@@ -28,7 +28,7 @@ async function initStorybookEnvironment() {
|
|
28
28
|
if (isWorker) logger.warn = noop; // NOTE: disable logger for 5.x storybook
|
29
29
|
|
30
30
|
logger.debug = noop;
|
31
|
-
return import('
|
31
|
+
return import('../entry');
|
32
32
|
}
|
33
33
|
|
34
34
|
function watchStories(channel, watcher, initialFiles) {
|
@@ -90,8 +90,8 @@ async function loadStoriesDirectly(config, {
|
|
90
90
|
const {
|
91
91
|
addParameters,
|
92
92
|
configure
|
93
|
-
} = await import('
|
94
|
-
const requireContext = await (await import('
|
93
|
+
} = await import('../entry');
|
94
|
+
const requireContext = await (await import('../../loaders/babel/register')).default(config, debug);
|
95
95
|
|
96
96
|
const preview = (() => {
|
97
97
|
try {
|
@@ -167,7 +167,8 @@ async function loadStoriesDirectly(config, {
|
|
167
167
|
void startStorybook();
|
168
168
|
});
|
169
169
|
void startStorybook();
|
170
|
-
}
|
170
|
+
} // TODO Do we need to support multiple storybooks here?
|
171
|
+
|
171
172
|
|
172
173
|
export async function loadStories(config, {
|
173
174
|
watch,
|
@@ -180,7 +181,7 @@ export async function loadStories(config, {
|
|
180
181
|
} = storybookApi;
|
181
182
|
channel.removeAllListeners(Events.CURRENT_STORY_WAS_SET);
|
182
183
|
channel.on('storiesUpdated', storiesListener);
|
183
|
-
let watcher
|
184
|
+
let watcher;
|
184
185
|
if (watch) watcher = chokidar.watch([], {
|
185
186
|
ignoreInitial: true
|
186
187
|
});
|
@@ -189,7 +190,7 @@ export async function loadStories(config, {
|
|
189
190
|
const stories = isStorybookVersionLessThan(6) || isStorybookVersionGreaterThan(6, 3) ? data.stories : denormalizeStoryParameters(data);
|
190
191
|
const files = new Set(Object.values(stories).map(story => story.parameters.fileName));
|
191
192
|
if (watcher) channel.on(Events.SET_STORIES, watchStories(channel, watcher, files));
|
192
|
-
resolve(
|
193
|
+
resolve(stories);
|
193
194
|
});
|
194
195
|
});
|
195
196
|
if (config.useWebpackToExtractTests) loadStoriesFromBundle(watch);else void loadStoriesDirectly(config, {
|
@@ -197,4 +198,20 @@ export async function loadStories(config, {
|
|
197
198
|
debug
|
198
199
|
});
|
199
200
|
return loadPromise;
|
201
|
+
}
|
202
|
+
export async function extractStoriesData(config, {
|
203
|
+
watch,
|
204
|
+
debug
|
205
|
+
}) {
|
206
|
+
const storybookApi = await initStorybookEnvironment();
|
207
|
+
const Events = await importStorybookCoreEvents();
|
208
|
+
const {
|
209
|
+
channel
|
210
|
+
} = storybookApi;
|
211
|
+
channel.removeAllListeners(Events.CURRENT_STORY_WAS_SET);
|
212
|
+
const loadPromise = new Promise(resolve => channel.once(Events.SET_STORIES, resolve));
|
213
|
+
if (config.useWebpackToExtractTests) loadStoriesFromBundle(watch);else void loadStoriesDirectly(config, {
|
214
|
+
debug
|
215
|
+
});
|
216
|
+
return loadPromise;
|
200
217
|
}
|
package/lib/esm/server/utils.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { createWriteStream, existsSync, readFileSync, unlink } from 'fs';
|
1
|
+
import { createWriteStream, existsSync, readFileSync, readdirSync, unlink } from 'fs';
|
2
2
|
import cluster from 'cluster';
|
3
3
|
import { isDefined, noop, isFunction } from '../types';
|
4
4
|
import { emitShutdownMessage, sendShutdownMessage } from './messages';
|
@@ -138,4 +138,9 @@ export function removeProps(obj, propPath) {
|
|
138
138
|
if (typeof prop == 'string') delete obj[prop];
|
139
139
|
if (isFunction(prop)) Object.keys(obj).filter(prop).forEach(key => delete obj[key]);
|
140
140
|
}
|
141
|
+
}
|
142
|
+
export function readDirRecursive(dirPath) {
|
143
|
+
return [].concat(...readdirSync(dirPath, {
|
144
|
+
withFileTypes: true
|
145
|
+
}).map(dirent => dirent.isDirectory() ? readDirRecursive(`${dirPath}/${dirent.name}`) : [`${dirPath}/${dirent.name}`]));
|
141
146
|
}
|
@@ -47,14 +47,10 @@ function removeTestOrSuite(testOrSuite) {
|
|
47
47
|
|
48
48
|
export async function addTestsFromStories(rootSuite, config, {
|
49
49
|
browser,
|
50
|
-
|
51
|
-
debug
|
50
|
+
...options
|
52
51
|
}) {
|
53
52
|
const mochaTestsById = new Map();
|
54
|
-
const tests = await loadTestsFromStories([browser], listener => config.storiesProvider(config, {
|
55
|
-
watch,
|
56
|
-
debug
|
57
|
-
}, listener), testsDiff => Object.entries(testsDiff).forEach(([id, newTest]) => {
|
53
|
+
const tests = await loadTestsFromStories([browser], listener => config.storiesProvider(config, options, listener), testsDiff => Object.entries(testsDiff).forEach(([id, newTest]) => {
|
58
54
|
const oldTest = mochaTestsById.get(id);
|
59
55
|
mochaTestsById.delete(id);
|
60
56
|
if (oldTest) removeTestOrSuite(oldTest);
|