creevey 0.10.0-beta.4 → 0.10.0-beta.6

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 (79) hide show
  1. package/dist/creevey.js +12 -5
  2. package/dist/creevey.js.map +1 -1
  3. package/dist/server/config.js +2 -1
  4. package/dist/server/config.js.map +1 -1
  5. package/dist/server/docker.js +2 -2
  6. package/dist/server/docker.js.map +1 -1
  7. package/dist/server/index.js +4 -4
  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/pool.d.ts +3 -3
  15. package/dist/server/master/pool.js +10 -65
  16. package/dist/server/master/pool.js.map +1 -1
  17. package/dist/server/master/queue.d.ts +13 -0
  18. package/dist/server/master/queue.js +64 -0
  19. package/dist/server/master/queue.js.map +1 -0
  20. package/dist/server/master/runner.d.ts +1 -0
  21. package/dist/server/master/runner.js +4 -1
  22. package/dist/server/master/runner.js.map +1 -1
  23. package/dist/server/master/server.js +1 -1
  24. package/dist/server/master/server.js.map +1 -1
  25. package/dist/server/master/start.js +4 -4
  26. package/dist/server/master/start.js.map +1 -1
  27. package/dist/server/playwright/internal.js +17 -20
  28. package/dist/server/playwright/internal.js.map +1 -1
  29. package/dist/server/playwright/webdriver.js +1 -1
  30. package/dist/server/playwright/webdriver.js.map +1 -1
  31. package/dist/server/providers/browser.js +1 -1
  32. package/dist/server/providers/browser.js.map +1 -1
  33. package/dist/server/providers/hybrid.js +1 -1
  34. package/dist/server/providers/hybrid.js.map +1 -1
  35. package/dist/server/reporter.js +3 -3
  36. package/dist/server/reporter.js.map +1 -1
  37. package/dist/server/selenium/internal.d.ts +1 -2
  38. package/dist/server/selenium/internal.js +105 -81
  39. package/dist/server/selenium/internal.js.map +1 -1
  40. package/dist/server/selenium/webdriver.js +1 -1
  41. package/dist/server/selenium/webdriver.js.map +1 -1
  42. package/dist/server/utils.d.ts +2 -1
  43. package/dist/server/utils.js +11 -0
  44. package/dist/server/utils.js.map +1 -1
  45. package/dist/server/webdriver.d.ts +2 -3
  46. package/dist/server/webdriver.js +10 -9
  47. package/dist/server/webdriver.js.map +1 -1
  48. package/dist/server/worker/chai-image.d.ts +1 -2
  49. package/dist/server/worker/chai-image.js +4 -3
  50. package/dist/server/worker/chai-image.js.map +1 -1
  51. package/dist/server/worker/start.js +7 -10
  52. package/dist/server/worker/start.js.map +1 -1
  53. package/dist/types.d.ts +6 -2
  54. package/dist/types.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/creevey.ts +12 -6
  57. package/src/server/config.ts +2 -1
  58. package/src/server/docker.ts +2 -2
  59. package/src/server/index.ts +4 -4
  60. package/src/server/logger.ts +6 -2
  61. package/src/server/master/api.ts +1 -1
  62. package/src/server/master/pool.ts +18 -58
  63. package/src/server/master/queue.ts +64 -0
  64. package/src/server/master/runner.ts +4 -1
  65. package/src/server/master/server.ts +1 -1
  66. package/src/server/master/start.ts +7 -4
  67. package/src/server/playwright/internal.ts +17 -20
  68. package/src/server/playwright/webdriver.ts +1 -1
  69. package/src/server/providers/browser.ts +1 -1
  70. package/src/server/providers/hybrid.ts +1 -1
  71. package/src/server/reporter.ts +3 -2
  72. package/src/server/selenium/internal.ts +107 -81
  73. package/src/server/selenium/webdriver.ts +1 -1
  74. package/src/server/utils.ts +12 -1
  75. package/src/server/webdriver.ts +10 -15
  76. package/src/server/worker/chai-image.ts +4 -4
  77. package/src/server/worker/start.ts +7 -11
  78. package/src/types.ts +6 -2
  79. package/.yarnrc.yml +0 -1
