creevey 0.10.0-beta.0 → 0.10.0-beta.10

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 (159) hide show
  1. package/dist/client/addon/components/Panel.js +2 -2
  2. package/dist/client/addon/components/Panel.js.map +1 -1
  3. package/dist/client/addon/controller.js +4 -5
  4. package/dist/client/addon/controller.js.map +1 -1
  5. package/dist/client/addon/withCreevey.js +18 -34
  6. package/dist/client/addon/withCreevey.js.map +1 -1
  7. package/dist/client/shared/components/ImagesView/SwapView.js +12 -0
  8. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  9. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
  10. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  11. package/dist/client/shared/components/ResultsPage.js +23 -5
  12. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  13. package/dist/client/web/CreeveyApp.js +22 -6
  14. package/dist/client/web/CreeveyApp.js.map +1 -1
  15. package/dist/client/web/CreeveyContext.d.ts +5 -0
  16. package/dist/client/web/CreeveyContext.js +3 -0
  17. package/dist/client/web/CreeveyContext.js.map +1 -1
  18. package/dist/client/web/CreeveyView/SideBar/Search.js +2 -2
  19. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  20. package/dist/client/web/CreeveyView/SideBar/SideBar.js +1 -0
  21. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  22. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +49 -6
  23. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  24. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +1 -3
  25. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  26. package/dist/client/web/CreeveyView/SideBar/TestLink.js +1 -3
  27. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  28. package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
  29. package/dist/client/web/KeyboardEventsContext.js +62 -57
  30. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  31. package/dist/client/web/assets/{index-DkmZfG9C.js → index-BE9CL5_G.js} +94 -94
  32. package/dist/client/web/index.html +1 -1
  33. package/dist/creevey.js +13 -5
  34. package/dist/creevey.js.map +1 -1
  35. package/dist/index.js +1 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/playwright.d.ts +2 -0
  38. package/dist/playwright.js +6 -0
  39. package/dist/playwright.js.map +1 -0
  40. package/dist/selenium.d.ts +2 -0
  41. package/dist/selenium.js +6 -0
  42. package/dist/selenium.js.map +1 -0
  43. package/dist/server/config.d.ts +1 -1
  44. package/dist/server/config.js +12 -5
  45. package/dist/server/config.js.map +1 -1
  46. package/dist/server/docker.js +2 -2
  47. package/dist/server/docker.js.map +1 -1
  48. package/dist/server/index.js +40 -4
  49. package/dist/server/index.js.map +1 -1
  50. package/dist/server/logger.d.ts +2 -1
  51. package/dist/server/logger.js +7 -3
  52. package/dist/server/logger.js.map +1 -1
  53. package/dist/server/master/api.js +1 -1
  54. package/dist/server/master/api.js.map +1 -1
  55. package/dist/server/master/pool.d.ts +3 -3
  56. package/dist/server/master/pool.js +10 -63
  57. package/dist/server/master/pool.js.map +1 -1
  58. package/dist/server/master/queue.d.ts +13 -0
  59. package/dist/server/master/queue.js +64 -0
  60. package/dist/server/master/queue.js.map +1 -0
  61. package/dist/server/master/runner.d.ts +1 -0
  62. package/dist/server/master/runner.js +4 -1
  63. package/dist/server/master/runner.js.map +1 -1
  64. package/dist/server/master/server.js +1 -1
  65. package/dist/server/master/server.js.map +1 -1
  66. package/dist/server/master/start.js +4 -4
  67. package/dist/server/master/start.js.map +1 -1
  68. package/dist/server/playwright/docker-file.js +12 -1
  69. package/dist/server/playwright/docker-file.js.map +1 -1
  70. package/dist/server/playwright/docker.d.ts +1 -1
  71. package/dist/server/playwright/docker.js +1 -6
  72. package/dist/server/playwright/docker.js.map +1 -1
  73. package/dist/server/playwright/internal.d.ts +2 -2
  74. package/dist/server/playwright/internal.js +56 -44
  75. package/dist/server/playwright/internal.js.map +1 -1
  76. package/dist/server/playwright/webdriver.d.ts +2 -1
  77. package/dist/server/playwright/webdriver.js +4 -1
  78. package/dist/server/playwright/webdriver.js.map +1 -1
  79. package/dist/server/providers/browser.js +2 -1
  80. package/dist/server/providers/browser.js.map +1 -1
  81. package/dist/server/providers/hybrid.js +1 -1
  82. package/dist/server/providers/hybrid.js.map +1 -1
  83. package/dist/server/reporter.js +4 -4
  84. package/dist/server/reporter.js.map +1 -1
  85. package/dist/server/selenium/internal.d.ts +3 -3
  86. package/dist/server/selenium/internal.js +126 -89
  87. package/dist/server/selenium/internal.js.map +1 -1
  88. package/dist/server/selenium/selenoid.js +2 -2
  89. package/dist/server/selenium/selenoid.js.map +1 -1
  90. package/dist/server/selenium/webdriver.d.ts +2 -1
  91. package/dist/server/selenium/webdriver.js +8 -1
  92. package/dist/server/selenium/webdriver.js.map +1 -1
  93. package/dist/server/telemetry.js +7 -3
  94. package/dist/server/telemetry.js.map +1 -1
  95. package/dist/server/utils.d.ts +2 -1
  96. package/dist/server/utils.js +13 -3
  97. package/dist/server/utils.js.map +1 -1
  98. package/dist/server/webdriver.d.ts +4 -4
  99. package/dist/server/webdriver.js +10 -9
  100. package/dist/server/webdriver.js.map +1 -1
  101. package/dist/server/worker/chai-image.d.ts +1 -2
  102. package/dist/server/worker/chai-image.js +4 -3
  103. package/dist/server/worker/chai-image.js.map +1 -1
  104. package/dist/server/worker/start.js +27 -39
  105. package/dist/server/worker/start.js.map +1 -1
  106. package/dist/types.d.ts +32 -21
  107. package/dist/types.js +13 -1
  108. package/dist/types.js.map +1 -1
  109. package/package.json +45 -38
  110. package/playwright.d.ts +2 -0
  111. package/selenium.d.ts +2 -0
  112. package/src/client/addon/components/Panel.tsx +2 -2
  113. package/src/client/addon/controller.ts +13 -6
  114. package/src/client/addon/withCreevey.ts +25 -13
  115. package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
  116. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
  117. package/src/client/shared/components/ResultsPage.tsx +28 -7
  118. package/src/client/web/CreeveyApp.tsx +25 -7
  119. package/src/client/web/CreeveyContext.tsx +9 -0
  120. package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
  121. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
  122. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
  123. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
  124. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
  125. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  126. package/src/creevey.ts +13 -6
  127. package/src/index.ts +0 -2
  128. package/src/playwright.ts +1 -0
  129. package/src/selenium.ts +1 -0
  130. package/src/server/config.ts +19 -7
  131. package/src/server/docker.ts +2 -2
  132. package/src/server/index.ts +45 -4
  133. package/src/server/logger.ts +6 -2
  134. package/src/server/master/api.ts +1 -1
  135. package/src/server/master/pool.ts +18 -56
  136. package/src/server/master/queue.ts +64 -0
  137. package/src/server/master/runner.ts +4 -1
  138. package/src/server/master/server.ts +1 -1
  139. package/src/server/master/start.ts +7 -4
  140. package/src/server/playwright/docker-file.ts +14 -1
  141. package/src/server/playwright/docker.ts +2 -12
  142. package/src/server/playwright/internal.ts +76 -49
  143. package/src/server/playwright/webdriver.ts +7 -2
  144. package/src/server/providers/browser.ts +2 -1
  145. package/src/server/providers/hybrid.ts +1 -1
  146. package/src/server/reporter.ts +4 -3
  147. package/src/server/selenium/internal.ts +147 -93
  148. package/src/server/selenium/selenoid.ts +2 -2
  149. package/src/server/selenium/webdriver.ts +12 -2
  150. package/src/server/telemetry.ts +7 -3
  151. package/src/server/utils.ts +14 -4
  152. package/src/server/webdriver.ts +13 -15
  153. package/src/server/worker/chai-image.ts +4 -4
  154. package/src/server/worker/start.ts +29 -48
  155. package/src/types.ts +35 -23
  156. package/types/playwright-context.d.ts +7 -0
  157. package/types/selenium-context.d.ts +7 -0
  158. package/.yarnrc.yml +0 -1
  159. package/chromatic.config.json +0 -5
