creevey 0.10.0-beta.3 → 0.10.0-beta.31

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 (186) hide show
  1. package/dist/client/addon/components/Addon.js +17 -7
  2. package/dist/client/addon/components/Addon.js.map +1 -1
  3. package/dist/client/addon/components/Panel.js +2 -2
  4. package/dist/client/addon/components/Panel.js.map +1 -1
  5. package/dist/client/addon/components/Tools.js +17 -7
  6. package/dist/client/addon/components/Tools.js.map +1 -1
  7. package/dist/client/addon/withCreevey.d.ts +1 -0
  8. package/dist/client/addon/withCreevey.js +10 -1
  9. package/dist/client/addon/withCreevey.js.map +1 -1
  10. package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
  11. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  12. package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
  13. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  14. package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
  15. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  16. package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
  17. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  18. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
  19. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  20. package/dist/client/shared/components/PageHeader/PageHeader.js +20 -8
  21. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  22. package/dist/client/shared/components/ResultsPage.js +43 -13
  23. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  24. package/dist/client/shared/creeveyClientApi.js +8 -1
  25. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  26. package/dist/client/shared/helpers.d.ts +1 -3
  27. package/dist/client/shared/helpers.js +4 -19
  28. package/dist/client/shared/helpers.js.map +1 -1
  29. package/dist/client/web/CreeveyApp.js +41 -14
  30. package/dist/client/web/CreeveyApp.js.map +1 -1
  31. package/dist/client/web/CreeveyContext.d.ts +5 -0
  32. package/dist/client/web/CreeveyContext.js +20 -7
  33. package/dist/client/web/CreeveyContext.js.map +1 -1
  34. package/dist/client/web/CreeveyLoader.js +2 -2
  35. package/dist/client/web/CreeveyLoader.js.map +1 -1
  36. package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
  37. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  38. package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
  39. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  40. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
  41. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  42. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
  43. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  44. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
  45. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  46. package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
  47. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  48. package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
  49. package/dist/client/web/KeyboardEventsContext.js +79 -64
  50. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  51. package/dist/client/web/assets/index-Cs8IUTQs.js +595 -0
  52. package/dist/client/web/index.html +1 -1
  53. package/dist/client/web/index.js +17 -7
  54. package/dist/client/web/index.js.map +1 -1
  55. package/dist/client/web/themes.d.ts +2 -0
  56. package/dist/client/web/themes.js +22 -0
  57. package/dist/client/web/themes.js.map +1 -0
  58. package/dist/creevey.js +16 -9
  59. package/dist/creevey.js.map +1 -1
  60. package/dist/index.d.ts +1 -0
  61. package/dist/server/config.d.ts +1 -1
  62. package/dist/server/config.js +29 -7
  63. package/dist/server/config.js.map +1 -1
  64. package/dist/server/connection.d.ts +3 -0
  65. package/dist/server/connection.js +28 -0
  66. package/dist/server/connection.js.map +1 -0
  67. package/dist/server/docker.js +38 -21
  68. package/dist/server/docker.js.map +1 -1
  69. package/dist/server/index.js +63 -11
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/server/logger.d.ts +2 -1
  72. package/dist/server/logger.js +7 -3
  73. package/dist/server/logger.js.map +1 -1
  74. package/dist/server/master/api.js +1 -1
  75. package/dist/server/master/api.js.map +1 -1
  76. package/dist/server/master/pool.d.ts +4 -3
  77. package/dist/server/master/pool.js +12 -63
  78. package/dist/server/master/pool.js.map +1 -1
  79. package/dist/server/master/queue.d.ts +13 -0
  80. package/dist/server/master/queue.js +71 -0
  81. package/dist/server/master/queue.js.map +1 -0
  82. package/dist/server/master/runner.d.ts +1 -0
  83. package/dist/server/master/runner.js +4 -1
  84. package/dist/server/master/runner.js.map +1 -1
  85. package/dist/server/master/server.js +1 -1
  86. package/dist/server/master/server.js.map +1 -1
  87. package/dist/server/master/start.js +13 -11
  88. package/dist/server/master/start.js.map +1 -1
  89. package/dist/server/playwright/docker-file.d.ts +2 -1
  90. package/dist/server/playwright/docker-file.js +7 -5
  91. package/dist/server/playwright/docker-file.js.map +1 -1
  92. package/dist/server/playwright/internal.d.ts +5 -4
  93. package/dist/server/playwright/internal.js +91 -71
  94. package/dist/server/playwright/internal.js.map +1 -1
  95. package/dist/server/playwright/webdriver.d.ts +1 -1
  96. package/dist/server/playwright/webdriver.js +1 -1
  97. package/dist/server/playwright/webdriver.js.map +1 -1
  98. package/dist/server/providers/browser.js +6 -4
  99. package/dist/server/providers/browser.js.map +1 -1
  100. package/dist/server/providers/hybrid.js +1 -1
  101. package/dist/server/providers/hybrid.js.map +1 -1
  102. package/dist/server/reporter.js +13 -9
  103. package/dist/server/reporter.js.map +1 -1
  104. package/dist/server/selenium/internal.d.ts +3 -4
  105. package/dist/server/selenium/internal.js +127 -99
  106. package/dist/server/selenium/internal.js.map +1 -1
  107. package/dist/server/selenium/selenoid.js +9 -6
  108. package/dist/server/selenium/selenoid.js.map +1 -1
  109. package/dist/server/selenium/webdriver.d.ts +1 -1
  110. package/dist/server/selenium/webdriver.js +1 -1
  111. package/dist/server/selenium/webdriver.js.map +1 -1
  112. package/dist/server/telemetry.js +7 -3
  113. package/dist/server/telemetry.js.map +1 -1
  114. package/dist/server/testsFiles/parser.js +44 -2
  115. package/dist/server/testsFiles/parser.js.map +1 -1
  116. package/dist/server/utils.d.ts +20 -1
  117. package/dist/server/utils.js +82 -7
  118. package/dist/server/utils.js.map +1 -1
  119. package/dist/server/webdriver.d.ts +3 -4
  120. package/dist/server/webdriver.js +10 -9
  121. package/dist/server/webdriver.js.map +1 -1
  122. package/dist/server/worker/chai-image.d.ts +1 -2
  123. package/dist/server/worker/chai-image.js +4 -3
  124. package/dist/server/worker/chai-image.js.map +1 -1
  125. package/dist/server/worker/match-image.d.ts +4 -4
  126. package/dist/server/worker/match-image.js +7 -4
  127. package/dist/server/worker/match-image.js.map +1 -1
  128. package/dist/server/worker/start.js +24 -14
  129. package/dist/server/worker/start.js.map +1 -1
  130. package/dist/shared/index.d.ts +1 -1
  131. package/dist/types.d.ts +38 -13
  132. package/dist/types.js.map +1 -1
  133. package/docs/config.md +3 -0
  134. package/package.json +65 -63
  135. package/src/client/addon/components/Panel.tsx +2 -2
  136. package/src/client/addon/withCreevey.ts +8 -1
  137. package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
  138. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
  139. package/src/client/shared/components/PageHeader/PageHeader.tsx +4 -2
  140. package/src/client/shared/components/ResultsPage.tsx +31 -8
  141. package/src/client/shared/creeveyClientApi.ts +9 -1
  142. package/src/client/shared/helpers.ts +4 -24
  143. package/src/client/web/CreeveyApp.tsx +26 -8
  144. package/src/client/web/CreeveyContext.tsx +9 -0
  145. package/src/client/web/CreeveyLoader.tsx +1 -1
  146. package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
  147. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
  148. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
  149. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
  150. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
  151. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  152. package/src/client/web/themes.ts +24 -0
  153. package/src/creevey.ts +16 -10
  154. package/src/server/config.ts +30 -8
  155. package/src/server/connection.ts +26 -0
  156. package/src/server/docker.ts +42 -24
  157. package/src/server/index.ts +73 -14
  158. package/src/server/logger.ts +6 -2
  159. package/src/server/master/api.ts +1 -1
  160. package/src/server/master/pool.ts +22 -56
  161. package/src/server/master/queue.ts +77 -0
  162. package/src/server/master/runner.ts +4 -1
  163. package/src/server/master/server.ts +1 -1
  164. package/src/server/master/start.ts +16 -11
  165. package/src/server/playwright/docker-file.ts +8 -5
  166. package/src/server/playwright/internal.ts +91 -78
  167. package/src/server/playwright/webdriver.ts +2 -2
  168. package/src/server/providers/browser.ts +6 -4
  169. package/src/server/providers/hybrid.ts +1 -1
  170. package/src/server/reporter.ts +15 -9
  171. package/src/server/selenium/internal.ts +131 -107
  172. package/src/server/selenium/selenoid.ts +9 -7
  173. package/src/server/selenium/webdriver.ts +2 -2
  174. package/src/server/telemetry.ts +7 -3
  175. package/src/server/testsFiles/parser.ts +51 -1
  176. package/src/server/utils.ts +87 -8
  177. package/src/server/webdriver.ts +11 -16
  178. package/src/server/worker/chai-image.ts +4 -4
  179. package/src/server/worker/match-image.ts +12 -8
  180. package/src/server/worker/start.ts +25 -16
  181. package/src/shared/index.ts +1 -1
  182. package/src/types.ts +40 -15
  183. package/types/global.d.ts +1 -0
  184. package/.yarnrc.yml +0 -1
  185. package/chromatic.config.json +0 -5
  186. package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
