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.
Files changed (60) hide show
  1. package/dist/creevey.js +8 -4
  2. package/dist/creevey.js.map +1 -1
  3. package/dist/server/config.js +1 -0
  4. package/dist/server/config.js.map +1 -1
  5. package/dist/server/docker.js +1 -1
  6. package/dist/server/docker.js.map +1 -1
  7. package/dist/server/index.js +2 -2
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/server/logger.d.ts +2 -1
  10. package/dist/server/logger.js +7 -3
  11. package/dist/server/logger.js.map +1 -1
  12. package/dist/server/master/api.js +1 -1
  13. package/dist/server/master/api.js.map +1 -1
  14. package/dist/server/master/index.js +4 -4
  15. package/dist/server/master/index.js.map +1 -1
  16. package/dist/server/master/pool.d.ts +3 -3
  17. package/dist/server/master/pool.js +10 -63
  18. package/dist/server/master/pool.js.map +1 -1
  19. package/dist/server/master/queue.d.ts +13 -0
  20. package/dist/server/master/queue.js +60 -0
  21. package/dist/server/master/queue.js.map +1 -0
  22. package/dist/server/master/runner.d.ts +1 -0
  23. package/dist/server/master/runner.js +4 -1
  24. package/dist/server/master/runner.js.map +1 -1
  25. package/dist/server/master/server.js +1 -1
  26. package/dist/server/master/server.js.map +1 -1
  27. package/dist/server/selenium/browser.js +118 -91
  28. package/dist/server/selenium/browser.js.map +1 -1
  29. package/dist/server/storybook/providers/browser.js +1 -1
  30. package/dist/server/storybook/providers/browser.js.map +1 -1
  31. package/dist/server/storybook/providers/hybrid.js +1 -1
  32. package/dist/server/storybook/providers/hybrid.js.map +1 -1
  33. package/dist/server/utils.d.ts +2 -1
  34. package/dist/server/utils.js +11 -0
  35. package/dist/server/utils.js.map +1 -1
  36. package/dist/server/worker/reporter.js +2 -2
  37. package/dist/server/worker/reporter.js.map +1 -1
  38. package/dist/server/worker/worker.js +6 -4
  39. package/dist/server/worker/worker.js.map +1 -1
  40. package/dist/types.d.ts +5 -0
  41. package/dist/types.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/creevey.ts +8 -5
  44. package/src/server/config.ts +1 -0
  45. package/src/server/docker.ts +1 -1
  46. package/src/server/index.ts +2 -2
  47. package/src/server/logger.ts +6 -2
  48. package/src/server/master/api.ts +1 -1
  49. package/src/server/master/index.ts +7 -4
  50. package/src/server/master/pool.ts +16 -48
  51. package/src/server/master/queue.ts +59 -0
  52. package/src/server/master/runner.ts +4 -1
  53. package/src/server/master/server.ts +1 -1
  54. package/src/server/selenium/browser.ts +131 -98
  55. package/src/server/storybook/providers/browser.ts +1 -1
  56. package/src/server/storybook/providers/hybrid.ts +1 -1
  57. package/src/server/utils.ts +12 -1
  58. package/src/server/worker/reporter.ts +2 -2
  59. package/src/server/worker/worker.ts +6 -5
  60. 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
- browserLogger.debug('Resolving storybook url');
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
- browserLogger.debug(`Checking storybook availability on ${chalk.magenta(resolvedUrl)}`);
106
+ logger().debug(`Checking storybook availability on ${chalk.magenta(resolvedUrl)}`);
107
107
  if (await checkUrl(resolvedUrl)) {
108
- browserLogger.debug(`Resolved storybook url ${chalk.magenta(resolvedUrl)}`);
108
+ logger().debug(`Resolved storybook url ${chalk.magenta(resolvedUrl)}`);
109
109
  return resolvedUrl;
110
110
  }
111
111
  }
112
- const error = new Error('Please specify `storybookUrl` with IP address that accessible from remote browser');
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
- browserLogger.debug(`Opening ${chalk.magenta('about:blank')} page`);
136
+ logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
139
137
  await openUrlAndWaitForPageSource(browser, 'about:blank', (source: string) => !source.includes('<body></body>'));
