creevey 0.9.2 → 0.9.4
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/dist/creevey.js +8 -4
- package/dist/creevey.js.map +1 -1
- package/dist/server/config.js +1 -0
- package/dist/server/config.js.map +1 -1
- package/dist/server/docker.js +1 -1
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +2 -1
- package/dist/server/logger.js +7 -3
- package/dist/server/logger.js.map +1 -1
- package/dist/server/master/api.js +1 -1
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/index.js +4 -4
- package/dist/server/master/index.js.map +1 -1
- package/dist/server/master/pool.d.ts +3 -3
- package/dist/server/master/pool.js +10 -63
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +60 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +1 -0
- package/dist/server/master/runner.js +4 -1
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +1 -1
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/selenium/browser.js +118 -91
- package/dist/server/selenium/browser.js.map +1 -1
- package/dist/server/storybook/providers/browser.js +1 -1
- package/dist/server/storybook/providers/browser.js.map +1 -1
- package/dist/server/storybook/providers/hybrid.js +1 -1
- package/dist/server/storybook/providers/hybrid.js.map +1 -1
- package/dist/server/utils.d.ts +2 -1
- package/dist/server/utils.js +11 -0
- package/dist/server/utils.js.map +1 -1
- package/dist/server/worker/reporter.js +2 -2
- package/dist/server/worker/reporter.js.map +1 -1
- package/dist/server/worker/worker.js +6 -4
- package/dist/server/worker/worker.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/creevey.ts +8 -5
- package/src/server/config.ts +1 -0
- package/src/server/docker.ts +1 -1
- package/src/server/index.ts +2 -2
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/index.ts +7 -4
- package/src/server/master/pool.ts +16 -48
- package/src/server/master/queue.ts +59 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/selenium/browser.ts +131 -98
- package/src/server/storybook/providers/browser.ts +1 -1
- package/src/server/storybook/providers/hybrid.ts +1 -1
- package/src/server/utils.ts +12 -1
- package/src/server/worker/reporter.ts +2 -2
- package/src/server/worker/worker.ts +6 -5
- package/src/types.ts +5 -0
@@ -29,6 +29,7 @@ import {
|
|
29
29
|
import { colors, logger } from '../logger.js';
|
30
30
|
import { emitStoriesMessage, subscribeOn } from '../messages.js';
|
31
31
|
import { isShuttingDown, LOCALHOST_REGEXP, runSequence } from '../utils.js';
|
32
|
+
import { Preferences } from 'selenium-webdriver/lib/logging.js';
|
32
33
|
|
33
34
|
interface ElementRect {
|
34
35
|
top: number;
|
@@ -50,7 +51,6 @@ declare global {
|
|
50
51
|
|
51
52
|
const storybookRootID = 'storybook-root';
|
52
53
|
const DOCKER_INTERNAL = 'host.docker.internal';
|
53
|
-
let browserLogger = logger;
|
54
54
|
let browserName = '';
|
55
55
|
let browser: WebDriver | null = null;
|
56
56
|
// let context: UnPromise<ReturnType<typeof BrowsingContext>> | null = null;
|
@@ -99,19 +99,17 @@ function getAddresses(): string[] {
|
|
99
99
|
}
|
100
100
|
|
101
101
|
async function resolveStorybookUrl(storybookUrl: string, checkUrl: (url: string) => Promise<boolean>): Promise<string> {
|
102
|
-
|
102
|
+
logger().debug('Resolving storybook url');
|
103
103
|
const addresses = getAddresses();
|
104
104
|
for (const ip of addresses) {
|
105
105
|
const resolvedUrl = storybookUrl.replace(LOCALHOST_REGEXP, ip);
|
106
|
-
|
106
|
+
logger().debug(`Checking storybook availability on ${chalk.magenta(resolvedUrl)}`);
|
107
107
|
if (await checkUrl(resolvedUrl)) {
|
108
|
-
|
108
|
+
logger().debug(`Resolved storybook url ${chalk.magenta(resolvedUrl)}`);
|
109
109
|
return resolvedUrl;
|
110
110
|
}
|
111
111
|
}
|
112
|
-
|
113
|
-
error.name = 'ResolveUrlError';
|
114
|
-
throw error;
|
112
|
+
throw new Error('Please specify `storybookUrl` with IP address that accessible from remote browser');
|
115
113
|
}
|
116
114
|
|
117
115
|
async function openUrlAndWaitForPageSource(
|
@@ -135,9 +133,9 @@ function getUrlChecker(browser: WebDriver): (url: string) => Promise<boolean> {
|
|
135
133
|
return async (url: string): Promise<boolean> => {
|
136
134
|
try {
|
137
135
|
// NOTE: Before trying a new url, reset the current one
|
138
|
-
|
136
|
+
logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
|
139
137
|
await openUrlAndWaitForPageSource(browser, 'about:blank', (source: string) => !source.includes('<body></body>'));
|
140
|
-
|
138
|
+
logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
|
141
139
|
const source = await openUrlAndWaitForPageSource(
|
142
140
|
browser,
|
143
141
|
url,
|
@@ -149,7 +147,7 @@ function getUrlChecker(browser: WebDriver): (url: string) => Promise<boolean> {
|
|
149
147
|
// because other add significant delay and some of them don't work in earlier chrome versions
|
150
148
|
// Browsers always load page successful even it's failed
|
151
149
|
// So we just check `root` element
|
152
|
-
|
150
|
+
logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
|
153
151
|
return source.includes(`id="${storybookRootID}"`);
|
154
152
|
} catch {
|
155
153
|
return false;
|
@@ -157,8 +155,70 @@ function getUrlChecker(browser: WebDriver): (url: string) => Promise<boolean> {
|
|
157
155
|
};
|
158
156
|
}
|
159
157
|
|
158
|
+
async function buildWebdriver(
|
159
|
+
gridUrl: string,
|
160
|
+
capabilities: Capabilities,
|
161
|
+
prefs: Preferences,
|
162
|
+
): Promise<readonly [string, WebDriver]> {
|
163
|
+
const maxRetries = 5;
|
164
|
+
let maybeResult = null;
|
165
|
+
let retries = 0;
|
166
|
+
do {
|
167
|
+
maybeResult = await Promise.race([
|
168
|
+
new Promise<null>((resolve) => {
|
169
|
+
setTimeout(() => {
|
170
|
+
retries += 1;
|
171
|
+
resolve(null);
|
172
|
+
}, 120_000);
|
173
|
+
}),
|
174
|
+
(async () => {
|
175
|
+
if (retries > 0) {
|
176
|
+
logger().debug(`Trying to initialize session to Selenium Grid: retried ${retries} of ${maxRetries}`);
|
177
|
+
}
|
178
|
+
const retry = retries;
|
179
|
+
// const ie = new IeOptions();
|
180
|
+
// const edge = new EdgeOptions();
|
181
|
+
// const chrome = new ChromeOptions();
|
182
|
+
// const safari = new SafariOptions();
|
183
|
+
// const firefox = new FirefoxOptions();
|
184
|
+
// edge.enableBidi();
|
185
|
+
// chrome.enableBidi();
|
186
|
+
// firefox.enableBidi();
|
187
|
+
|
188
|
+
const browser = await new Builder()
|
189
|
+
// .setIeOptions(ie)
|
190
|
+
// .setEdgeOptions(edge)
|
191
|
+
// .setChromeOptions(chrome)
|
192
|
+
// .setSafariOptions(safari)
|
193
|
+
// .setFirefoxOptions(firefox)
|
194
|
+
.usingServer(gridUrl)
|
195
|
+
.withCapabilities(capabilities)
|
196
|
+
.setLoggingPrefs(prefs) // NOTE: Should go last
|
197
|
+
.build();
|
198
|
+
|
199
|
+
// const id = await browser.getWindowHandle();
|
200
|
+
// context = await BrowsingContext(browser, { browsingContextId: id });
|
201
|
+
|
202
|
+
const sessionId = (await browser.getSession()).getId();
|
203
|
+
|
204
|
+
if (retry != retries) {
|
205
|
+
void browser.quit();
|
206
|
+
return null;
|
207
|
+
}
|
208
|
+
|
209
|
+
return [sessionId, browser] as const;
|
210
|
+
})(),
|
211
|
+
]);
|
212
|
+
if (maybeResult) break;
|
213
|
+
} while (retries < maxRetries);
|
214
|
+
|
215
|
+
if (!maybeResult) throw new Error('Failed to initialize session to Selenium Grid due to many retries');
|
216
|
+
|
217
|
+
return maybeResult;
|
218
|
+
}
|
219
|
+
|
160
220
|
async function waitForStorybook(browser: WebDriver): Promise<void> {
|
161
|
-
|
221
|
+
logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
|
162
222
|
|
163
223
|
const isTimeout = await Promise.race([
|
164
224
|
new Promise<boolean>((resolve) => {
|
@@ -176,7 +236,7 @@ async function waitForStorybook(browser: WebDriver): Promise<void> {
|
|
176
236
|
return false;
|
177
237
|
}, SET_GLOBALS);
|
178
238
|
} catch (e: unknown) {
|
179
|
-
|
239
|
+
logger().debug('An error has been caught during the script:', e);
|
180
240
|
}
|
181
241
|
} while (wait);
|
182
242
|
return false;
|
@@ -188,7 +248,7 @@ async function waitForStorybook(browser: WebDriver): Promise<void> {
|
|
188
248
|
}
|
189
249
|
|
190
250
|
async function resetMousePosition(browser: WebDriver): Promise<void> {
|
191
|
-
|
251
|
+
logger().debug('Resetting mouse position to the top-left corner');
|
192
252
|
const browserName = (await browser.getCapabilities()).getBrowserName();
|
193
253
|
const [browserVersion] =
|
194
254
|
(await browser.getCapabilities()).getBrowserVersion()?.split('.') ??
|
@@ -216,8 +276,9 @@ async function resetMousePosition(browser: WebDriver): Promise<void> {
|
|
216
276
|
y: Math.ceil((-1 * height) / 2) - top,
|
217
277
|
})
|
218
278
|
.perform();
|
219
|
-
} else if (browserName == 'firefox'
|
279
|
+
} else if (browserName == 'firefox') {
|
220
280
|
// NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
|
281
|
+
// NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
|
221
282
|
await browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
|
222
283
|
} else {
|
223
284
|
// NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
|
@@ -236,7 +297,7 @@ async function resizeViewport(browser: WebDriver, viewport: { width: number; hei
|
|
236
297
|
},
|
237
298
|
);
|
238
299
|
|
239
|
-
|
300
|
+
logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
|
240
301
|
|
241
302
|
const dWidth = windowRect.width - innerWidth;
|
242
303
|
const dHeight = windowRect.height - innerHeight;
|
@@ -367,7 +428,7 @@ export async function takeScreenshot(
|
|
367
428
|
|
368
429
|
const ignoreStyles = await insertIgnoreStyles(browser, ignoreElements);
|
369
430
|
|
370
|
-
if (
|
431
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
371
432
|
const { innerWidth, innerHeight } = await browser.executeScript<{ innerWidth: number; innerHeight: number }>(
|
372
433
|
function () {
|
373
434
|
return {
|
@@ -376,16 +437,16 @@ export async function takeScreenshot(
|
|
376
437
|
};
|
377
438
|
},
|
378
439
|
);
|
379
|
-
|
440
|
+
logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
|
380
441
|
}
|
381
442
|
|
382
443
|
try {
|
383
444
|
if (!captureElement) {
|
384
|
-
|
445
|
+
logger().debug('Capturing viewport screenshot');
|
385
446
|
screenshot = await browser.takeScreenshot();
|
386
|
-
|
447
|
+
logger().debug('Viewport screenshot is captured');
|
387
448
|
} else {
|
388
|
-
|
449
|
+
logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
|
389
450
|
const rects = await browser.executeScript<{ elementRect: ElementRect; windowRect: ElementRect } | undefined>(
|
390
451
|
function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
|
391
452
|
window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition`
|
@@ -425,11 +486,9 @@ export async function takeScreenshot(
|
|
425
486
|
elementRect.height + elementRect.top <= windowRect.height;
|
426
487
|
|
427
488
|
if (isFitIntoViewport) {
|
428
|
-
|
429
|
-
`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
430
|
-
);
|
489
|
+
logger().debug(`Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`);
|
431
490
|
} else
|
432
|
-
|
491
|
+
logger().debug(
|
433
492
|
`Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
|
434
493
|
);
|
435
494
|
|
@@ -445,7 +504,7 @@ export async function takeScreenshot(
|
|
445
504
|
: // TODO pointer-events: none, need to research
|
446
505
|
await takeCompositeScreenshot(browser, windowRect, elementRect);
|
447
506
|
|
448
|
-
|
507
|
+
logger().debug(`${chalk.cyan(captureElement)} is captured`);
|
449
508
|
}
|
450
509
|
} finally {
|
451
510
|
await removeIgnoreStyles(browser, ignoreStyles);
|
@@ -455,7 +514,7 @@ export async function takeScreenshot(
|
|
455
514
|
}
|
456
515
|
|
457
516
|
async function selectStory(browser: WebDriver, storyId: string, waitForReady = false): Promise<boolean> {
|
458
|
-
|
517
|
+
logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(storyId)}`);
|
459
518
|
|
460
519
|
const result = await browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
|
461
520
|
function (
|
@@ -483,7 +542,7 @@ async function selectStory(browser: WebDriver, storyId: string, waitForReady = f
|
|
483
542
|
}
|
484
543
|
|
485
544
|
export async function updateStorybookGlobals(browser: WebDriver, globals: StorybookGlobals): Promise<void> {
|
486
|
-
|
545
|
+
logger().debug('Applying storybook globals');
|
487
546
|
await browser.executeScript(function (globals: StorybookGlobals) {
|
488
547
|
window.__CREEVEY_UPDATE_GLOBALS__(globals);
|
489
548
|
}, globals);
|
@@ -504,11 +563,11 @@ async function openStorybookPage(
|
|
504
563
|
|
505
564
|
try {
|
506
565
|
if (resolver) {
|
507
|
-
|
566
|
+
logger().debug('Resolving storybook url with custom resolver');
|
508
567
|
|
509
568
|
const resolvedUrl = await resolver();
|
510
569
|
|
511
|
-
|
570
|
+
logger().debug(`Resolver storybook url ${resolvedUrl}`);
|
512
571
|
|
513
572
|
await browser.get(appendIframePath(resolvedUrl));
|
514
573
|
} else {
|
@@ -516,7 +575,7 @@ async function openStorybookPage(
|
|
516
575
|
await resolveStorybookUrl(appendIframePath(storybookUrl), getUrlChecker(browser));
|
517
576
|
}
|
518
577
|
} catch (error) {
|
519
|
-
|
578
|
+
logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
|
520
579
|
throw error;
|
521
580
|
}
|
522
581
|
}
|
@@ -598,73 +657,53 @@ export async function getBrowser(config: Config, options: Options & { browser: s
|
|
598
657
|
browser = null;
|
599
658
|
});
|
600
659
|
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
browserLogger.debug(`(${browserName}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
660
|
+
const url = new URL(gridUrl);
|
661
|
+
url.username = url.username ? '********' : '';
|
662
|
+
url.password = url.password ? '********' : '';
|
663
|
+
logger().debug(`(${browserName}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
|
606
664
|
|
607
|
-
|
665
|
+
const prefs = new logging.Preferences();
|
608
666
|
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
}
|
667
|
+
if (options.trace) {
|
668
|
+
for (const type of Object.values(logging.Type)) {
|
669
|
+
prefs.setLevel(type as string, logging.Level.ALL);
|
613
670
|
}
|
671
|
+
}
|
614
672
|
|
615
|
-
|
616
|
-
|
617
|
-
// const chrome = new ChromeOptions();
|
618
|
-
// const safari = new SafariOptions();
|
619
|
-
// const firefox = new FirefoxOptions();
|
620
|
-
// edge.enableBidi();
|
621
|
-
// chrome.enableBidi();
|
622
|
-
// firefox.enableBidi();
|
623
|
-
|
624
|
-
browser = await new Builder()
|
625
|
-
// .setIeOptions(ie)
|
626
|
-
// .setEdgeOptions(edge)
|
627
|
-
// .setChromeOptions(chrome)
|
628
|
-
// .setSafariOptions(safari)
|
629
|
-
// .setFirefoxOptions(firefox)
|
630
|
-
.usingServer(gridUrl)
|
631
|
-
.withCapabilities(capabilities)
|
632
|
-
.setLoggingPrefs(prefs) // NOTE: Should go last
|
633
|
-
.build();
|
634
|
-
|
635
|
-
// const id = await browser.getWindowHandle();
|
636
|
-
// context = await BrowsingContext(browser, { browsingContextId: id });
|
637
|
-
|
638
|
-
const sessionId = (await browser.getSession()).getId();
|
639
|
-
let browserHost = '';
|
673
|
+
let sessionId;
|
674
|
+
let browserHost = '';
|
640
675
|
|
641
|
-
|
642
|
-
const { Name } = await getSessionData(gridUrl, sessionId);
|
643
|
-
if (typeof Name == 'string') browserHost = Name;
|
644
|
-
} catch {
|
645
|
-
/* noop */
|
646
|
-
}
|
676
|
+
[sessionId, browser] = await buildWebdriver(gridUrl, capabilities, prefs);
|
647
677
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
678
|
+
try {
|
679
|
+
const { Name } = await getSessionData(gridUrl, sessionId);
|
680
|
+
if (typeof Name == 'string') browserHost = Name;
|
681
|
+
} catch {
|
682
|
+
/* noop */
|
683
|
+
}
|
653
684
|
|
654
|
-
|
685
|
+
logger().debug(
|
686
|
+
`(${browserName}) Connected successful with ${[chalk.green(browserHost), chalk.magenta(sessionId)]
|
687
|
+
.filter(Boolean)
|
688
|
+
.join(':')}`,
|
689
|
+
);
|
655
690
|
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
691
|
+
prefix.apply(logger(), {
|
692
|
+
format(level) {
|
693
|
+
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
694
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
|
695
|
+
},
|
696
|
+
});
|
662
697
|
|
698
|
+
try {
|
663
699
|
await runSequence(
|
664
700
|
[
|
665
701
|
() => browser?.manage().setTimeouts({ pageLoad: 10000, script: 60000 }),
|
666
702
|
() => browser && openStorybookPage(browser, realAddress, config.resolveStorybookUrl),
|
667
703
|
() => browser && waitForStorybook(browser),
|
704
|
+
() => browser && resolveCreeveyHost(browser, options.port),
|
705
|
+
() => browser && updateBrowserGlobalVariables(browser),
|
706
|
+
() => _storybookGlobals && browser && updateStorybookGlobals(browser, _storybookGlobals),
|
668
707
|
// NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
|
669
708
|
// NOTE: So if we resize window right after getting webdriver instance we might get situation
|
670
709
|
// NOTE: When the toolbar appears after resize and final viewport size become smaller than we set
|
@@ -674,24 +713,18 @@ export async function getBrowser(config: Config, options: Options & { browser: s
|
|
674
713
|
);
|
675
714
|
} catch (originalError) {
|
676
715
|
if (isShuttingDown.current) {
|
677
|
-
browser
|
716
|
+
browser.quit().catch(noop);
|
678
717
|
browser = null;
|
679
718
|
return null;
|
680
719
|
}
|
681
|
-
|
682
|
-
const error = new Error(
|
720
|
+
const currentUrl = await browser.getCurrentUrl();
|
721
|
+
const error = new Error(
|
722
|
+
`Can't load storybook root page${currentUrl ? ` by URL ${currentUrl}` : ''}: ${originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string)}`,
|
723
|
+
);
|
683
724
|
if (originalError instanceof Error) error.stack = originalError.stack;
|
684
725
|
throw error;
|
685
726
|
}
|
686
727
|
|
687
|
-
if (_storybookGlobals) {
|
688
|
-
await updateStorybookGlobals(browser, _storybookGlobals);
|
689
|
-
}
|
690
|
-
|
691
|
-
await resolveCreeveyHost(browser, options.port);
|
692
|
-
|
693
|
-
await updateBrowserGlobalVariables(browser);
|
694
|
-
|
695
728
|
return browser;
|
696
729
|
}
|
697
730
|
|
@@ -762,7 +795,7 @@ export async function switchStory(this: Context): Promise<void> {
|
|
762
795
|
ignoreElements,
|
763
796
|
} = (parameters.creevey ?? {}) as CreeveyStoryParams;
|
764
797
|
|
765
|
-
|
798
|
+
logger().debug(`Switching to story ${chalk.cyan(title)}/${chalk.cyan(name)} by id ${chalk.magenta(id)}`);
|
766
799
|
|
767
800
|
if (captureElement)
|
768
801
|
Object.defineProperty(this, 'captureElement', {
|
@@ -813,7 +846,7 @@ export async function switchStory(this: Context): Promise<void> {
|
|
813
846
|
|
814
847
|
unsubscribe();
|
815
848
|
|
816
|
-
|
849
|
+
logger().debug(`Story ${chalk.magenta(id)} ready for capturing`);
|
817
850
|
}
|
818
851
|
|
819
852
|
async function insertIgnoreStyles(
|
@@ -823,7 +856,7 @@ async function insertIgnoreStyles(
|
|
823
856
|
const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
|
824
857
|
if (!ignoreSelectors.length) return null;
|
825
858
|
|
826
|
-
|
859
|
+
logger().debug('Hiding ignored elements before capturing');
|
827
860
|
|
828
861
|
return await browser.executeScript(function (ignoreSelectors: string[]) {
|
829
862
|
return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
|
@@ -832,7 +865,7 @@ async function insertIgnoreStyles(
|
|
832
865
|
|
833
866
|
async function removeIgnoreStyles(browser: WebDriver, ignoreStyles: WebElement | null): Promise<void> {
|
834
867
|
if (ignoreStyles) {
|
835
|
-
|
868
|
+
logger().debug('Revert hiding ignored elements');
|
836
869
|
await browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
|
837
870
|
window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
|
838
871
|
}, ignoreStyles);
|
@@ -18,7 +18,7 @@ export const loadStories: StoriesProvider = async (_config, _options, storiesLis
|
|
18
18
|
if (message.type == 'set') {
|
19
19
|
const { stories, oldTests } = message.payload;
|
20
20
|
if (oldTests.length > 0)
|
21
|
-
logger.warn(
|
21
|
+
logger().warn(
|
22
22
|
`If you use browser stories provider of CSFv3 Storybook feature\n` +
|
23
23
|
`Creevey will not load tests defined in story parameters from following stories:\n` +
|
24
24
|
oldTests.join('\n'),
|
@@ -54,7 +54,7 @@ async function parseParams(
|
|
54
54
|
|
55
55
|
if (listener) {
|
56
56
|
chokidar.watch(testFiles).on('change', (filePath) => {
|
57
|
-
logger.debug(`changed: ${filePath}`);
|
57
|
+
logger().debug(`changed: ${filePath}`);
|
58
58
|
|
59
59
|
// doesn't work, always returns {} due modules caching
|
60
60
|
// see https://github.com/nodejs/modules/issues/307
|
package/src/server/utils.ts
CHANGED
@@ -7,7 +7,7 @@ import { createRequire } from 'module';
|
|
7
7
|
import findCacheDir from 'find-cache-dir';
|
8
8
|
import { register as esmRegister } from 'tsx/esm/api';
|
9
9
|
import { register as cjsRegister } from 'tsx/cjs/api';
|
10
|
-
import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest } from '../types.js';
|
10
|
+
import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest, Worker } from '../types.js';
|
11
11
|
import { emitShutdownMessage, sendShutdownMessage } from './messages.js';
|
12
12
|
|
13
13
|
const importMetaUrl = pathToFileURL(__filename).href;
|
@@ -99,6 +99,17 @@ export async function shutdownWorkers(): Promise<void> {
|
|
99
99
|
emitShutdownMessage();
|
100
100
|
}
|
101
101
|
|
102
|
+
export function gracefullyKill(worker: Worker): void {
|
103
|
+
worker.isShuttingDown = true;
|
104
|
+
const timeout = setTimeout(() => {
|
105
|
+
worker.kill();
|
106
|
+
}, 10000);
|
107
|
+
worker.on('exit', () => {
|
108
|
+
clearTimeout(timeout);
|
109
|
+
});
|
110
|
+
sendShutdownMessage(worker);
|
111
|
+
}
|
112
|
+
|
102
113
|
export function shutdown(): void {
|
103
114
|
process.exit();
|
104
115
|
}
|
@@ -24,11 +24,11 @@ export class CreeveyReporter extends reporters.Base {
|
|
24
24
|
super(runner);
|
25
25
|
|
26
26
|
const { sessionId, topLevelSuite } = options.reporterOptions as ReporterOptions;
|
27
|
-
const testLogger = Logger.getLogger(
|
27
|
+
const testLogger = Logger.getLogger(sessionId);
|
28
28
|
|
29
29
|
prefix.apply(testLogger, {
|
30
30
|
format(level) {
|
31
|
-
return
|
31
|
+
return `[${topLevelSuite}:${chalk.gray(process.pid)}] ${testLevels[level]} => ${chalk.gray(sessionId)}`;
|
32
32
|
},
|
33
33
|
});
|
34
34
|
|
@@ -140,9 +140,11 @@ export async function start(config: Config, options: Options & { browser: string
|
|
140
140
|
try {
|
141
141
|
return await getBrowser(config, options);
|
142
142
|
} catch (error) {
|
143
|
+
const errorMessage = error instanceof Error ? error.message : ((error ?? 'Unknown error') as string);
|
144
|
+
logger().error('Failed to initiate webdriver:', errorMessage);
|
143
145
|
emitWorkerMessage({
|
144
146
|
type: 'error',
|
145
|
-
payload: { error:
|
147
|
+
payload: { error: errorMessage },
|
146
148
|
});
|
147
149
|
return null;
|
148
150
|
}
|
@@ -163,7 +165,7 @@ export async function start(config: Config, options: Options & { browser: string
|
|
163
165
|
() =>
|
164
166
|
// NOTE Simple way to keep session alive
|
165
167
|
void browser.getCurrentUrl().then((url) => {
|
166
|
-
logger.debug(
|
168
|
+
logger().debug('current url', chalk.magenta(url));
|
167
169
|
}),
|
168
170
|
10 * 1000,
|
169
171
|
);
|
@@ -191,9 +193,8 @@ export async function start(config: Config, options: Options & { browser: string
|
|
191
193
|
const logs = await browser.manage().logs().get(type);
|
192
194
|
output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
|
193
195
|
}
|
194
|
-
logger.debug(
|
196
|
+
logger().debug(
|
195
197
|
'----------',
|
196
|
-
sessionId,
|
197
198
|
this.currentTest?.titlePath().join('/'),
|
198
199
|
'----------\n',
|
199
200
|
output.join('\n'),
|
@@ -235,7 +236,7 @@ export async function start(config: Config, options: Options & { browser: string
|
|
235
236
|
});
|
236
237
|
});
|
237
238
|
|
238
|
-
logger.info(
|
239
|
+
logger().info('Worker is ready');
|
239
240
|
|
240
241
|
emitWorkerMessage({ type: 'ready' });
|
241
242
|
}
|
package/src/types.ts
CHANGED
@@ -234,6 +234,11 @@ export interface Config {
|
|
234
234
|
* The `--ui` CLI option ignores this option
|
235
235
|
*/
|
236
236
|
failFast: boolean;
|
237
|
+
/**
|
238
|
+
* Start workers in sequential queue
|
239
|
+
* @default false
|
240
|
+
*/
|
241
|
+
useWorkerQueue: boolean;
|
237
242
|
/**
|
238
243
|
* Specify platform for docker images
|
239
244
|
*/
|