@@ -13,12 +13,14 @@ import {
13
13
  TestMeta,
14
14
  } from '../../types.js';
15
15
  import Pool from './pool.js';
16
+ import { WorkerQueue } from './queue.js';
16
17
 
17
18
  export default class Runner extends EventEmitter {
18
19
  private failFast: boolean;
19
20
  private screenDir: string;
20
21
  private reportDir: string;
21
22
  private browsers: string[];
23
+ private scheduler: WorkerQueue;
22
24
  private pools: Record<string, Pool> = {};
23
25
  tests: Partial<Record<string, ServerTest>> = {};
24
26
  public get isRunning(): boolean {
@@ -30,9 +32,10 @@ export default class Runner extends EventEmitter {
30
32
  this.failFast = config.failFast;
31
33
  this.screenDir = config.screenDir;
32
34
  this.reportDir = config.reportDir;
35
+ this.scheduler = new WorkerQueue(config.useWorkerQueue);
33
36
  this.browsers = Object.keys(config.browsers);
34
37
  this.browsers
35
- .map((browser) => (this.pools[browser] = new Pool(config, browser, gridUrl)))
38
+ .map((browser) => (this.pools[browser] = new Pool(this.scheduler, config, browser, gridUrl)))
36
39
  .map((pool) => pool.on('test', this.handlePoolMessage));
37
40
  }
38
41
 
@@ -96,7 +96,7 @@ export function start(reportDir: string, port: number, ui: boolean): (api: Creev
96
96
  app.use(mount('/report', serve(reportDir)));
97
97
 
98
98
  wss.on('error', (error) => {
99
- logger.error(error);
99
+ logger().error(error);
100
100
  });
101
101
 
102
102
  server.listen(port);
@@ -42,7 +42,10 @@ function outputUnnecessaryImages(imagesDir: string, images: Set<string>): void {
42
42
  .map((imagePath) => path.posix.relative(imagesDir, imagePath))
43
43
  .filter((imagePath) => !images.has(imagePath));
44
44
  if (unnecessaryImages.length > 0) {
45
- logger.warn('We found unnecessary screenshot images, those can be safely removed:\n', unnecessaryImages.join('\n'));
45
+ logger().warn(
46
+ 'We found unnecessary screenshot images, those can be safely removed:\n',
47
+ unnecessaryImages.join('\n'),
48
+ );
46
49
  }
47
50
  }
48
51
 
@@ -81,10 +84,10 @@ export async function start(
81
84
 
82
85
  if (options.ui) {
83
86
  resolveApi(creeveyApi(runner));
84
- logger.info(`Started on http://localhost:${options.port}`);
87
+ logger().info(`Started on http://localhost:${options.port}`);
85
88
  } else {
86
89
  if (Object.values(runner.status.tests).filter((test) => test && !test.skip).length == 0) {
87
- logger.warn("Don't have any tests to run");
90
+ logger().warn("Don't have any tests to run");
88
91
 
89
92
  void shutdownWorkers().then(() => process.exit());
90
93
  return;
@@ -101,7 +104,7 @@ export async function start(
101
104
  void sendScreenshotsCount(config, options, runner.status)
102
105
  .catch((reason: unknown) => {
103
106
  const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
104
- logger.warn(`Can't send telemetry: ${error}`);
107
+ logger().warn(`Can't send telemetry: ${error}`);
105
108
  })
106
109
  .finally(() => {
107
110
  void shutdownWorkers().then(() => process.exit());
@@ -1,5 +1,4 @@
1
1
  import { Browser, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
2
- import Logger from 'loglevel';
3
2
  import chalk from 'chalk';
4
3
  import { v4 } from 'uuid';
5
4
  import prefix from 'loglevel-plugin-prefix';
@@ -28,7 +27,7 @@ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser |
28
27
  (resolve) =>
29
28
  (timeout = setTimeout(() => {
30
29
  isTimeout = true;
31
- logger.error(`Can't connect to ${type.name()} playwright browser`, error);
30
+ logger().error(`Can't connect to ${type.name()} playwright browser`, error);
32
31
  resolve(null);
33
32
  }, 10000)),
34
33
  ),
@@ -57,13 +56,11 @@ export class InternalBrowser {
57
56
  #sessionId: string = v4();
58
57
  #serverHost: string | null = null;
59
58
  #serverPort: number;
60
- #logger: Logger.Logger;
61
59
  #unsubscribe: () => void = noop;
62
60
  constructor(browser: Browser, page: Page, port: number) {
63
61
  this.#browser = browser;
64
62
  this.#page = page;
65
63
  this.#serverPort = port;
66
- this.#logger = Logger.getLogger(this.#sessionId);
67
64
  this.#unsubscribe = subscribeOn('shutdown', () => {
68
65
  void this.closeBrowser();
69
66
  });
@@ -113,7 +110,7 @@ export class InternalBrowser {
113
110
  await this.updateBrowserGlobalVariables();
114
111
  await this.resetMousePosition();
115
112
 
116
- this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
113
+ logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
117
114
 
118
115
  const result = await this.#page.evaluate<
119
116
  [error?: string | null, isCaptureCalled?: boolean] | null,
@@ -198,13 +195,13 @@ export class InternalBrowser {
198
195
  break;
199
196
 
200
197
  default:
201
- logger.error(
198
+ logger().error(
202
199
  `Unknown browser ${browserConfig.browserName}. Playwright supports browsers: chromium, firefox, webkit`,
203
200
  );
204
201
  }
205
202
  } else {
206
203
  if (browserConfig.browserName != 'chrome') {
207
- logger.error("Playwright's Selenium Grid feature supports only chrome browser");
204
+ logger().error("Playwright's Selenium Grid feature supports only chrome browser");
208
205
  return null;
209
206
  }
210
207
 
@@ -242,7 +239,7 @@ export class InternalBrowser {
242
239
  const error = new Error(`Can't load storybook root page: ${message}`);
243
240
  if (originalError instanceof Error) error.stack = originalError.stack;
244
241
 
245
- logger.error(error);
242
+ logger().error(error);
246
243
 
247
244
  return null;
248
245
  }
@@ -263,14 +260,13 @@ export class InternalBrowser {
263
260
  }) {
264
261
  const sessionId = this.#sessionId;
265
262
 
266
- prefix.apply(this.#logger, {
263
+ prefix.apply(logger(), {
267
264
  format(level) {
268
265
  const levelColor = colors[level.toUpperCase() as keyof typeof colors];
269
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
266
+ return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
270
267
  },
271
268
  });
272
269
 
273
- this.#page.setDefaultNavigationTimeout(10000);
274
270
  this.#page.setDefaultTimeout(60000);
275
271
 
276
272
  return await runSequence(
@@ -294,29 +290,30 @@ export class InternalBrowser {
294
290
 
295
291
  try {
296
292
  if (resolver) {
297
- this.#logger.debug('Resolving storybook url with custom resolver');
293
+ logger().debug('Resolving storybook url with custom resolver');
298
294
 
299
295
  const resolvedUrl = await resolver();
300
296
 
301
- this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
297
+ logger().debug(`Resolver storybook url ${resolvedUrl}`);
302
298
 
303
299
  await this.#page.goto(appendIframePath(resolvedUrl));
304
300
  } else {
305
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
301
+ // TODO this.#page.setDefaultNavigationTimeout(10000);
302
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
306
303
  }
307
304
  } catch (error) {
308
- this.#logger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
305
+ logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
309
306
  throw error;
310
307
  }
311
308
  }
312
309
 
313
310
  private async checkUrl(url: string): Promise<boolean> {
314
311
  try {
315
- this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
312
+ logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
316
313
  const response = await this.#page.goto(url, { waitUntil: 'commit' });
317
314
  const source = await response?.text();
318
315
 
319
- this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
316
+ logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
320
317
  return source?.includes(`id="${storybookRootID}"`) ?? false;
321
318
  } catch {
322
319
  return false;
@@ -325,7 +322,7 @@ export class InternalBrowser {
325
322
 
326
323
  private async waitForStorybook(): Promise<void> {
327
324
  // TODO Duplicated code with selenium
328
- this.#logger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
325
+ logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
329
326
 
330
327
  const isTimeout = await Promise.race([
331
328
  new Promise<boolean>((resolve) => {
@@ -344,7 +341,7 @@ export class InternalBrowser {
344
341
  return false;
345
342
  }, StorybookEvents.SET_GLOBALS);
346
343
  } catch (e: unknown) {
347
- this.#logger.debug('An error has been caught during the script:', e);
344
+ logger().debug('An error has been caught during the script:', e);
348
345
  }
349
346
  } while (wait);
350
347
  return false;
@@ -358,7 +355,7 @@ export class InternalBrowser {
358
355
  private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
359
356
  if (!globals) return;
360
357
 
361
- this.#logger.debug('Applying storybook globals');
358
+ logger().debug('Applying storybook globals');
362
359
  await this.#page.evaluate((globals: StorybookGlobals) => {
363
360
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
364
361
  }, globals);
@@ -53,7 +53,7 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
53
53
  try {
54
54
  return await import('./internal.js');
55
55
  } catch (error) {
56
- logger.error(error);
56
+ logger().error(error);
57
57
  return null;
58
58
  }
59
59
  })();
@@ -17,7 +17,7 @@ export const loadStories: StoriesProvider = async (_config, storiesListener, web
17
17
  if (message.type == 'set') {
18
18
  const { stories, oldTests } = message.payload;
19
19
  if (oldTests.length > 0)
20
- logger.warn(
20
+ logger().warn(
21
21
  `If you use browser stories provider of CSFv3 Storybook feature\n` +
22
22
  `Creevey will not load tests defined in story parameters from following stories:\n` +
23
23
  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
@@ -22,11 +22,11 @@ export class CreeveyReporter {
22
22
  // TODO Output in better way, like vitest, maybe
23
23
  constructor(runner: EventEmitter, options: { reporterOptions: { creevey: ReporterOptions } }) {
24
24
  const { sessionId, browserName } = options.reporterOptions.creevey;
25
- const testLogger = Logger.getLogger(browserName);
25
+ const testLogger = Logger.getLogger(sessionId);
26
26
 
27
27
  prefix.apply(testLogger, {
28
28
  format(level) {
29
- return `${testLevels[level]} => (${browserName}:${chalk.gray(sessionId)})`;
29
+ return `[${browserName}:${chalk.gray(process.pid)}] ${testLevels[level]} => ${chalk.gray(sessionId)}`;
30
30
  },
31
31
  });
32
32
 
@@ -39,6 +39,7 @@ export class CreeveyReporter {
39
39
  runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
40
40
  testLogger.error(
41
41
  chalk.cyan(test.titlePath().join('/')),
42
+ chalk.gray(`(${test.duration} ms)`),
42
43
  '\n ',
43
44
  this.getErrors(
44
45
  error,
@@ -94,21 +94,25 @@ async function openUrlAndWaitForPageSource(
94
94
  }
95
95
 
96
96
  async function buildWebdriver(
97
- browserName: string,
97
+ browser: string,
98
98
  gridUrl: string,
99
99
  config: Config,
100
100
  options: Options,
101
101
  ): Promise<WebDriver | null> {
102
- const browserConfig = config.browsers[browserName] as BrowserConfigObject;
103
- const { /*customizeBuilder,*/ seleniumCapabilities } = browserConfig;
102
+ const browserConfig = config.browsers[browser] as BrowserConfigObject;
103
+ const { /*customizeBuilder,*/ seleniumCapabilities, browserName } = browserConfig;
104
104
 
105
105
  const url = new URL(gridUrl);
106
106
  url.username = url.username ? '********' : '';
107
107
  url.password = url.password ? '********' : '';
108
- logger.debug(`(${browserName}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
108
+ logger().debug(`Connecting to Selenium ${chalk.magenta(url.toString())}`);
109
109
 
110
110
  // TODO Define some capabilities explicitly and define typings
111
- const capabilities = new Capabilities({ ...seleniumCapabilities, pageLoadStrategy: PageLoadStrategy.EAGER });
111
+ const capabilities = new Capabilities({
112
+ browserName,
113
+ ...seleniumCapabilities,
114
+ pageLoadStrategy: PageLoadStrategy.EAGER,
115
+ });
112
116
  const prefs = new logging.Preferences();
113
117
 
114
118
  if (options.trace) {
@@ -121,37 +125,65 @@ async function buildWebdriver(
121
125
  // TODO Validate browsers, versions, and platform
122
126
  // TODO Use `customizeBuilder`
123
127
 
124
- let browser: WebDriver;
128
+ let webdriver: WebDriver | null;
125
129
 
126
130
  try {
127
- // const ie = new IeOptions();
128
- // const edge = new EdgeOptions();
129
- // const chrome = new ChromeOptions();
130
- // const safari = new SafariOptions();
131
- // const firefox = new FirefoxOptions();
132
- // edge.enableBidi();
133
- // chrome.enableBidi();
134
- // firefox.enableBidi();
135
-
136
- browser = await new Builder()
137
- // .setIeOptions(ie)
138
- // .setEdgeOptions(edge)
139
- // .setChromeOptions(chrome)
140
- // .setSafariOptions(safari)
141
- // .setFirefoxOptions(firefox)
142
- .usingServer(gridUrl)
143
- .withCapabilities(capabilities)
144
- .setLoggingPrefs(prefs) // NOTE: Should go last
145
- .build();
146
-
147
- // const id = await browser.getWindowHandle();
148
- // context = await BrowsingContext(browser, { browsingContextId: id });
131
+ const maxRetries = 5;
132
+ let retries = 0;
133
+ do {
134
+ webdriver = await Promise.race([
135
+ new Promise<null>((resolve) => {
136
+ setTimeout(() => {
137
+ retries += 1;
138
+ resolve(null);
139
+ }, 120_000);
140
+ }),
141
+ (async () => {
142
+ if (retries > 0) {
143
+ logger().debug(`Trying to initialize session to Selenium Grid: retried ${retries} of ${maxRetries}`);
144
+ }
145
+ const retry = retries;
146
+ // const ie = new IeOptions();
147
+ // const edge = new EdgeOptions();
148
+ // const chrome = new ChromeOptions();
149
+ // const safari = new SafariOptions();
150
+ // const firefox = new FirefoxOptions();
151
+ // edge.enableBidi();
152
+ // chrome.enableBidi();
153
+ // firefox.enableBidi();
154
+
155
+ const driver = await new Builder()
156
+ // .setIeOptions(ie)
157
+ // .setEdgeOptions(edge)
158
+ // .setChromeOptions(chrome)
159
+ // .setSafariOptions(safari)
160
+ // .setFirefoxOptions(firefox)
161
+ .usingServer(gridUrl)
162
+ .withCapabilities(capabilities)
163
+ .setLoggingPrefs(prefs) // NOTE: Should go last
164
+ .build();
165
+
166
+ // const id = await driver.getWindowHandle();
167
+ // context = await BrowsingContext(driver, { browsingContextId: id });
168
+
169
+ if (retry != retries) {
170
+ void driver.quit();
171
+ return null;
172
+ }
173
+
174
+ return driver;
175
+ })(),
176
+ ]);
177
+ if (webdriver) break;
178
+ } while (retries < maxRetries);
179
+
180
+ if (!webdriver) throw new Error('Failed to initialize session to Selenium Grid due to many retries');
149
181
  } catch (error) {
150
- logger.error(`(${browserName}) Failed to start browser:`, error);
182
+ logger().error(`Failed to start browser:`, error);
151
183
  return null;
152
184
  }
153
185
 
154
- return browser;
186
+ return webdriver;
155
187
  }
156
188
 
157
189
  export class InternalBrowser {
@@ -159,13 +191,11 @@ export class InternalBrowser {
159
191
  #browser: WebDriver;
160
192
  #serverHost: string | null = null;
161
193
  #serverPort: number;
162
- #logger: Logger.Logger;
163
194
  #unsubscribe: () => void = noop;
164
195
  #keepAliveInterval: NodeJS.Timeout | null = null;
165
- constructor(browser: WebDriver, port: number, logger: Logger.Logger) {
196
+ constructor(browser: WebDriver, port: number) {
166
197
  this.#browser = browser;
167
198
  this.#serverPort = port;
168
- this.#logger = logger;
169
199
  this.#unsubscribe = subscribeOn('shutdown', () => {
170
200
  void this.closeBrowser();
171
201
  });
@@ -194,7 +224,7 @@ export class InternalBrowser {
194
224
 
195
225
  const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
196
226
 
197
- if (this.#logger.getLevel() <= Logger.levels.DEBUG) {
227
+ if (logger().getLevel() <= Logger.levels.DEBUG) {
198
228
  const { innerWidth, innerHeight } = await this.#browser.executeScript<{
199
229
  innerWidth: number;
200
230
  innerHeight: number;
@@ -204,16 +234,16 @@ export class InternalBrowser {
204
234
  innerHeight: window.innerHeight,
205
235
  };
206
236
  });
207
- this.#logger.debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
237
+ logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
208
238
  }
209
239
 
210
240
  try {
211
241
  if (!captureElement) {
212
- this.#logger.debug('Capturing viewport screenshot');
242
+ logger().debug('Capturing viewport screenshot');
213
243
  screenshot = await this.#browser.takeScreenshot();
214
- this.#logger.debug('Viewport screenshot is captured');
244
+ logger().debug('Viewport screenshot is captured');
215
245
  } else {
216
- this.#logger.debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
246
+ logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
217
247
  const rects = await this.#browser.executeScript<
218
248
  { elementRect: ElementRect; windowRect: ElementRect } | undefined
219
249
  >(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
@@ -252,11 +282,11 @@ export class InternalBrowser {
252
282
  elementRect.height + elementRect.top <= windowRect.height;
253
283
 
254
284
  if (isFitIntoViewport) {
255
- this.#logger.debug(
285
+ logger().debug(
256
286
  `Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
257
287
  );
258
288
  } else
259
- this.#logger.debug(
289
+ logger().debug(
260
290
  `Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
261
291
  );
262
292
 
@@ -272,7 +302,7 @@ export class InternalBrowser {
272
302
  : // TODO pointer-events: none, need to research
273
303
  await this.takeCompositeScreenshot(windowRect, elementRect);
274
304
 
275
- this.#logger.debug(`${chalk.cyan(captureElement)} is captured`);
305
+ logger().debug(`${chalk.cyan(captureElement)} is captured`);
276
306
  }
277
307
  } finally {
278
308
  await this.removeIgnoreStyles(ignoreStyles);
@@ -294,7 +324,7 @@ export class InternalBrowser {
294
324
  await this.updateBrowserGlobalVariables();
295
325
  await this.resetMousePosition();
296
326
 
297
- this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
327
+ logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
298
328
 
299
329
  const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
300
330
  function (
@@ -356,14 +386,14 @@ export class InternalBrowser {
356
386
  }
357
387
 
358
388
  async afterTest(test: ServerTest): Promise<void> {
359
- if (this.#logger.getLevel() === Logger.levels.TRACE) {
389
+ if (logger().getLevel() === Logger.levels.TRACE) {
360
390
  const output: string[] = [];
361
391
  const types = await this.#browser.manage().logs().getAvailableLogTypes();
362
392
  for (const type of types) {
363
393
  const logs = await this.#browser.manage().logs().get(type);
364
394
  output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
365
395
  }
366
- this.#logger.debug(
396
+ logger().debug(
367
397
  '----------',
368
398
  getTestPath(test).join('/'),
369
399
  '----------\n',
@@ -387,7 +417,7 @@ export class InternalBrowser {
387
417
 
388
418
  if (!browser) return null;
389
419
 
390
- const internalBrowser = new InternalBrowser(browser, options.port, logger);
420
+ const internalBrowser = new InternalBrowser(browser, options.port);
391
421
 
392
422
  try {
393
423
  if (isShuttingDown.current) return null;
@@ -405,11 +435,12 @@ export class InternalBrowser {
405
435
  } catch (originalError) {
406
436
  void internalBrowser.closeBrowser();
407
437
 
408
- const message = originalError instanceof Error ? originalError.message : (originalError as string);
409
- const error = new Error(`Can't load storybook root page by URL ${await browser.getCurrentUrl()}: ${message}`);
438
+ const message =
439
+ originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
440
+ const error = new Error(`Can't load storybook root page: ${message}`);
410
441
  if (originalError instanceof Error) error.stack = originalError.stack;
411
442
 
412
- logger.error(error);
443
+ logger().error(error);
413
444
 
414
445
  return null;
415
446
  }
@@ -440,20 +471,18 @@ export class InternalBrowser {
440
471
  /* noop */
441
472
  }
442
473
 
443
- this.#logger = Logger.getLogger(sessionId);
444
-
445
- prefix.apply(this.#logger, {
474
+ prefix.apply(logger(), {
446
475
  format(level) {
447
476
  const levelColor = colors[level.toUpperCase() as keyof typeof colors];
448
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
477
+ return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
449
478
  },
450
479
  });
451
480
 
452
- this.#logger.debug(`Connected successful with ${chalk.green(browserHost)}`);
481
+ logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
453
482
 
454
483
  return await runSequence(
455
484
  [
456
- () => this.#browser.manage().setTimeouts({ pageLoad: 10000, script: 60000 }),
485
+ () => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
457
486
  () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
458
487
  () => this.waitForStorybook(),
459
488
  () => this.updateStorybookGlobals(storybookGlobals),
@@ -478,19 +507,20 @@ export class InternalBrowser {
478
507
 
479
508
  try {
480
509
  if (resolver) {
481
- this.#logger.debug('Resolving storybook url with custom resolver');
510
+ logger().debug('Resolving storybook url with custom resolver');
482
511
 
483
512
  const resolvedUrl = await resolver();
484
513
 
485
- this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
514
+ logger().debug(`Resolver storybook url ${resolvedUrl}`);
486
515
 
487
516
  await this.#browser.get(appendIframePath(resolvedUrl));
488
517
  } else {
518
+ // TODO Pageload timeout 10s
489
519
  // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
490
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
520
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
491
521
  }
492
522
  } catch (error) {
493
- this.#logger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
523
+ logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
494
524
  throw error;
495
525
  }
496
526
  }
@@ -498,13 +528,13 @@ export class InternalBrowser {
498
528
  private async checkUrl(url: string): Promise<boolean> {
499
529
  try {
500
530
  // NOTE: Before trying a new url, reset the current one
501
- this.#logger.debug(`Opening ${chalk.magenta('about:blank')} page`);
531
+ logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
502
532
  await openUrlAndWaitForPageSource(
503
533
  this.#browser,
504
534
  'about:blank',
505
535
  (source: string) => !source.includes('<body></body>'),
506
536
  );
507
- this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
537
+ logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
508
538
  const source = await openUrlAndWaitForPageSource(
509
539
  this.#browser,
510
540
  url,
@@ -516,7 +546,7 @@ export class InternalBrowser {
516
546
  // because other add significant delay and some of them don't work in earlier chrome versions
517
547
  // Browsers always load page successful even it's failed
518
548
  // So we just check `root` element
519
- this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
549
+ logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
520
550
  return source.includes(`id="${storybookRootID}"`);
521
551
  } catch {
522
552
  return false;
@@ -524,7 +554,7 @@ export class InternalBrowser {
524
554
  }
525
555
 
526
556
  private async waitForStorybook(): Promise<void> {
527
- this.#logger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
557
+ logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
528
558
 
529
559
  const isTimeout = await Promise.race([
530
560
  new Promise<boolean>((resolve) => {
@@ -535,19 +565,15 @@ export class InternalBrowser {
535
565
  (async () => {
536
566
  let wait = true;
537
567
  do {
538
- try {
539
- // TODO Research a different way to ensure storybook is initiated
540
- wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
541
- // TODO Maybe use
542
- // import { global } from '@storybook/global';
543
- // global.IS_STORYBOOK
544
- if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
545
- if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
546
- return false;
547
- }, StorybookEvents.SET_GLOBALS);
548
- } catch (e: unknown) {
549
- this.#logger.debug('An error has been caught during the script:', e);
550
- }
568
+ // TODO Research a different way to ensure storybook is initiated
569
+ wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
570
+ // TODO Maybe use
571
+ // import { global } from '@storybook/global';
572
+ // global.IS_STORYBOOK
573
+ if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
574
+ if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
575
+ return false;
576
+ }, StorybookEvents.SET_GLOBALS);
551
577
  } while (wait);
552
578
  return false;
553
579
  })(),
@@ -560,7 +586,7 @@ export class InternalBrowser {
560
586
  private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
561
587
  if (!globals) return;
562
588
 
563
- this.#logger.debug('Applying storybook globals');
589
+ logger().debug('Applying storybook globals');
564
590
  await this.#browser.executeScript(function (globals: StorybookGlobals) {
565
591
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
566
592
  }, globals);
@@ -630,7 +656,7 @@ export class InternalBrowser {
630
656
  },
631
657
  );
632
658
 
633
- this.#logger.debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
659
+ logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
634
660
 
635
661
  const dWidth = windowRect.width - innerWidth;
636
662
  const dHeight = windowRect.height - innerHeight;
@@ -644,7 +670,7 @@ export class InternalBrowser {
644
670
  }
645
671
 
646
672
  private async resetMousePosition(): Promise<void> {
647
- this.#logger.debug('Resetting mouse position to the top-left corner');
673
+ logger().debug('Resetting mouse position to the top-left corner');
648
674
  const browserName = (await this.#browser.getCapabilities()).getBrowserName();
649
675
  const [browserVersion] =
650
676
  (await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
@@ -685,7 +711,7 @@ export class InternalBrowser {
685
711
  const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
686
712
  if (!ignoreSelectors.length) return null;
687
713
 
688
- this.#logger.debug('Hiding ignored elements before capturing');
714
+ logger().debug('Hiding ignored elements before capturing');
689
715
 
690
716
  return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
691
717
  return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
@@ -766,7 +792,7 @@ export class InternalBrowser {
766
792
 
767
793
  private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
768
794
  if (ignoreStyles) {
769
- this.#logger.debug('Revert hiding ignored elements');
795
+ logger().debug('Revert hiding ignored elements');
770
796
  await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
771
797
  window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
772
798
  }, ignoreStyles);
@@ -805,7 +831,7 @@ export class InternalBrowser {
805
831
  this.#keepAliveInterval = setInterval(() => {
806
832
  // NOTE Simple way to keep session alive
807
833
  void this.#browser.getCurrentUrl().then((url) => {
808
- logger.debug('current url', chalk.magenta(url));
834
+ logger().debug('current url', chalk.magenta(url));
809
835
  });
810
836
  }, 10 * 1000);
811
837
  }