@@ -22,23 +22,24 @@ 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
 
33
33
  runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
34
- testLogger.warn(chalk.cyan(test.titlePath().join('/')));
34
+ testLogger.warn(chalk.cyan(test.fullTitle()));
35
35
  });
36
36
  runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
37
- testLogger.info(chalk.cyan(test.titlePath().join('/')));
37
+ testLogger.info(chalk.cyan(test.fullTitle()), chalk.gray(`(${test.duration} ms)`));
38
38
  });
39
39
  runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
40
40
  testLogger.error(
41
- chalk.cyan(test.titlePath().join('/')),
41
+ chalk.cyan(test.fullTitle()),
42
+ chalk.gray(`(${test.duration} ms)`),
42
43
  '\n ',
43
44
  this.getErrors(
44
45
  error,
@@ -76,7 +77,11 @@ export class TeamcityReporter {
76
77
  const reporterOptions = options.reporterOptions.creevey;
77
78
 
78
79
  runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
79
- console.log(`##teamcity[testStarted name='${this.escape(test.title)}' flowId='${process.pid}']`);
80
+ console.log(`##teamcity[testStarted name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
81
+ });
82
+
83
+ runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
84
+ console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
80
85
  });
81
86
 
82
87
  runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error: Error) => {
@@ -84,6 +89,7 @@ export class TeamcityReporter {
84
89
  if (!image) return;
85
90
  const filePath = test
86
91
  .titlePath()
92
+ .slice(0, -1)
87
93
  .concat(name == browserName ? [] : [browserName])
88
94
  .map(this.escape)
89
95
  .join('/');
@@ -97,7 +103,7 @@ export class TeamcityReporter {
97
103
  );
98
104
  console.log(
99
105
  `##teamcity[testMetadata testName='${this.escape(
100
- test.title,
106
+ test.fullTitle(),
101
107
  )}' type='image' value='report/${filePath}/${fileName}' flowId='${process.pid}']`,
102
108
  );
103
109
  });