@@ -1,5 +1,4 @@
1
1
  import { Args } from '@storybook/csf';
2
- import { SET_GLOBALS, UPDATE_STORY_ARGS, STORY_RENDERED } from '@storybook/core-events';
3
2
  import chalk from 'chalk';
4
3
  import http from 'http';
5
4
  import https from 'https';
@@ -13,10 +12,20 @@ import { Builder, By, Capabilities, Origin, WebDriver, WebElement, logging } fro
13
12
  // import { Options as SafariOptions } from 'selenium-webdriver/safari';
14
13
  // import { Options as FirefoxOptions } from 'selenium-webdriver/firefox';
15
14
  import { PageLoadStrategy } from 'selenium-webdriver/lib/capabilities.js';
16
- import { BrowserConfigObject, Config, noop, StorybookGlobals, StoryInput, StoriesRaw, Options } from '../../types.js';
15
+ import {
16
+ BrowserConfigObject,
17
+ Config,
18
+ noop,
19
+ StorybookGlobals,
20
+ StoryInput,
21
+ StoriesRaw,
22
+ Options,
23
+ ServerTest,
24
+ StorybookEvents,
25
+ } from '../../types.js';
17
26
  import { colors, logger } from '../logger.js';
18
27
  import { subscribeOn } from '../messages.js';
