creevey 0.10.0-beta.4 → 0.10.0-beta.41

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 (227) hide show
  1. package/README.md +19 -41
  2. package/dist/client/addon/components/Addon.js +17 -7
  3. package/dist/client/addon/components/Addon.js.map +1 -1
  4. package/dist/client/addon/components/Panel.js +2 -2
  5. package/dist/client/addon/components/Panel.js.map +1 -1
  6. package/dist/client/addon/components/Tools.js +17 -7
  7. package/dist/client/addon/components/Tools.js.map +1 -1
  8. package/dist/client/addon/withCreevey.d.ts +2 -1
  9. package/dist/client/addon/withCreevey.js +11 -1
  10. package/dist/client/addon/withCreevey.js.map +1 -1
  11. package/dist/client/shared/components/ImagesView/BlendView.d.ts +1 -1
  12. package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
  13. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  14. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
  15. package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
  16. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  17. package/dist/client/shared/components/ImagesView/SlideView.d.ts +1 -1
  18. package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
  19. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  20. package/dist/client/shared/components/ImagesView/SwapView.d.ts +1 -1
  21. package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
  22. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  23. package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +1 -1
  24. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
  25. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  26. package/dist/client/shared/components/PageHeader/PageHeader.js +20 -8
  27. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  28. package/dist/client/shared/components/ResultsPage.d.ts +1 -1
  29. package/dist/client/shared/components/ResultsPage.js +43 -13
  30. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  31. package/dist/client/shared/creeveyClientApi.js +8 -1
  32. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  33. package/dist/client/shared/helpers.d.ts +1 -3
  34. package/dist/client/shared/helpers.js +4 -19
  35. package/dist/client/shared/helpers.js.map +1 -1
  36. package/dist/client/web/CreeveyApp.js +42 -14
  37. package/dist/client/web/CreeveyApp.js.map +1 -1
  38. package/dist/client/web/CreeveyContext.d.ts +5 -0
  39. package/dist/client/web/CreeveyContext.js +20 -7
  40. package/dist/client/web/CreeveyContext.js.map +1 -1
  41. package/dist/client/web/CreeveyLoader.js +2 -2
  42. package/dist/client/web/CreeveyLoader.js.map +1 -1
  43. package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
  44. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  45. package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
  46. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  47. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
  48. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  49. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
  50. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  51. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +2 -2
  52. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
  53. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  54. package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
  55. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  56. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +1 -1
  57. package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +1 -1
  58. package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
  59. package/dist/client/web/KeyboardEventsContext.js +79 -64
  60. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  61. package/dist/client/web/assets/index-C47njyZV.js +802 -0
  62. package/dist/client/web/index.html +1 -1
  63. package/dist/client/web/index.js +17 -7
  64. package/dist/client/web/index.js.map +1 -1
  65. package/dist/client/web/themes.d.ts +2 -0
  66. package/dist/client/web/themes.js +22 -0
  67. package/dist/client/web/themes.js.map +1 -0
  68. package/dist/creevey.js +16 -9
  69. package/dist/creevey.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/server/config.d.ts +1 -1
  72. package/dist/server/config.js +30 -7
  73. package/dist/server/config.js.map +1 -1
  74. package/dist/server/connection.d.ts +3 -0
  75. package/dist/server/connection.js +28 -0
  76. package/dist/server/connection.js.map +1 -0
  77. package/dist/server/docker.d.ts +1 -1
  78. package/dist/server/docker.js +56 -32
  79. package/dist/server/docker.js.map +1 -1
  80. package/dist/server/index.js +64 -11
  81. package/dist/server/index.js.map +1 -1
  82. package/dist/server/logger.d.ts +2 -1
  83. package/dist/server/logger.js +7 -3
  84. package/dist/server/logger.js.map +1 -1
  85. package/dist/server/master/api.js +1 -1
  86. package/dist/server/master/api.js.map +1 -1
  87. package/dist/server/master/pool.d.ts +4 -3
  88. package/dist/server/master/pool.js +13 -66
  89. package/dist/server/master/pool.js.map +1 -1
  90. package/dist/server/master/queue.d.ts +13 -0
  91. package/dist/server/master/queue.js +71 -0
  92. package/dist/server/master/queue.js.map +1 -0
  93. package/dist/server/master/runner.d.ts +3 -0
  94. package/dist/server/master/runner.js +78 -10
  95. package/dist/server/master/runner.js.map +1 -1
  96. package/dist/server/master/server.js +1 -1
  97. package/dist/server/master/server.js.map +1 -1
  98. package/dist/server/master/start.js +13 -11
  99. package/dist/server/master/start.js.map +1 -1
  100. package/dist/server/playwright/docker-file.d.ts +1 -1
  101. package/dist/server/playwright/docker-file.js +15 -6
  102. package/dist/server/playwright/docker-file.js.map +1 -1
  103. package/dist/server/playwright/docker.d.ts +2 -1
  104. package/dist/server/playwright/docker.js +10 -2
  105. package/dist/server/playwright/docker.js.map +1 -1
  106. package/dist/server/playwright/index-source.mjs +16 -0
  107. package/dist/server/playwright/internal.d.ts +6 -6
  108. package/dist/server/playwright/internal.js +143 -91
  109. package/dist/server/playwright/internal.js.map +1 -1
  110. package/dist/server/playwright/webdriver.d.ts +1 -1
  111. package/dist/server/playwright/webdriver.js +5 -8
  112. package/dist/server/playwright/webdriver.js.map +1 -1
  113. package/dist/server/providers/browser.js +6 -4
  114. package/dist/server/providers/browser.js.map +1 -1
  115. package/dist/server/providers/hybrid.js +1 -1
  116. package/dist/server/providers/hybrid.js.map +1 -1
  117. package/dist/server/reporters/creevey.d.ts +7 -0
  118. package/dist/server/reporters/creevey.js +63 -0
  119. package/dist/server/reporters/creevey.js.map +1 -0
  120. package/dist/server/reporters/index.d.ts +2 -0
  121. package/dist/server/reporters/index.js +16 -0
  122. package/dist/server/reporters/index.js.map +1 -0
  123. package/dist/server/reporters/junit.d.ts +16 -0
  124. package/dist/server/reporters/junit.js +165 -0
  125. package/dist/server/reporters/junit.js.map +1 -0
  126. package/dist/server/reporters/teamcity.d.ts +7 -0
  127. package/dist/server/reporters/teamcity.js +60 -0
  128. package/dist/server/reporters/teamcity.js.map +1 -0
  129. package/dist/server/selenium/internal.d.ts +3 -4
  130. package/dist/server/selenium/internal.js +127 -108
  131. package/dist/server/selenium/internal.js.map +1 -1
  132. package/dist/server/selenium/selenoid.js +8 -6
  133. package/dist/server/selenium/selenoid.js.map +1 -1
  134. package/dist/server/selenium/webdriver.d.ts +1 -1
  135. package/dist/server/selenium/webdriver.js +5 -9
  136. package/dist/server/selenium/webdriver.js.map +1 -1
  137. package/dist/server/telemetry.js +2 -2
  138. package/dist/server/testsFiles/parser.js +45 -5
  139. package/dist/server/testsFiles/parser.js.map +1 -1
  140. package/dist/server/utils.d.ts +19 -1
  141. package/dist/server/utils.js +87 -8
  142. package/dist/server/utils.js.map +1 -1
  143. package/dist/server/webdriver.d.ts +5 -4
  144. package/dist/server/webdriver.js +23 -10
  145. package/dist/server/webdriver.js.map +1 -1
  146. package/dist/server/worker/chai-image.d.ts +1 -2
  147. package/dist/server/worker/chai-image.js +4 -3
  148. package/dist/server/worker/chai-image.js.map +1 -1
  149. package/dist/server/worker/context.d.ts +3 -0
  150. package/dist/server/worker/context.js +15 -0
  151. package/dist/server/worker/context.js.map +1 -0
  152. package/dist/server/worker/match-image.d.ts +4 -4
  153. package/dist/server/worker/match-image.js +7 -4
  154. package/dist/server/worker/match-image.js.map +1 -1
  155. package/dist/server/worker/start.js +47 -73
  156. package/dist/server/worker/start.js.map +1 -1
  157. package/dist/shared/index.d.ts +1 -1
  158. package/dist/types.d.ts +46 -10
  159. package/dist/types.js +2 -0
  160. package/dist/types.js.map +1 -1
  161. package/docs/cli.md +12 -0
  162. package/docs/config.md +179 -165
  163. package/docs/storybook.md +60 -0
  164. package/docs/tests.md +50 -45
  165. package/package.json +64 -63
  166. package/src/client/addon/components/Panel.tsx +2 -2
  167. package/src/client/addon/withCreevey.ts +10 -2
  168. package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
  169. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
  170. package/src/client/shared/components/PageHeader/PageHeader.tsx +4 -2
  171. package/src/client/shared/components/ResultsPage.tsx +31 -8
  172. package/src/client/shared/creeveyClientApi.ts +9 -1
  173. package/src/client/shared/helpers.ts +4 -24
  174. package/src/client/web/CreeveyApp.tsx +27 -8
  175. package/src/client/web/CreeveyContext.tsx +9 -0
  176. package/src/client/web/CreeveyLoader.tsx +1 -1
  177. package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
  178. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
  179. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
  180. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
  181. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
  182. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  183. package/src/client/web/themes.ts +24 -0
  184. package/src/creevey.ts +16 -10
  185. package/src/server/config.ts +30 -7
  186. package/src/server/connection.ts +26 -0
  187. package/src/server/docker.ts +63 -34
  188. package/src/server/index.ts +72 -14
  189. package/src/server/logger.ts +6 -2
  190. package/src/server/master/api.ts +1 -1
  191. package/src/server/master/pool.ts +23 -59
  192. package/src/server/master/queue.ts +77 -0
  193. package/src/server/master/runner.ts +96 -10
  194. package/src/server/master/server.ts +1 -1
  195. package/src/server/master/start.ts +16 -11
  196. package/src/server/playwright/docker-file.ts +18 -6
  197. package/src/server/playwright/docker.ts +16 -3
  198. package/src/server/playwright/index-source.mjs +16 -0
  199. package/src/server/playwright/internal.ts +182 -111
  200. package/src/server/playwright/webdriver.ts +6 -9
  201. package/src/server/providers/browser.ts +6 -4
  202. package/src/server/providers/hybrid.ts +1 -1
  203. package/src/server/reporters/creevey.ts +71 -0
  204. package/src/server/reporters/index.ts +11 -0
  205. package/src/server/reporters/junit.ts +205 -0
  206. package/src/server/reporters/teamcity.ts +74 -0
  207. package/src/server/selenium/internal.ts +131 -116
  208. package/src/server/selenium/selenoid.ts +8 -6
  209. package/src/server/selenium/webdriver.ts +6 -10
  210. package/src/server/telemetry.ts +2 -2
  211. package/src/server/testsFiles/parser.ts +52 -4
  212. package/src/server/utils.ts +97 -9
  213. package/src/server/webdriver.ts +24 -16
  214. package/src/server/worker/chai-image.ts +4 -4
  215. package/src/server/worker/context.ts +14 -0
  216. package/src/server/worker/match-image.ts +12 -8
  217. package/src/server/worker/start.ts +51 -86
  218. package/src/shared/index.ts +1 -1
  219. package/src/types.ts +50 -11
  220. package/types/global.d.ts +1 -0
  221. package/.yarnrc.yml +0 -1
  222. package/chromatic.config.json +0 -5
  223. package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
  224. package/dist/server/reporter.d.ts +0 -26
  225. package/dist/server/reporter.js +0 -108
  226. package/dist/server/reporter.js.map +0 -1
  227. package/src/server/reporter.ts +0 -138