@@ -107,10 +113,10 @@ export class TeamcityReporter {
107
113
  // https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
108
114
 
109
115
  if (reporterOptions.willRetry)
110
- console.log(`##teamcity[testFinished name='${this.escape(test.title)}' flowId='${process.pid}']`);
116
+ console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
111
117
  else
112
118
  console.log(
113
- `##teamcity[testFailed name='${this.escape(test.title)}' message='${this.escape(
119
+ `##teamcity[testFailed name='${this.escape(test.fullTitle())}' message='${this.escape(
114
120
  error.message,
115
121
  )}' details='${this.escape(error.stack ?? '')}' flowId='${process.pid}']`,
116
122
  );
@@ -1,4 +1,4 @@
1
- import { Args } from '@storybook/csf';
1
+ import type { Args } from '@storybook/csf';
2
2
  import chalk from 'chalk';
3
3
  import http from 'http';
4
4
  import https from 'https';
@@ -24,7 +24,7 @@ import {
24
24
  StorybookEvents,
25
25
  } from '../../types.js';
26
26
  import { colors, logger } from '../logger.js';
27
- import { subscribeOn } from '../messages.js';
27
+ import { emitWorkerMessage, subscribeOn } from '../messages.js';
28
28
  import { getTestPath, isShuttingDown, runSequence } from '../utils.js';