19
- import { isShuttingDown, runSequence } from '../utils.js';
28
+ import { getTestPath, isShuttingDown, runSequence } from '../utils.js';
20
29
  import {
21
30
  appendIframePath,
22
31
  getAddresses,
@@ -85,21 +94,25 @@ async function openUrlAndWaitForPageSource(
85
94
  }
86
95
 
87
96
  async function buildWebdriver(
88
- browserName: string,
97
+ browser: string,
89
98
  gridUrl: string,
90
99
  config: Config,
91
100
  options: Options,
92
101
  ): Promise<WebDriver | null> {
93
- const browserConfig = config.browsers[browserName] as BrowserConfigObject;
94
- const { _storybookGlobals, /*customizeBuilder,*/ ...userCapabilities } = browserConfig;
102
+ const browserConfig = config.browsers[browser] as BrowserConfigObject;
103
+ const { /*customizeBuilder,*/ seleniumCapabilities, browserName } = browserConfig;
95
104
 
96
105
  const url = new URL(gridUrl);
97
106
  url.username = url.username ? '********' : '';
98
107
  url.password = url.password ? '********' : '';
99
- logger.debug(`(${browserName}) Connecting to Selenium ${chalk.magenta(url.toString())}`);
108
+ logger().debug(`Connecting to Selenium ${chalk.magenta(url.toString())}`);
100
109
 
101
110
  // TODO Define some capabilities explicitly and define typings
102
- const capabilities = new Capabilities({ ...userCapabilities, pageLoadStrategy: PageLoadStrategy.EAGER });
111
+ const capabilities = new Capabilities({
112
+ browserName,
113
+ ...seleniumCapabilities,
114
+ pageLoadStrategy: PageLoadStrategy.EAGER,
115
+ });
103
116
  const prefs = new logging.Preferences();
104
117
 
105
118
  if (options.trace) {
@@ -112,37 +125,65 @@ async function buildWebdriver(
112
125
  // TODO Validate browsers, versions, and platform
113
126
  // TODO Use `customizeBuilder`
114
127
 
115
- let browser: WebDriver;
128
+ let webdriver: WebDriver | null;
116
129
 
117
130
  try {
118
- // const ie = new IeOptions();
119
- // const edge = new EdgeOptions();
120
- // const chrome = new ChromeOptions();
121
- // const safari = new SafariOptions();
122
- // const firefox = new FirefoxOptions();
123
- // edge.enableBidi();
124
- // chrome.enableBidi();
125
- // firefox.enableBidi();
126
-
127
- browser = await new Builder()
128
- // .setIeOptions(ie)
129
- // .setEdgeOptions(edge)
130
- // .setChromeOptions(chrome)
131
- // .setSafariOptions(safari)
132
- // .setFirefoxOptions(firefox)
133
- .usingServer(gridUrl)
134
- .withCapabilities(capabilities)
135
- .setLoggingPrefs(prefs) // NOTE: Should go last
136
- .build();
137
-
138
- // const id = await browser.getWindowHandle();
139
- // 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');
140
181
  } catch (error) {
141
- logger.error(`(${browserName}) Failed to start browser:`, error);
182
+ logger().error(`Failed to start browser:`, error);
142
183
  return null;
143
184
  }
144
185
 
145
- return browser;
186
+ return webdriver;
146
187
  }
147
188
 
148
189
  export class InternalBrowser {
@@ -150,13 +191,13 @@ export class InternalBrowser {
150
191
  #browser: WebDriver;
151
192
  #serverHost: string | null = null;
152
193
  #serverPort: number;
153
- #logger: Logger.Logger;
194
+ #storybookGlobals?: StorybookGlobals;
154
195
  #unsubscribe: () => void = noop;
155
196
  #keepAliveInterval: NodeJS.Timeout | null = null;
156
- constructor(browser: WebDriver, port: number, logger: Logger.Logger) {
197
+ constructor(browser: WebDriver, port: number, storybookGlobals?: StorybookGlobals) {
157
198
  this.#browser = browser;
158
199
  this.#serverPort = port;
159
- this.#logger = logger;
200
+ this.#storybookGlobals = storybookGlobals;
160
201
  this.#unsubscribe = subscribeOn('shutdown', () => {
161
202
  void this.closeBrowser();
162
203
  });
@@ -185,7 +226,7 @@ export class InternalBrowser {
185
226
 
186
227
  const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
187
228
 
188
- if (this.#logger.getLevel() <= Logger.levels.DEBUG) {
229
+ if (logger().getLevel() <= Logger.levels.DEBUG) {
189
230
  const { innerWidth, innerHeight } = await this.#browser.executeScript<{
190
231
  innerWidth: number;
191
232
  innerHeight: number;
@@ -195,16 +236,16 @@ export class InternalBrowser {
195
236
  innerHeight: window.innerHeight,
196
237
  };
197
238
  });
198
- this.#logger.debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
239
+ logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
199
240
  }
200
241
 
201
242
  try {
202
243
  if (!captureElement) {
203
- this.#logger.debug('Capturing viewport screenshot');
244
+ logger().debug('Capturing viewport screenshot');
204
245
  screenshot = await this.#browser.takeScreenshot();
205
- this.#logger.debug('Viewport screenshot is captured');
246
+ logger().debug('Viewport screenshot is captured');
206
247
  } else {
207
- this.#logger.debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
248
+ logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
208
249
  const rects = await this.#browser.executeScript<
209
250
  { elementRect: ElementRect; windowRect: ElementRect } | undefined
210
251
  >(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
@@ -243,11 +284,11 @@ export class InternalBrowser {
243
284
  elementRect.height + elementRect.top <= windowRect.height;
244
285
 
245
286
  if (isFitIntoViewport) {
246
- this.#logger.debug(
287
+ logger().debug(
247
288
  `Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
248
289
  );
249
290
  } else
250
- this.#logger.debug(
291
+ logger().debug(
251
292
  `Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
252
293
  );
253
294
 
@@ -263,7 +304,7 @@ export class InternalBrowser {
263
304
  : // TODO pointer-events: none, need to research
264
305
  await this.takeCompositeScreenshot(windowRect, elementRect);
265
306
 
266
- this.#logger.debug(`${chalk.cyan(captureElement)} is captured`);
307
+ logger().debug(`${chalk.cyan(captureElement)} is captured`);
267
308
  }
268
309
  } finally {
269
310
  await this.removeIgnoreStyles(ignoreStyles);
@@ -282,10 +323,11 @@ export class InternalBrowser {
282
323
 
283
324
  async selectStory(id: string, waitForReady = false): Promise<boolean> {
284
325
  // NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
326
+ await this.updateStorybookGlobals();
285
327
  await this.updateBrowserGlobalVariables();
286
328
  await this.resetMousePosition();
287
329
 
288
- this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
330
+ logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
289
331
 
290
332
  const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
291
333
  function (
@@ -329,8 +371,8 @@ export class InternalBrowser {
329
371
  },
330
372
  story.id,
331
373
  updatedArgs,
332
- UPDATE_STORY_ARGS,
333
- STORY_RENDERED,
374
+ StorybookEvents.UPDATE_STORY_ARGS,
375
+ StorybookEvents.STORY_RENDERED,
334
376
  );
335
377
  }
336
378
 
@@ -346,6 +388,24 @@ export class InternalBrowser {
346
388
  return stories;
347
389
  }
348
390
 
391
+ async afterTest(test: ServerTest): Promise<void> {
392
+ if (logger().getLevel() === Logger.levels.TRACE) {
393
+ const output: string[] = [];
394
+ const types = await this.#browser.manage().logs().getAvailableLogTypes();
395
+ for (const type of types) {
396
+ const logs = await this.#browser.manage().logs().get(type);
397
+ output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
398
+ }
399
+ logger().debug(
400
+ '----------',
401
+ getTestPath(test).join('/'),
402
+ '----------\n',
403
+ output.join('\n'),
404
+ '\n----------------------------------------------------------------------------------------------------',
405
+ );
406
+ }
407
+ }
408
+
349
409
  static async getBrowser(
350
410
  browserName: string,
351
411
  gridUrl: string,
@@ -360,7 +420,7 @@ export class InternalBrowser {
360
420
 
361
421
  if (!browser) return null;
362
422
 
363
- const internalBrowser = new InternalBrowser(browser, options.port, logger);
423
+ const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
364
424
 
365
425
  try {
366
426
  if (isShuttingDown.current) return null;
@@ -370,7 +430,6 @@ export class InternalBrowser {
370
430
  gridUrl,
371
431
  viewport,
372
432
  storybookUrl: address,
373
- storybookGlobals: _storybookGlobals,
374
433
  resolveStorybookUrl: config.resolveStorybookUrl,
375
434
  });
376
435
 
@@ -378,11 +437,12 @@ export class InternalBrowser {
378
437
  } catch (originalError) {
379
438
  void internalBrowser.closeBrowser();
380
439
 
381
- const message = originalError instanceof Error ? originalError.message : (originalError as string);
382
- const error = new Error(`Can't load storybook root page by URL ${await browser.getCurrentUrl()}: ${message}`);
440
+ const message =
441
+ originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
442
+ const error = new Error(`Can't load storybook root page: ${message}`);
383
443
  if (originalError instanceof Error) error.stack = originalError.stack;
384
444
 
385
- logger.error(error);
445
+ logger().error(error);
386
446
 
387
447
  return null;
388
448
  }
@@ -393,14 +453,12 @@ export class InternalBrowser {
393
453
  gridUrl,
394
454
  viewport,
395
455
  storybookUrl,
396
- storybookGlobals,
397
456
  resolveStorybookUrl,
398
457
  }: {
399
458
  browserName: string;
400
459
  gridUrl: string;
401
460
  viewport?: { width: number; height: number };
402
461
  storybookUrl: string;
403
- storybookGlobals?: StorybookGlobals;
404
462
  resolveStorybookUrl?: () => Promise<string>;
405
463
  }): Promise<boolean> {
406
464
  const sessionId = (await this.#browser.getSession()).getId();
@@ -413,23 +471,21 @@ export class InternalBrowser {
413
471
  /* noop */
414
472
  }
415
473
 
416
- this.#logger = Logger.getLogger(sessionId);
417
-
418
- prefix.apply(this.#logger, {
474
+ prefix.apply(logger(), {
419
475
  format(level) {
420
476
  const levelColor = colors[level.toUpperCase() as keyof typeof colors];
421
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
477
+ return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
422
478
  },
423
479
  });
424
480
 
425
- this.#logger.debug(`Connected successful with ${chalk.green(browserHost)}`);
481
+ logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
426
482
 
427
483
  return await runSequence(
428
484
  [
429
- () => this.#browser.manage().setTimeouts({ pageLoad: 10000, script: 60000 }),
485
+ () => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
430
486
  () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
431
487
  () => this.waitForStorybook(),
432
- () => this.updateStorybookGlobals(storybookGlobals),
488
+ () => this.updateStorybookGlobals(),
433
489
  () => this.resolveCreeveyHost(),
434
490
  () => this.updateBrowserGlobalVariables(),
435
491
  // NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
@@ -451,19 +507,20 @@ export class InternalBrowser {
451
507
 
452
508
  try {
453
509
  if (resolver) {
454
- this.#logger.debug('Resolving storybook url with custom resolver');
510
+ logger().debug('Resolving storybook url with custom resolver');
455
511
 
456
512
  const resolvedUrl = await resolver();
457
513
 
458
- this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
514
+ logger().debug(`Resolver storybook url ${resolvedUrl}`);
459
515
 
460
516
  await this.#browser.get(appendIframePath(resolvedUrl));
461
517
  } else {
518
+ // TODO Pageload timeout 10s
462
519
  // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
463
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
520
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
464
521
  }
465
522
  } catch (error) {
466
- 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 : '');
467
524
  throw error;
468
525
  }
469
526
  }
@@ -471,13 +528,13 @@ export class InternalBrowser {
471
528
  private async checkUrl(url: string): Promise<boolean> {
472
529
  try {
473
530
  // NOTE: Before trying a new url, reset the current one
474
- this.#logger.debug(`Opening ${chalk.magenta('about:blank')} page`);
531
+ logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
475
532
  await openUrlAndWaitForPageSource(
476
533
  this.#browser,
477
534
  'about:blank',
478
535
  (source: string) => !source.includes('<body></body>'),
479
536
  );
480
- this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
537
+ logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
481
538
  const source = await openUrlAndWaitForPageSource(
482
539
  this.#browser,
483
540
  url,
@@ -489,7 +546,7 @@ export class InternalBrowser {
489
546
  // because other add significant delay and some of them don't work in earlier chrome versions
490
547
  // Browsers always load page successful even it's failed
491
548
  // So we just check `root` element
492
- this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
549
+ logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
493
550
  return source.includes(`id="${storybookRootID}"`);
494
551
  } catch {
495
552
  return false;
@@ -497,7 +554,7 @@ export class InternalBrowser {
497
554
  }
498
555
 
499
556
  private async waitForStorybook(): Promise<void> {
500
- 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');
501
558
 
502
559
  const isTimeout = await Promise.race([
503
560
  new Promise<boolean>((resolve) => {
@@ -508,19 +565,15 @@ export class InternalBrowser {
508
565
  (async () => {
509
566
  let wait = true;
510
567
  do {
511
- try {
512
- // TODO Research a different way to ensure storybook is initiated
513
- wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
514
- // TODO Maybe use
515
- // import { global } from '@storybook/global';
516
- // global.IS_STORYBOOK
517
- if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
518
- if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
519
- return false;
520
- }, SET_GLOBALS);
521
- } catch (e: unknown) {
522
- this.#logger.debug('An error has been caught during the script:', e);
523
- }
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);
524
577
  } while (wait);
525
578
  return false;
526
579
  })(),
@@ -530,13 +583,13 @@ export class InternalBrowser {
530
583
  if (isTimeout) throw new Error('Failed to wait `setStories` event');
531
584
  }
532
585
 
533
- private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
534
- if (!globals) return;
586
+ private async updateStorybookGlobals(): Promise<void> {
587
+ if (!this.#storybookGlobals) return;
535
588
 
536
- this.#logger.debug('Applying storybook globals');
589
+ logger().debug('Applying storybook globals');
537
590
  await this.#browser.executeScript(function (globals: StorybookGlobals) {
538
591
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
539
- }, globals);
592
+ }, this.#storybookGlobals);
540
593
  }
541
594
 
542
595
  private async resolveCreeveyHost(): Promise<void> {
@@ -603,7 +656,7 @@ export class InternalBrowser {
603
656
  },
604
657
  );
605
658
 
606
- 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}`);
607
660
 
608
661
  const dWidth = windowRect.width - innerWidth;
609
662
  const dHeight = windowRect.height - innerHeight;
@@ -617,7 +670,7 @@ export class InternalBrowser {
617
670
  }
618
671
 
619
672
  private async resetMousePosition(): Promise<void> {
620
- this.#logger.debug('Resetting mouse position to the top-left corner');
673
+ logger().debug('Resetting mouse position to the top-left corner');
621
674
  const browserName = (await this.#browser.getCapabilities()).getBrowserName();
622
675
  const [browserVersion] =
623
676
  (await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
@@ -645,8 +698,9 @@ export class InternalBrowser {
645
698
  y: Math.ceil((-1 * height) / 2) - top,
646
699
  })
647
700
  .perform();
648
- } else if (browserName == 'firefox' && browserVersion == '61') {
701
+ } else if (browserName == 'firefox') {
649
702
  // NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
703
+ // NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
650
704
  await this.#browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
651
705
  } else {
652
706
  // NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
@@ -658,7 +712,7 @@ export class InternalBrowser {
658
712
  const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
659
713
  if (!ignoreSelectors.length) return null;
660
714
 
661
- this.#logger.debug('Hiding ignored elements before capturing');
715
+ logger().debug('Hiding ignored elements before capturing');
662
716
 
663
717
  return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
664
718
  return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
@@ -734,12 +788,12 @@ export class InternalBrowser {
734
788
  compositeImage.data[i + 3] = image.data[j + 3];
735
789
  }
736
790
  }
737
- return compositeImage.data;
791
+ return PNG.sync.write(compositeImage);
738
792
  }
739
793
 
740
794
  private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
741
795
  if (ignoreStyles) {
742
- this.#logger.debug('Revert hiding ignored elements');
796
+ logger().debug('Revert hiding ignored elements');
743
797
  await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
744
798
  window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
745
799
  }, ignoreStyles);
@@ -778,7 +832,7 @@ export class InternalBrowser {
778
832
  this.#keepAliveInterval = setInterval(() => {
779
833
  // NOTE Simple way to keep session alive
780
834
  void this.#browser.getCurrentUrl().then((url) => {
781
- logger.debug('current url', chalk.magenta(url));
835
+ logger().debug('current url', chalk.magenta(url));
782
836
  });
783
837
  }, 10 * 1000);
784
838
  }
@@ -30,7 +30,7 @@ async function createSelenoidConfig(
30
30
  browsers.forEach(
31
31
  ({
32
32
  browserName,
33
- browserVersion = 'latest',
33
+ seleniumCapabilities: { browserVersion = 'latest' } = {},
34
34
  dockerImage = `selenoid/${browserName}:${browserVersion}`,
35
35
  webdriverCommand = [],
36
36
  }) => {
@@ -117,7 +117,7 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
117
117
  browsers.forEach(
118
118
  ({
119
119
  browserName,
120
- browserVersion = 'latest',
120
+ seleniumCapabilities: { browserVersion = 'latest' } = {},
121
121
  limit: browserLimit = 1,
122
122
  dockerImage = `selenoid/${browserName}:${browserVersion}`,
123
123
  }) => {
@@ -1,5 +1,6 @@
1
+ /// <reference types="../../../types/selenium-context" />
1
2
  import { Args } from '@storybook/csf';
2
- import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options } from '../../types.js';
3
+ import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options, ServerTest } from '../../types.js';
3
4
  import { subscribeOn } from '../messages.js';
4
5
  import { CreeveyWebdriverBase } from '../webdriver.js';
5
6
  import type { InternalBrowser } from './internal.js';
@@ -62,7 +63,7 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
62
63
  try {
63
64
  return await import('./internal.js');
64
65
  } catch (error) {
65
- logger.error(error);
66
+ logger().error(error);
66
67
  return null;
67
68
  }
68
69
  })();
@@ -95,6 +96,15 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
95
96
  return this.#browser.loadStoriesFromBrowser();
96
97
  }
97
98
 
99
+ afterTest(test: ServerTest): Promise<void> {
100
+ if (!this.#browser) {
101
+ // TODO Describe the error
102
+ throw new Error('Browser is not initialized');
103
+ }
104
+
105
+ return this.#browser.afterTest(test);
106
+ }
107
+
98
108
  protected async takeScreenshot(
99
109
  captureElement: string | null,
100
110
  ignoreElements?: string | string[] | null,
@@ -154,12 +154,16 @@ export async function sendScreenshotsCount(
154
154
  name: name,
155
155
  gridUrl: browser.gridUrl ? sanitizeGridUrl(browser.gridUrl) : undefined,
156
156
  browserName: browser.browserName,
157
- browserVersion: browser.browserVersion,
158
- platformName: browser.platformName,
157
+ // @ts-expect-error Support old config version
158
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
159
+ browserVersion: browser.seleniumCapabilities?.browserVersion ?? browser.browserVersion,
160
+ // @ts-expect-error Support old config version
161
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
162
+ platformName: browser.seleniumCapabilities?.platformName ?? browser.platformName,
159
163
  viewport: browser.viewport,
160
164
  limit: browser.limit,
161
165
  dockerImage: browser.dockerImage,
162
- 'se:teamname': browser['se:teamname'],
166
+ 'se:teamname': browser.seleniumCapabilities?.['se:teamname'],
163
167
  }
164
168
  : browser,
165
169
  ]),
@@ -3,10 +3,9 @@ import { get } from 'https';
3
3
  import cluster from 'cluster';
4
4
  import { dirname } from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
- import { createRequire } from 'module';
7
6
  import { register as esmRegister } from 'tsx/esm/api';
8
7
  import { register as cjsRegister } from 'tsx/cjs/api';
9
- import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest } from '../types.js';
8
+ import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest, Worker } from '../types.js';
10
9
  import { emitShutdownMessage, sendShutdownMessage } from './messages.js';
11
10
 
12
11
  const importMetaUrl = pathToFileURL(__filename).href;
@@ -96,6 +95,17 @@ export async function shutdownWorkers(): Promise<void> {
96
95
  emitShutdownMessage();
97
96
  }
98
97
 
98
+ export function gracefullyKill(worker: Worker): void {
99
+ worker.isShuttingDown = true;
100
+ const timeout = setTimeout(() => {
101
+ worker.kill();
102
+ }, 10000);
103
+ worker.on('exit', () => {
104
+ clearTimeout(timeout);
105
+ });
106
+ sendShutdownMessage(worker);
107
+ }
108
+
99
109
  export async function getCreeveyCache(): Promise<string | undefined> {
100
110
  const { default: findCacheDir } = await import('find-cache-dir');
101
111
  return findCacheDir({ name: 'creevey', cwd: dirname(fileURLToPath(importMetaUrl)) });
@@ -175,10 +185,10 @@ export function readDirRecursive(dirPath: string): string[] {
175
185
  );
176
186
  }
177
187
 
178
- const _require = createRequire(importMetaUrl);
179
188
  export function tryToLoadTestsData(filename: string): Partial<Record<string, ServerTest>> | undefined {
180
189
  try {
181
- return _require(filename) as Partial<Record<string, ServerTest>>;
190
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
191
+ return require(filename) as Partial<Record<string, ServerTest>>;
182
192
  } catch {
183
193
  /* noop */
184
194
  }