@@ -1,4 +1,4 @@
1
- import { Args } from '@storybook/csf';
1
+ import type { Args } from '@storybook/types';
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 { /*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,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,20 +236,20 @@ 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 {
220
- window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition`
252
+ window.scrollTo(0, 0);
221
253
  // eslint-disable-next-line no-var
222
254
  var element = document.querySelector(selector);
223
255
  if (!element) return;
@@ -234,9 +266,7 @@ export class InternalBrowser {
234
266
  },
235
267
  // NOTE page_Offset is used only for IE9-11
236
268
  windowRect: {
237
- // eslint-disable-next-line @typescript-eslint/no-deprecated
238
269
  top: Math.round(window.scrollY || window.pageYOffset),
239
- // eslint-disable-next-line @typescript-eslint/no-deprecated
240
270
  left: Math.round(window.scrollX || window.pageXOffset),
241
271
  width: window.innerWidth,
242
272
  height: window.innerHeight,
@@ -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
 
@@ -265,14 +295,12 @@ export class InternalBrowser {
265
295
  // ? context
266
296
  // ? await context.captureElementScreenshot(await element.getId())
267
297
  // : await browser.findElement(By.css(captureElement)).takeScreenshot()
268
- // : // TODO pointer-events: none, need to research
269
- // await takeCompositeScreenshot(browser, windowRect, elementRect);
298
+ // : await takeCompositeScreenshot(browser, windowRect, elementRect);
270
299
  screenshot = isFitIntoViewport
271
300
  ? await this.#browser.findElement(By.css(captureElement)).takeScreenshot()
272
- : // TODO pointer-events: none, need to research
273
- await this.takeCompositeScreenshot(windowRect, elementRect);
301
+ : await this.takeCompositeScreenshot(windowRect, elementRect);
274
302
 
275
- this.#logger.debug(`${chalk.cyan(captureElement)} is captured`);
303
+ logger().debug(`${chalk.cyan(captureElement)} is captured`);
276
304
  }
277
305
  } finally {
278
306
  await this.removeIgnoreStyles(ignoreStyles);
@@ -291,10 +319,11 @@ export class InternalBrowser {
291
319
 
292
320
  async selectStory(id: string, waitForReady = false): Promise<boolean> {
293
321
  // NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
322
+ await this.updateStorybookGlobals();
294
323
  await this.updateBrowserGlobalVariables();
295
324
  await this.resetMousePosition();
296
325
 
297
- this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
326
+ logger().debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
298
327
 
299
328
  const result = await this.#browser.executeAsyncScript<[error?: string | null, isCaptureCalled?: boolean] | null>(
300
329
  function (
@@ -356,14 +385,14 @@ export class InternalBrowser {
356
385
  }
357
386
 
358
387
  async afterTest(test: ServerTest): Promise<void> {
359
- if (this.#logger.getLevel() === Logger.levels.TRACE) {
388
+ if (logger().getLevel() === Logger.levels.TRACE) {
360
389
  const output: string[] = [];
361
390
  const types = await this.#browser.manage().logs().getAvailableLogTypes();
362
391
  for (const type of types) {
363
392
  const logs = await this.#browser.manage().logs().get(type);
364
393
  output.push(logs.map((log) => JSON.stringify(log.toJSON(), null, 2)).join('\n'));
365
394
  }
366
- this.#logger.debug(
395
+ logger().debug(
367
396
  '----------',
368
397
  getTestPath(test).join('/'),
369
398
  '----------\n',
@@ -387,7 +416,7 @@ export class InternalBrowser {
387
416
 
388
417
  if (!browser) return null;
389
418
 
390
- const internalBrowser = new InternalBrowser(browser, options.port, logger);
419
+ const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
391
420
 
392
421
  try {
393
422
  if (isShuttingDown.current) return null;
@@ -397,19 +426,18 @@ export class InternalBrowser {
397
426
  gridUrl,
398
427
  viewport,
399
428
  storybookUrl: address,
400
- storybookGlobals: _storybookGlobals,
401
- resolveStorybookUrl: config.resolveStorybookUrl,
402
429
  });
403
430
 
404
431
  return done ? internalBrowser : null;
405
432
  } catch (originalError) {
406
433
  void internalBrowser.closeBrowser();
407
434
 
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}`);
435
+ const message =
436
+ originalError instanceof Error ? originalError.message : ((originalError ?? 'Unknown error') as string);
437
+ const error = new Error(`Can't load storybook root page: ${message}`);
410
438
  if (originalError instanceof Error) error.stack = originalError.stack;
411
439
 
412
- logger.error(error);
440
+ logger().error(error);
413
441
 
414
442
  return null;
415
443
  }
@@ -420,15 +448,11 @@ export class InternalBrowser {
420
448
  gridUrl,
421
449
  viewport,
422
450
  storybookUrl,
423
- storybookGlobals,
424
- resolveStorybookUrl,
425
451
  }: {
426
452
  browserName: string;
427
453
  gridUrl: string;
428
454
  viewport?: { width: number; height: number };
429
455
  storybookUrl: string;
430
- storybookGlobals?: StorybookGlobals;
431
- resolveStorybookUrl?: () => Promise<string>;
432
456
  }): Promise<boolean> {
433
457
  const sessionId = (await this.#browser.getSession()).getId();
434
458
  let browserHost = '';
@@ -440,23 +464,21 @@ export class InternalBrowser {
440
464
  /* noop */
441
465
  }