29
29
  import {
30
30
  appendIframePath,
@@ -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 { _storybookGlobals, /*customizeBuilder,*/ ...userCapabilities } = 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({ ...userCapabilities, 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,13 @@ export class InternalBrowser {
159
191
  #browser: WebDriver;
160
192
  #serverHost: string | null = null;
161
193
  #serverPort: number;
162
- #logger: Logger.Logger;
194
+ #storybookGlobals?: StorybookGlobals;
163
195
  #unsubscribe: () => void = noop;
164
196
  #keepAliveInterval: NodeJS.Timeout | null = null;
165
- constructor(browser: WebDriver, port: number, logger: Logger.Logger) {
197
+ constructor(browser: WebDriver, port: number, storybookGlobals?: StorybookGlobals) {
166
198
  this.#browser = browser;
167
199
  this.#serverPort = port;
168
- this.#logger = logger;
200
+ this.#storybookGlobals = storybookGlobals;
169
201
  this.#unsubscribe = subscribeOn('shutdown', () => {
170
202
  void this.closeBrowser();
171
203
  });
@@ -194,7 +226,7 @@ export class InternalBrowser {
194
226
 
195
227
  const ignoreStyles = await this.insertIgnoreStyles(ignoreElements);
196
228
 
197
- if (this.#logger.getLevel() <= Logger.levels.DEBUG) {
229
+ if (logger().getLevel() <= Logger.levels.DEBUG) {
198
230
  const { innerWidth, innerHeight } = await this.#browser.executeScript<{
199
231
  innerWidth: number;
200
232
  innerHeight: number;
@@ -204,16 +236,16 @@ export class InternalBrowser {
204
236
  innerHeight: window.innerHeight,
205
237
  };
206
238
  });
207
- this.#logger.debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
239
+ logger().debug(`Viewport size is: ${innerWidth}x${innerHeight}`);
208
240
  }
209
241
 
210
242
  try {
211
243
  if (!captureElement) {
212
- this.#logger.debug('Capturing viewport screenshot');
244
+ logger().debug('Capturing viewport screenshot');
213
245
  screenshot = await this.#browser.takeScreenshot();
214
- this.#logger.debug('Viewport screenshot is captured');
246
+ logger().debug('Viewport screenshot is captured');
215
247
  } else {
216
- this.#logger.debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
248
+ logger().debug(`Checking is element ${chalk.cyan(captureElement)} fit into viewport`);
217
249
  const rects = await this.#browser.executeScript<
218
250
  { elementRect: ElementRect; windowRect: ElementRect } | undefined
219
251
  >(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
@@ -252,11 +284,11 @@ export class InternalBrowser {
252
284
  elementRect.height + elementRect.top <= windowRect.height;
253
285
 
254
286
  if (isFitIntoViewport) {
255
- this.#logger.debug(
287
+ logger().debug(
256
288
  `Capturing ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
257
289
  );
258
290
  } else
259
- this.#logger.debug(
291
+ logger().debug(
260
292
  `Capturing composite screenshot image of ${chalk.cyan(captureElement)} with size: ${elementRect.width}x${elementRect.height}`,
261
293
  );
262
294
 
@@ -272,7 +304,7 @@ export class InternalBrowser {
272
304
  : // TODO pointer-events: none, need to research
273
305
  await this.takeCompositeScreenshot(windowRect, elementRect);
274
306
 
275
- this.#logger.debug(`${chalk.cyan(captureElement)} is captured`);
307
+ logger().debug(`${chalk.cyan(captureElement)} is captured`);
276
308
  }
277
309
  } finally {
278
310
  await this.removeIgnoreStyles(ignoreStyles);
@@ -291,10 +323,11 @@ export class InternalBrowser {
291
323
 
292
324
  async selectStory(id: string, waitForReady = false): Promise<boolean> {
293
325
  // NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
326
+ await this.updateStorybookGlobals();
294
327
  await this.updateBrowserGlobalVariables();
295
328
  await this.resetMousePosition();
296
329
 
297
- this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
330
+ logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
298
331
 
299
332
  const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
300
333
  function (
@@ -356,14 +389,14 @@ export class InternalBrowser {
356
389
  }
357
390
 
358
391
  async afterTest(test: ServerTest): Promise<void> {
359
- if (this.#logger.getLevel() === Logger.levels.TRACE) {
392
+ if (logger().getLevel() === Logger.levels.TRACE) {
360
393
  const output: string[] = [];
361
394
  const types = await this.#browser.manage().logs().getAvailableLogTypes();
362
395
  for (const type of types) {
363
396
  const logs = await this.#browser.manage().logs().get(type);
364
397
  output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
365
398
  }
366
- this.#logger.debug(
399
+ logger().debug(
367
400
  '----------',
368
401
  getTestPath(test).join('/'),
369
402
  '----------\n',
@@ -387,7 +420,7 @@ export class InternalBrowser {
387
420
 
388
421
  if (!browser) return null;
389
422
 
390
- const internalBrowser = new InternalBrowser(browser, options.port, logger);
423
+ const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
391
424
 
392
425
  try {
393
426
  if (isShuttingDown.current) return null;
@@ -397,19 +430,18 @@ export class InternalBrowser {
397
430
  gridUrl,
398
431
  viewport,
399
432
  storybookUrl: address,
400
- storybookGlobals: _storybookGlobals,
401
- resolveStorybookUrl: config.resolveStorybookUrl,
402
433
  });
403
434
 
404
435
  return done ? internalBrowser : null;
405
436
  } catch (originalError) {
406
437
  void internalBrowser.closeBrowser();
407
438
 
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}`);
439
+ const message =
440
+ originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
441
+ const error = new Error(`Can't load storybook root page: ${message}`);
410
442
  if (originalError instanceof Error) error.stack = originalError.stack;
411
443
 
412
- logger.error(error);
444
+ logger().error(error);
413
445
 
414
446
  return null;
415
447
  }
@@ -420,15 +452,11 @@ export class InternalBrowser {
420
452
  gridUrl,
421
453
  viewport,
422
454
  storybookUrl,
423
- storybookGlobals,
424
- resolveStorybookUrl,
425
455
  }: {
426
456
  browserName: string;
427
457
  gridUrl: string;
428
458
  viewport?: { width: number; height: number };
429
459
  storybookUrl: string;
430
- storybookGlobals?: StorybookGlobals;
431
- resolveStorybookUrl?: () => Promise<string>;
432
460
  }): Promise<boolean> {
433
461
  const sessionId = (await this.#browser.getSession()).getId();
434
462
  let browserHost = '';
@@ -440,23 +468,21 @@ export class InternalBrowser {
440
468
  /* noop */
441
469
  }
442
470
 
443
- this.#logger = Logger.getLogger(sessionId);
444
-
445
- prefix.apply(this.#logger, {
471
+ prefix.apply(logger(), {
446
472
  format(level) {
447
473
  const levelColor = colors[level.toUpperCase() as keyof typeof colors];
448
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
474
+ return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
449
475
  },
450
476
  });
451
477
 
452
- this.#logger.debug(`Connected successful with ${chalk.green(browserHost)}`);
478
+ logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
453
479
 
454
480
  return await runSequence(
455
481
  [
456
- () => this.#browser.manage().setTimeouts({ pageLoad: 10000, script: 60000 }),
457
- () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
482
+ () => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
483
+ () => this.openStorybookPage(storybookUrl),
458
484
  () => this.waitForStorybook(),
459
- () => this.updateStorybookGlobals(storybookGlobals),
485
+ () => this.updateStorybookGlobals(),
460
486
  () => this.resolveCreeveyHost(),
461
487
  () => this.updateBrowserGlobalVariables(),
462
488
  // NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
@@ -471,26 +497,17 @@ export class InternalBrowser {
471
497
  );
472
498
  }
473
499
 
474
- private async openStorybookPage(storybookUrl: string, resolver?: () => Promise<string>): Promise<void> {
500
+ private async openStorybookPage(storybookUrl: string): Promise<void> {
475
501
  if (!LOCALHOST_REGEXP.test(storybookUrl)) {
476
502
  return this.#browser.get(appendIframePath(storybookUrl));
477
503
  }
478
504
 
479
505
  try {
480
- if (resolver) {
481
- this.#logger.debug('Resolving storybook url with custom resolver');
482
-
483
- const resolvedUrl = await resolver();
484
-
485
- this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
486
-
487
- await this.#browser.get(appendIframePath(resolvedUrl));
488
- } else {
489
- // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
490
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
491
- }
506
+ // TODO Pageload timeout 10s
507
+ // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
508
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
492
509
  } catch (error) {
493
- this.#logger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
510
+ logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
494
511
  throw error;
495
512
  }
496
513
  }
@@ -498,13 +515,13 @@ export class InternalBrowser {
498
515
  private async checkUrl(url: string): Promise<boolean> {
499
516
  try {
500
517
  // NOTE: Before trying a new url, reset the current one
501
- this.#logger.debug(`Opening ${chalk.magenta('about:blank')} page`);
518
+ logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
502
519
  await openUrlAndWaitForPageSource(
503
520
  this.#browser,
504
521
  'about:blank',
505
522
  (source: string) => !source.includes('<body></body>'),
506
523
  );
507
- this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
524
+ logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
508
525
  const source = await openUrlAndWaitForPageSource(
509
526
  this.#browser,
510
527
  url,
@@ -516,7 +533,7 @@ export class InternalBrowser {
516
533
  // because other add significant delay and some of them don't work in earlier chrome versions
517
534
  // Browsers always load page successful even it's failed
518
535
  // So we just check `root` element
519
- this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
536
+ logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
520
537
  return source.includes(`id="${storybookRootID}"`);
521
538
  } catch {
522
539
  return false;
@@ -524,7 +541,7 @@ export class InternalBrowser {
524
541
  }
525
542
 
526
543
  private async waitForStorybook(): Promise<void> {
527
- this.#logger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
544
+ logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
528
545
 
529
546
  const isTimeout = await Promise.race([
530
547
  new Promise<boolean>((resolve) => {
@@ -535,19 +552,15 @@ export class InternalBrowser {
535
552
  (async () => {
536
553
  let wait = true;
537
554
  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
- }
555
+ // TODO Research a different way to ensure storybook is initiated
556
+ wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
557
+ // TODO Maybe use
558
+ // import { global } from '@storybook/global';
559
+ // global.IS_STORYBOOK
560
+ if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
561
+ if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
562
+ return false;
563
+ }, StorybookEvents.SET_GLOBALS);
551
564
  } while (wait);
552
565
  return false;
553
566
  })(),
@@ -557,13 +570,13 @@ export class InternalBrowser {
557
570
  if (isTimeout) throw new Error('Failed to wait `setStories` event');
558
571
  }
559
572
 
560
- private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
561
- if (!globals) return;
573
+ private async updateStorybookGlobals(): Promise<void> {
574
+ if (!this.#storybookGlobals) return;
562
575
 
563
- this.#logger.debug('Applying storybook globals');
576
+ logger().debug('Applying storybook globals');
564
577
  await this.#browser.executeScript(function (globals: StorybookGlobals) {
565
578
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
566
- }, globals);
579
+ }, this.#storybookGlobals);
567
580
  }
568
581
 
569
582
  private async resolveCreeveyHost(): Promise<void> {
@@ -607,6 +620,7 @@ export class InternalBrowser {
607
620
  private async updateBrowserGlobalVariables() {
608
621
  await this.#browser.executeScript(
609
622
  function (workerId: number, creeveyHost: string, creeveyPort: number) {
623
+ window.__CREEVEY_ENV__ = true;
610
624
  window.__CREEVEY_WORKER_ID__ = workerId;
611
625
  window.__CREEVEY_SERVER_HOST__ = creeveyHost;
612
626
  window.__CREEVEY_SERVER_PORT__ = creeveyPort;
@@ -630,7 +644,7 @@ export class InternalBrowser {
630
644
  },
631
645
  );
632
646
 
633
- this.#logger.debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
647
+ logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
634
648
 
635
649
  const dWidth = windowRect.width - innerWidth;
636
650
  const dHeight = windowRect.height - innerHeight;
@@ -644,7 +658,7 @@ export class InternalBrowser {
644
658
  }
645
659
 
646
660
  private async resetMousePosition(): Promise<void> {
647
- this.#logger.debug('Resetting mouse position to the top-left corner');
661
+ logger().debug('Resetting mouse position to the top-left corner');
648
662
  const browserName = (await this.#browser.getCapabilities()).getBrowserName();
649
663
  const [browserVersion] =
650
664
  (await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
@@ -672,8 +686,9 @@ export class InternalBrowser {
672
686
  y: Math.ceil((-1 * height) / 2) - top,
673
687
  })
674
688
  .perform();
675
- } else if (browserName == 'firefox' && browserVersion == '61') {
689
+ } else if (browserName == 'firefox') {
676
690
  // NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
691
+ // NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
677
692
  await this.#browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
678
693
  } else {
679
694
  // NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
@@ -685,7 +700,7 @@ export class InternalBrowser {
685
700
  const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
686
701
  if (!ignoreSelectors.length) return null;
687
702
 
688
- this.#logger.debug('Hiding ignored elements before capturing');
703
+ logger().debug('Hiding ignored elements before capturing');
689
704
 
690
705
  return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
691
706
  return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
@@ -766,7 +781,7 @@ export class InternalBrowser {
766
781
 
767
782
  private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
768
783
  if (ignoreStyles) {
769
- this.#logger.debug('Revert hiding ignored elements');
784
+ logger().debug('Revert hiding ignored elements');
770
785
  await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
771
786
  window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
772
787
  }, ignoreStyles);
@@ -804,9 +819,18 @@ export class InternalBrowser {
804
819
  private keepAlive(): void {
805
820
  this.#keepAliveInterval = setInterval(() => {
806
821
  // NOTE Simple way to keep session alive
807
- void this.#browser.getCurrentUrl().then((url) => {
808
- logger.debug('current url', chalk.magenta(url));
809
- });
822
+ void this.#browser
823
+ .getCurrentUrl()
824
+ .then((url) => {
825
+ logger().debug('current url', chalk.magenta(url));
826
+ })
827
+ .catch((error: unknown) => {
828
+ logger().error(error);
829
+ emitWorkerMessage({
830
+ type: 'error',
831
+ payload: { subtype: 'browser', error: 'Failed to ping browser' },
832
+ });
833
+ });
810
834
  }, 10 * 1000);
811
835
  }
812
836
  }
@@ -2,9 +2,9 @@ import path from 'path';
2
2
  import assert from 'assert';
3
3
  import { lstatSync, existsSync } from 'fs';
4
4
  import { mkdir, writeFile, copyFile } from 'fs/promises';
5
- import sh from 'shelljs';
5
+ import { exec, chmod } from 'shelljs';
6
6
  import { Config, BrowserConfigObject } from '../../types.js';
7
- import { downloadBinary, getCreeveyCache } from '../utils.js';
7
+ import { downloadBinary, getCreeveyCache, killTree } from '../utils.js';
8
8
  import { pullImages, runImage } from '../docker.js';
9
9
  import { subscribeOn } from '../messages.js';
10
10
 
@@ -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
  }) => {
@@ -91,12 +91,12 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
91
91
 
92
92
  // TODO Download browser webdrivers
93
93
  try {
94
- if (process.platform != 'win32') sh.chmod('+x', binaryPath);
94
+ if (process.platform != 'win32') chmod('+x', binaryPath);
95
95
  } catch {
96
96
  /* noop */
97
97
  }
98
98
 
99
- const selenoidProcess = sh.exec(`${binaryPath} -conf ./browsers.json -disable-docker`, {
99
+ const selenoidProcess = exec(`${binaryPath} -conf ./browsers.json -disable-docker`, {
100
100
  async: true,
101
101
  cwd: selenoidConfigDir,
102
102
  });
@@ -106,7 +106,9 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
106
106
  selenoidProcess.stderr?.pipe(process.stderr);
107
107
  }
108
108
 
109
- subscribeOn('shutdown', () => selenoidProcess.kill());
109
+ subscribeOn('shutdown', () => {
110
+ if (selenoidProcess.pid) void killTree(selenoidProcess.pid);
111
+ });
110
112
  }
111
113
 
112
114
  export async function startSelenoidContainer(config: Config, debug: boolean): Promise<string> {
@@ -117,7 +119,7 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
117
119
  browsers.forEach(
118
120
  ({
119
121
  browserName,
120
- browserVersion = 'latest',
122
+ seleniumCapabilities: { browserVersion = 'latest' } = {},
121
123
  limit: browserLimit = 1,
122
124
  dockerImage = `selenoid/${browserName}:${browserVersion}`,
123
125
  }) => {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="../../../types/selenium-context" />
2
- import { Args } from '@storybook/csf';
2
+ import type { Args } from '@storybook/csf';
3
3
  import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options, ServerTest } from '../../types.js';
4
4
  import { subscribeOn } from '../messages.js';
5
5
  import { CreeveyWebdriverBase } from '../webdriver.js';
@@ -63,7 +63,7 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
63
63
  try {
64
64
  return await import('./internal.js');
65
65
  } catch (error) {
66
- logger.error(error);
66
+ logger().error(error);
67
67
  return null;
68
68
  }
69
69
  })();
@@ -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
  ]),