140
- browserLogger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
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
- browserLogger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
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
- browserLogger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
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
- browserLogger.debug('An error has been caught during the script:', e);
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
- browserLogger.debug('Resetting mouse position to the top-left corner');
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' && browserVersion == '61') {
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
- browserLogger.debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
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 (browserLogger.getLevel() <= Logger.levels.DEBUG) {
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
- browserLogger.debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
440
+ logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
380
441
  }
381
442
 
382
443
  try {
383
444
  if (!captureElement) {
384
- browserLogger.debug('Capturing viewport screenshot');
445
+ logger().debug('Capturing viewport screenshot');
385
446
  screenshot = await browser.takeScreenshot();
386
- browserLogger.debug('Viewport screenshot is captured');
447
+ logger().debug('Viewport screenshot is captured');
387
448
  } else {
388
- browserLogger.debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
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
- browserLogger.debug(
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
- browserLogger.debug(
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
- browserLogger.debug(`${chalk.cyan(captureElement)} is captured`);
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
- browserLogger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(storyId)}`);
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
- browserLogger.debug('Applying storybook globals');
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
- browserLogger.debug('Resolving storybook url with custom resolver');
566
+ logger().debug('Resolving storybook url with custom resolver');
508
567
 
509
568
  const resolvedUrl = await resolver();
510
569
 
511
- browserLogger.debug(`Resolver storybook url ${resolvedUrl}`);
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
- browserLogger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
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
- try {
602
- const url = new URL(gridUrl);
603
- url.username = url.username ? '********' : '';
604
- url.password = url.password ? '********' : '';
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
- const prefs = new logging.Preferences();
665
+ const prefs = new logging.Preferences();
608
666
 
609
- if (options.trace) {
610
- for (const type of Object.values(logging.Type)) {
611
- prefs.setLevel(type as string, logging.Level.ALL);
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
- // const ie = new IeOptions();
616
- // const edge = new EdgeOptions();
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
- try {
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
- browserLogger.debug(
649
- `(${browserName}) Connected successful with ${[chalk.green(browserHost), chalk.magenta(sessionId)]
650
- .filter(Boolean)
651
- .join(':')}`,
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
- browserLogger = Logger.getLogger(sessionId);
685
+ logger().debug(
686
+ `(${browserName}) Connected successful with ${[chalk.green(browserHost), chalk.magenta(sessionId)]
687
+ .filter(Boolean)
688
+ .join(':')}`,
689
+ );
655
690
 
656
- prefix.apply(browserLogger, {
657
- format(level) {
658
- const levelColor = colors[level.toUpperCase() as keyof typeof colors];
659
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
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?.quit().catch(noop);
716
+ browser.quit().catch(noop);
678
717
  browser = null;
679
718
  return null;
680
719
  }
681
- if (originalError instanceof Error && originalError.name == 'ResolveUrlError') throw originalError;
682
- const error = new Error(`Can't load storybook root page by URL ${(await browser?.getCurrentUrl()) ?? realAddress}`);
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
- browserLogger.debug(`Switching to story ${chalk.cyan(title)}/${chalk.cyan(name)} by id ${chalk.magenta(id)}`);
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
- browserLogger.debug(`Story ${chalk.magenta(id)} ready for capturing`);
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
- browserLogger.debug('Hiding ignored elements before capturing');
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
- browserLogger.debug('Revert hiding ignored elements');
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
@@ -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(topLevelSuite);
27
+ const testLogger = Logger.getLogger(sessionId);
28
28
 
29
29
  prefix.apply(testLogger, {
30
30
  format(level) {
31
- return `${testLevels[level]} => (${topLevelSuite}:${chalk.gray(sessionId)})`;
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: error instanceof Error ? error.message : ((error ?? 'Unknown error') as string) },
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(`${options.browser}:${chalk.gray(sessionId)}`, 'current url', chalk.magenta(url));
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(`${options.browser}:${chalk.gray(sessionId)} is ready`);
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
  */