442
466
 
443
- this.#logger = Logger.getLogger(sessionId);
444
-
445
- prefix.apply(this.#logger, {
467
+ prefix.apply(logger(), {
446
468
  format(level) {
447
469
  const levelColor = colors[level.toUpperCase() as keyof typeof colors];
448
- return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
470
+ return `[${browserName}:${chalk.gray(process.pid)}] ${levelColor(level)} => ${chalk.gray(sessionId)}`;
449
471
  },
450
472
  });
451
473
 
452
- this.#logger.debug(`Connected successful with ${chalk.green(browserHost)}`);
474
+ logger().debug(`Connected successful with ${chalk.green(browserHost)}`);
453
475
 
454
476
  return await runSequence(
455
477
  [
456
- () => this.#browser.manage().setTimeouts({ pageLoad: 10000, script: 60000 }),
457
- () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
478
+ () => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
479
+ () => this.openStorybookPage(storybookUrl),
458
480
  () => this.waitForStorybook(),
459
- () => this.updateStorybookGlobals(storybookGlobals),
481
+ () => this.updateStorybookGlobals(),
460
482
  () => this.resolveCreeveyHost(),
461
483
  () => this.updateBrowserGlobalVariables(),
462
484
  // NOTE: Selenium draws automation toolbar with some delay after webdriver initialization
@@ -471,26 +493,16 @@ export class InternalBrowser {
471
493
  );
472
494
  }
473
495
 
474
- private async openStorybookPage(storybookUrl: string, resolver?: () => Promise<string>): Promise<void> {
496
+ private async openStorybookPage(storybookUrl: string): Promise<void> {
475
497
  if (!LOCALHOST_REGEXP.test(storybookUrl)) {
476
498
  return this.#browser.get(appendIframePath(storybookUrl));
477
499
  }
478
500
 
479
501
  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
- }
502
+ // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
503
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
492
504
  } catch (error) {
493
- this.#logger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
505
+ logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
494
506
  throw error;
495
507
  }
496
508
  }
@@ -498,13 +510,13 @@ export class InternalBrowser {
498
510
  private async checkUrl(url: string): Promise<boolean> {
499
511
  try {
500
512
  // NOTE: Before trying a new url, reset the current one
501
- this.#logger.debug(`Opening ${chalk.magenta('about:blank')} page`);
513
+ logger().debug(`Opening ${chalk.magenta('about:blank')} page`);
502
514
  await openUrlAndWaitForPageSource(
503
515
  this.#browser,
504
516
  'about:blank',
505
517
  (source: string) => !source.includes('<body></body>'),
506
518
  );
507
- this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
519
+ logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
508
520
  const source = await openUrlAndWaitForPageSource(
509
521
  this.#browser,
510
522
  url,
@@ -516,7 +528,7 @@ export class InternalBrowser {
516
528
  // because other add significant delay and some of them don't work in earlier chrome versions
517
529
  // Browsers always load page successful even it's failed
518
530
  // So we just check `root` element
519
- this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
531
+ logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
520
532
  return source.includes(`id="${storybookRootID}"`);
521
533
  } catch {
522
534
  return false;
@@ -524,7 +536,7 @@ export class InternalBrowser {
524
536
  }
525
537
 
526
538
  private async waitForStorybook(): Promise<void> {
527
- this.#logger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
539
+ logger().debug('Waiting for Storybook to initiate');
528
540
 
529
541
  const isTimeout = await Promise.race([
530
542
  new Promise<boolean>((resolve) => {
@@ -535,35 +547,27 @@ export class InternalBrowser {
535
547
  (async () => {
536
548
  let wait = true;
537
549
  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
- }
550
+ // TODO Research a different way to ensure storybook is initiated
551
+ wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
552
+ if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
553
+ if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
554
+ return false;
555
+ }, StorybookEvents.SET_GLOBALS);
551
556
  } while (wait);
552
557
  return false;
553
558
  })(),
554
559
  ]);
555
560
 
556
- // TODO Change the message to describe a reason why it might happen
557
- if (isTimeout) throw new Error('Failed to wait `setStories` event');
561
+ if (isTimeout) throw new Error('Failed to wait Storybook init');
558
562
  }
559
563
 
560
- private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
561
- if (!globals) return;
564
+ private async updateStorybookGlobals(): Promise<void> {
565
+ if (!this.#storybookGlobals) return;
562
566
 
563
- this.#logger.debug('Applying storybook globals');
567
+ logger().debug('Applying storybook globals');
564
568
  await this.#browser.executeScript(function (globals: StorybookGlobals) {
565
569
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
566
- }, globals);
570
+ }, this.#storybookGlobals);
567
571
  }
568
572
 
569
573
  private async resolveCreeveyHost(): Promise<void> {
@@ -607,6 +611,7 @@ export class InternalBrowser {
607
611
  private async updateBrowserGlobalVariables() {
608
612
  await this.#browser.executeScript(
609
613
  function (workerId: number, creeveyHost: string, creeveyPort: number) {
614
+ window.__CREEVEY_ENV__ = true;
610
615
  window.__CREEVEY_WORKER_ID__ = workerId;
611
616
  window.__CREEVEY_SERVER_HOST__ = creeveyHost;
612
617
  window.__CREEVEY_SERVER_PORT__ = creeveyPort;
@@ -630,7 +635,7 @@ export class InternalBrowser {
630
635
  },
631
636
  );
632
637
 
633
- this.#logger.debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
638
+ logger().debug(`Resizing viewport from ${innerWidth}x${innerHeight} to ${viewport.width}x${viewport.height}`);
634
639
 
635
640
  const dWidth = windowRect.width - innerWidth;
636
641
  const dHeight = windowRect.height - innerHeight;
@@ -644,7 +649,7 @@ export class InternalBrowser {
644
649
  }
645
650
 
646
651
  private async resetMousePosition(): Promise<void> {
647
- this.#logger.debug('Resetting mouse position to the top-left corner');
652
+ logger().debug('Resetting mouse position to the top-left corner');
648
653
  const browserName = (await this.#browser.getCapabilities()).getBrowserName();
649
654
  const [browserVersion] =
650
655
  (await this.#browser.getCapabilities()).getBrowserVersion()?.split('.') ??
@@ -672,8 +677,9 @@ export class InternalBrowser {
672
677
  y: Math.ceil((-1 * height) / 2) - top,
673
678
  })
674
679
  .perform();
675
- } else if (browserName == 'firefox' && browserVersion == '61') {
680
+ } else if (browserName == 'firefox') {
676
681
  // NOTE Firefox for some reason moving by 0 x 0 move cursor in bottom left corner :sad:
682
+ // NOTE In recent versions (eg 128.0) moving by 0 x 0 doesn't work at all
677
683
  await this.#browser.actions().move({ origin: Origin.VIEWPORT, x: 0, y: 1 }).perform();
678
684
  } else {
679
685
  // NOTE IE don't emit move events until force window focus or connect by RDP on virtual machine
@@ -685,7 +691,7 @@ export class InternalBrowser {
685
691
  const ignoreSelectors = Array.prototype.concat(ignoreElements).filter(Boolean);
686
692
  if (!ignoreSelectors.length) return null;
687
693
 
688
- this.#logger.debug('Hiding ignored elements before capturing');
694
+ logger().debug('Hiding ignored elements before capturing');
689
695
 
690
696
  return await this.#browser.executeScript(function (ignoreSelectors: string[]) {
691
697
  return window.__CREEVEY_INSERT_IGNORE_STYLES__(ignoreSelectors);
@@ -766,7 +772,7 @@ export class InternalBrowser {
766
772
 
767
773
  private async removeIgnoreStyles(ignoreStyles: WebElement | null): Promise<void> {
768
774
  if (ignoreStyles) {
769
- this.#logger.debug('Revert hiding ignored elements');
775
+ logger().debug('Revert hiding ignored elements');
770
776
  await this.#browser.executeScript(function (ignoreStyles: HTMLStyleElement) {
771
777
  window.__CREEVEY_REMOVE_IGNORE_STYLES__(ignoreStyles);
772
778
  }, ignoreStyles);
@@ -804,9 +810,18 @@ export class InternalBrowser {
804
810
  private keepAlive(): void {
805
811
  this.#keepAliveInterval = setInterval(() => {
806
812
  // NOTE Simple way to keep session alive
807
- void this.#browser.getCurrentUrl().then((url) => {
808
- logger.debug('current url', chalk.magenta(url));
809
- });
813
+ void this.#browser
814
+ .getCurrentUrl()
815
+ .then((url) => {
816
+ logger().debug('current url', chalk.magenta(url));
817
+ })
818
+ .catch((error: unknown) => {
819
+ logger().error(error);
820
+ emitWorkerMessage({
821
+ type: 'error',
822
+ payload: { subtype: 'browser', error: 'Failed to ping browser' },
823
+ });
824
+ });
810
825
  }, 10 * 1000);
811
826
  }
812
827
  }
@@ -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
 
@@ -34,7 +34,7 @@ async function createSelenoidConfig(
34
34
  dockerImage = `selenoid/${browserName}:${browserVersion}`,
35
35
  webdriverCommand = [],
36
36
  }) => {
37
- if (!selenoidConfig[browserName]) selenoidConfig[browserName] = { default: browserVersion, versions: {} };
37
+ selenoidConfig[browserName] ??= { default: browserVersion, versions: {} };
38
38
  if (!useDocker && webdriverCommand.length == 0)
39
39
  throw new Error('Please specify "webdriverCommand" browser option with path to browser webdriver');
40
40
  selenoidConfig[browserName].versions[browserVersion] = {
@@ -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> {
@@ -1,10 +1,11 @@
1
1
  /// <reference types="../../../types/selenium-context" />
2
- import { Args } from '@storybook/csf';
2
+ import type { Args } from '@storybook/types';
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';
6
6
  import type { InternalBrowser } from './internal.js';
7
7
  import { logger } from '../logger.js';
8
+ import { removeWorkerContainer } from '../worker/context.js';
8
9
 
9
10
  declare global {
10
11
  interface Window {
@@ -31,7 +32,9 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
31
32
  this.#options = options;
32
33
 
33
34
  subscribeOn('shutdown', () => {
34
- void this.#browser?.closeBrowser().finally(() => process.exit());
35
+ void this.#browser?.closeBrowser().finally(() => {
36
+ void removeWorkerContainer().finally(() => process.exit());
37
+ });
35
38
  this.#browser = null;
36
39
  });
37
40
  }
@@ -42,7 +45,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
42
45
 
43
46
  getSessionId(): Promise<string> {
44
47
  if (!this.#browser) {
45
- // TODO Describe the error
46
48
  throw new Error('Browser is not initialized');
47
49
  }
48
50
 
@@ -63,7 +65,7 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
63
65
  try {
64
66
  return await import('./internal.js');
65
67
  } catch (error) {
66
- logger.error(error);
68
+ logger().error(error);
67
69
  return null;
68
70
  }
69
71
  })();
@@ -89,7 +91,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
89
91
 
90
92
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
91
93
  if (!this.#browser) {
92
- // TODO Describe the error
93
94
  throw new Error('Browser is not initialized');
94
95
  }
95
96
 
@@ -98,7 +99,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
98
99
 
99
100
  afterTest(test: ServerTest): Promise<void> {
100
101
  if (!this.#browser) {
101
- // TODO Describe the error
102
102
  throw new Error('Browser is not initialized');
103
103
  }
104
104
 
@@ -110,7 +110,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
110
110
  ignoreElements?: string | string[] | null,
111
111
  ): Promise<Buffer> {
112
112
  if (!this.#browser) {
113
- // TODO Describe the error
114
113
  throw new Error('Browser is not initialized');
115
114
  }
116
115
 
@@ -119,7 +118,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
119
118
 
120
119
  protected waitForComplete(callback: (isCompleted: boolean) => void): void {
121
120
  if (!this.#browser) {
122
- // TODO Describe the error
123
121
  throw new Error('Browser is not initialized');
124
122
  }
125
123
 
@@ -128,7 +126,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
128
126
 
129
127
  protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
130
128
  if (!this.#browser) {
131
- // TODO Describe the error
132
129
  throw new Error('Browser is not initialized');
133
130
  }
134
131
 
@@ -137,7 +134,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
137
134
 
138
135
  protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
139
136
  if (!this.#browser) {
140
- // TODO Describe the error
141
137
  throw new Error('Browser is not initialized');
142
138
  }
143
139
 
@@ -181,8 +181,8 @@ export async function sendScreenshotsCount(
181
181
  const testsMeta = { runId: uuid, tests };
182
182
 
183
183
  const fullPathname = buildPathname('tests', testsMeta);
184
- // NOTE: Keep request path shorter than 32k symbols
185
- const chunksCount = Math.ceil(fullPathname.length / 32_000);
184
+ // NOTE: Keep request path shorter than 24k symbols
185
+ const chunksCount = Math.ceil(fullPathname.length / 24_000);
186
186
  let chunks: string[] = [];
187
187
  if (chunksCount > 1) {
188
188
  const testsString = JSON.stringify(tests);