creevey 0.10.0-beta.31 → 0.10.0-beta.32

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 (50) hide show
  1. package/dist/client/shared/components/ResultsPage.d.ts +1 -1
  2. package/dist/client/web/CreeveyApp.js +1 -0
  3. package/dist/client/web/CreeveyApp.js.map +1 -1
  4. package/dist/server/docker.d.ts +1 -1
  5. package/dist/server/docker.js +21 -4
  6. package/dist/server/docker.js.map +1 -1
  7. package/dist/server/index.js +9 -10
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/server/master/start.js +1 -1
  10. package/dist/server/master/start.js.map +1 -1
  11. package/dist/server/playwright/docker-file.d.ts +1 -2
  12. package/dist/server/playwright/docker-file.js +10 -4
  13. package/dist/server/playwright/docker-file.js.map +1 -1
  14. package/dist/server/playwright/docker.d.ts +2 -1
  15. package/dist/server/playwright/docker.js +10 -2
  16. package/dist/server/playwright/docker.js.map +1 -1
  17. package/dist/server/playwright/internal.d.ts +3 -4
  18. package/dist/server/playwright/internal.js +48 -37
  19. package/dist/server/playwright/internal.js.map +1 -1
  20. package/dist/server/playwright/webdriver.js +1 -7
  21. package/dist/server/playwright/webdriver.js.map +1 -1
  22. package/dist/server/selenium/internal.js +5 -12
  23. package/dist/server/selenium/internal.js.map +1 -1
  24. package/dist/server/selenium/webdriver.js +0 -7
  25. package/dist/server/selenium/webdriver.js.map +1 -1
  26. package/dist/server/telemetry.js +2 -2
  27. package/dist/server/utils.d.ts +1 -2
  28. package/dist/server/utils.js +11 -8
  29. package/dist/server/utils.js.map +1 -1
  30. package/dist/server/webdriver.d.ts +2 -0
  31. package/dist/server/webdriver.js +13 -1
  32. package/dist/server/webdriver.js.map +1 -1
  33. package/dist/types.d.ts +0 -2
  34. package/dist/types.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/client/web/CreeveyApp.tsx +1 -0
  37. package/src/server/docker.ts +24 -4
  38. package/src/server/index.ts +11 -14
  39. package/src/server/master/start.ts +1 -1
  40. package/src/server/playwright/docker-file.ts +12 -5
  41. package/src/server/playwright/docker.ts +16 -3
  42. package/src/server/playwright/index-source.mjs +16 -0
  43. package/src/server/playwright/internal.ts +78 -52
  44. package/src/server/playwright/webdriver.ts +1 -7
  45. package/src/server/selenium/internal.ts +5 -12
  46. package/src/server/selenium/webdriver.ts +0 -7
  47. package/src/server/telemetry.ts +2 -2
  48. package/src/server/utils.ts +33 -25
  49. package/src/server/webdriver.ts +13 -0
  50. package/src/types.ts +0 -2
@@ -5,6 +5,7 @@ import Dockerode, { Container } from 'dockerode';
5
5
  import { DockerAuth } from '../types.js';
6
6
  import { subscribeOn } from './messages.js';
7
7
  import { logger } from './logger.js';
8
+ import { waitForBrowserClose } from './webdriver.js';
8
9
 
9
10
  const docker = new Dockerode();
10
11
 
@@ -58,12 +59,13 @@ export async function pullImages(
58
59
  }
59
60
  }
60
61
 
61
- export async function buildImage(imageName: string, dockerfile: string): Promise<void> {
62
+ export async function buildImage(imageName: string, version: string, dockerfile: string): Promise<void> {
62
63
  const images = await docker.listImages({ filters: { label: [`creevey=${imageName}`] } });
63
64
 
64
- if (images.at(0)) {
65
+ const containers = await docker.listContainers({ all: true, filters: { label: [`creevey=${imageName}`] } });
66
+ if (containers.length > 0) {
65
67
  await Promise.all(
66
- (await docker.listContainers({ all: true, filters: { label: [`creevey=${imageName}`] } })).map(async (info) => {
68
+ containers.map(async (info) => {
67
69
  const container = docker.getContainer(info.Id);
68
70
  try {
69
71
  await container.remove({ force: true });
@@ -72,6 +74,23 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
72
74
  }
73
75
  }),
74
76
  );
77
+ }
78
+
79
+ const oldImages = images.filter((info) => info.Labels.version !== version);
80
+ if (oldImages.length > 0) {
81
+ await Promise.all(
82
+ oldImages.map(async (info) => {
83
+ const image = docker.getImage(info.Id);
84
+ try {
85
+ await image.remove({ force: true });
86
+ } catch {
87
+ /* noop */
88
+ }
89
+ }),
90
+ );
91
+ }
92
+
93
+ if (oldImages.length !== images.length) {
75
94
  logger().info(`Image ${imageName} already exists`);
76
95
  return;
77
96
  }
@@ -91,7 +110,7 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
91
110
  // @ts-expect-error Type incompatibility AsyncIterator and AsyncIterableIterator
92
111
  pack,
93
112
  // TODO Support buildkit decode grpc (version: '2')
94
- { t: imageName, labels: { creevey: imageName }, version: '1' },
113
+ { t: imageName, labels: { creevey: imageName, version }, version: '1' },
95
114
  (buildError: Error | null, stream) => {
96
115
  if (buildError || !stream) {
97
116
  // spinner.error(buildError?.message);
@@ -151,6 +170,7 @@ export async function runImage(
151
170
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
152
171
  subscribeOn('shutdown', async () => {
153
172
  try {
173
+ await Promise.race([waitForBrowserClose(), new Promise((resolve) => setTimeout(resolve, 2_000))]);
154
174
  await container.remove({ force: true });
155
175
  } catch {
156
176
  /* noop */
@@ -6,13 +6,13 @@ import { resolveCommand } from 'package-manager-detector/commands';
6
6
  import { readConfig, defaultBrowser } from './config.js';
7
7
  import { Options, Config, BrowserConfigObject, isWorkerMessage } from '../types.js';
8
8
  import { logger } from './logger.js';
9
+ import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
9
10
  import { SeleniumWebdriver } from './selenium/webdriver.js';
10
11
  import { LOCALHOST_REGEXP } from './webdriver.js';
11
12
  import { isInsideDocker, killTree, resolvePlaywrightBrowserType, shutdownWithError } from './utils.js';
12
13
  import { sendWorkerMessage, subscribeOn } from './messages.js';
13
14
  import { buildImage } from './docker.js';
14
15
  import { mkdir, writeFile } from 'fs/promises';
15
- import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
16
16
  import assert from 'assert';
17
17
 
18
18
  async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
@@ -35,7 +35,7 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
35
35
  if (cluster.isPrimary) return undefined;
36
36
 
37
37
  const { browserName } = config.browsers[browser] as BrowserConfigObject;
38
- return `creevey://${resolvePlaywrightBrowserType(browserName)}.playwright`;
38
+ return `creevey://${resolvePlaywrightBrowserType(browserName)}`;
39
39
  }
40
40
 
41
41
  const {
@@ -48,24 +48,21 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
48
48
  const { browserName } = config.browsers[browser] as BrowserConfigObject;
49
49
 
50
50
  const imageName = `creevey/${browserName}:v${version}`;
51
- const host = await startPlaywrightContainer(imageName, options.debug);
51
+ const host = await startPlaywrightContainer(imageName, browser, config, options.debug);
52
52
 
53
53
  return host;
54
54
  } else {
55
55
  const { playwrightDockerFile } = await import('./playwright/docker-file.js');
56
- const browsers = [
57
- ...new Set(
58
- Object.values(config.browsers).map(
59
- (c) => [(c as BrowserConfigObject).browserName, (c as BrowserConfigObject).playwrightOptions] as const,
60
- ),
61
- ),
62
- ];
56
+ const {
57
+ default: { version: creeveyVersion },
58
+ } = await import('../../package.json', { with: { type: 'json' } });
59
+ const browsers = [...new Set(Object.values(config.browsers).map((c) => (c as BrowserConfigObject).browserName))];
63
60
  await Promise.all(
64
- browsers.map(async ([browserName, launchOptions]) => {
61
+ browsers.map(async (browserName) => {
65
62
  const imageName = `creevey/${browserName}:v${version}`;
66
- const dockerfile = playwrightDockerFile(browserName, version, launchOptions);
63
+ const dockerfile = await playwrightDockerFile(browserName, version);
67
64
 
68
- await buildImage(imageName, dockerfile);
65
+ await buildImage(imageName, creeveyVersion, dockerfile);
69
66
  }),
70
67
  );
71
68
 
@@ -107,7 +104,7 @@ export default async function (options: Options): Promise<void> {
107
104
  gridUrl = await startWebdriverServer(browser, config, options);
108
105
  }
109
106
 
110
- if (cluster.isPrimary) {
107
+ if (cluster.isPrimary && process.env.CI !== 'true') {
111
108
  const [localUrl, remoteUrl] = getStorybookUrl(config, options);
112
109
  const pm = getUserAgent();
113
110
  assert(pm, new Error('Failed to detect current package manager'));
@@ -109,7 +109,7 @@ export async function start(
109
109
  })
110
110
  .finally(() => {
111
111
  // NOTE: Take some time to kill processes
112
- void shutdownWorkers().then(() => setTimeout(() => process.exit(), 500));
112
+ void shutdownWorkers().then(() => setTimeout(() => process.exit(), 1_000));
113
113
  });
114
114
  });
115
115
  // TODO grep
@@ -1,10 +1,12 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { pathToFileURL } from 'url';
1
3
  import semver from 'semver';
2
4
  import { exec } from 'shelljs';
3
- import { LaunchOptions } from 'playwright-core';
4
- import { resolvePlaywrightBrowserType } from '../utils';
5
+
6
+ const importMetaUrl = pathToFileURL(__filename).href;
5
7
 
6
8
  // TODO Support custom docker images
7
- export function playwrightDockerFile(browser: string, version: string, serverOptions?: LaunchOptions): string {
9
+ export async function playwrightDockerFile(browser: string, version: string): Promise<string> {
8
10
  const sv = semver.coerce(version);
9
11
 
10
12
  let npmRegistry;
@@ -14,14 +16,19 @@ export function playwrightDockerFile(browser: string, version: string, serverOpt
14
16
  /* noop */
15
17
  }
16
18
 
19
+ const indexJs = await readFile(new URL('./index-source.mjs', importMetaUrl), 'utf-8');
20
+
17
21
  return `
18
22
  FROM node:lts
19
23
 
20
24
  WORKDIR /creevey
21
25
 
22
26
  RUN echo "{ \\"type\\": \\"module\\" }" > package.json && \\
23
- echo "import { ${resolvePlaywrightBrowserType(browser)} as browser } from 'playwright-core';" >> index.js && \\
24
- echo "const ws = await browser.launchServer({ ...${JSON.stringify(serverOptions)}, port: 4444, wsPath: 'creevey' })" >> index.js && \\${
27
+ ${indexJs
28
+ .split('\n')
29
+ .map((line) => `echo "${line.replace(/"/g, '\\"')}" >> index.js && \\`)
30
+ .join('\n')}
31
+ ${
25
32
  npmRegistry
26
33
  ? `
27
34
  echo "registry=${npmRegistry}" > .npmrc && \\`
@@ -1,9 +1,17 @@
1
+ import assert from 'assert';
1
2
  import { runImage } from '../docker';
2
3
  import { emitWorkerMessage, subscribeOn } from '../messages';
3
- import { isInsideDocker } from '../utils';
4
+ import { getCreeveyCache, isInsideDocker, resolvePlaywrightBrowserType } from '../utils';
4
5
  import { LOCALHOST_REGEXP } from '../webdriver';
6
+ import type { BrowserConfigObject, Config } from '../../types';
5
7
 
6
- export async function startPlaywrightContainer(imageName: string, debug: boolean): Promise<string> {
8
+ export async function startPlaywrightContainer(
9
+ imageName: string,
10
+ browser: string,
11
+ config: Config,
12
+ debug: boolean,
13
+ ): Promise<string> {
14
+ const { browserName, playwrightOptions } = config.browsers[browser] as BrowserConfigObject;
7
15
  const port = await new Promise<number>((resolve) => {
8
16
  subscribeOn('worker', (message) => {
9
17
  if (message.type == 'port') {
@@ -13,13 +21,18 @@ export async function startPlaywrightContainer(imageName: string, debug: boolean
13
21
  emitWorkerMessage({ type: 'port', payload: { port: -1 } });
14
22
  });
15
23
 
24
+ const cacheDir = await getCreeveyCache();
25
+
26
+ assert(cacheDir, "Couldn't get cache directory");
27
+
16
28
  const host = await runImage(
17
29
  imageName,
18
- [],
30
+ [JSON.stringify({ ...playwrightOptions, browser: resolvePlaywrightBrowserType(browserName) })],
19
31
  {
20
32
  ExposedPorts: { [`${port}/tcp`]: {} },
21
33
  HostConfig: {
22
34
  PortBindings: { ['4444/tcp']: [{ HostPort: `${port}` }] },
35
+ Binds: [`${cacheDir}/${process.pid}:/creevey/traces`],
23
36
  },
24
37
  },
25
38
  debug,
@@ -0,0 +1,16 @@
1
+ import { chromium, firefox, webkit } from 'playwright-core';
2
+
3
+ /** @type import("playwright-core").LaunchOptions & { browser: 'chromium' | 'firefox' | 'webkit' } */
4
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
5
+ const config = JSON.parse(process.argv.slice(2)[0]);
6
+
7
+ const browsers = { chromium, firefox, webkit };
8
+
9
+ const ws = await browsers[config.browser].launchServer({
10
+ ...config,
11
+ port: 4444,
12
+ wsPath: 'creevey',
13
+ tracesDir: 'traces',
14
+ });
15
+
16
+ console.log(config.browser, 'browser server launched on:', ws.wsEndpoint());
@@ -1,8 +1,10 @@
1
- import { Browser, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
1
+ import path from 'path';
2
+ import { Browser, BrowserContext, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
2
3
  import chalk from 'chalk';
3
4
  import { v4 } from 'uuid';
4
5
  import Logger from 'loglevel';
5
6
  import prefix from 'loglevel-plugin-prefix';
7
+ import type { Args } from '@storybook/csf';
6
8
  import {
7
9
  BrowserConfigObject,
8
10
  Config,
@@ -11,13 +13,18 @@ import {
11
13
  StoryInput,
12
14
  StorybookEvents,
13
15
  StorybookGlobals,
14
- noop,
15
16
  } from '../../types';
16
- import { subscribeOn } from '../messages';
17
- import { appendIframePath, getAddresses, LOCALHOST_REGEXP, resolveStorybookUrl, storybookRootID } from '../webdriver';
18
- import { isShuttingDown, resolvePlaywrightBrowserType, runSequence } from '../utils';
17
+ import {
18
+ appendIframePath,
19
+ getAddresses,
20
+ LOCALHOST_REGEXP,
21
+ openBrowser,
22
+ resolveStorybookUrl,
23
+ storybookRootID,
24
+ } from '../webdriver';
25
+ import { getCreeveyCache, isShuttingDown, resolvePlaywrightBrowserType, runSequence } from '../utils';
19
26
  import { colors, logger } from '../logger';
20
- import type { Args } from '@storybook/csf';
27
+ import assert from 'assert';
21
28
 
22
29
  const browsers = {
23
30
  chromium,
@@ -59,22 +66,34 @@ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser |
59
66
  export class InternalBrowser {
60
67
  #isShuttingDown = false;
61
68
  #browser: Browser;
69
+ #context: BrowserContext;
62
70
  #page: Page;
71
+ #traceDir: string;
63
72
  #sessionId: string = v4();
64
73
  #serverHost: string | null = null;
65
74
  #serverPort: number;
75
+ #debug: boolean;
66
76
  #storybookGlobals?: StorybookGlobals;
67
- #unsubscribe: () => void = noop;
68
- constructor(browser: Browser, page: Page, port: number, storybookGlobals?: StorybookGlobals) {
77
+ #closeBrowser = openBrowser();
78
+ constructor(
79
+ browser: Browser,
80
+ context: BrowserContext,
81
+ page: Page,
82
+ traceDir: string,
83
+ port: number,
84
+ debug: boolean,
85
+ storybookGlobals?: StorybookGlobals,
86
+ ) {
69
87
  this.#browser = browser;
88
+ this.#context = context;
70
89
  this.#page = page;
90
+ this.#traceDir = traceDir;
71
91
  this.#serverPort = port;
92
+ this.#debug = debug;
72
93
  this.#storybookGlobals = storybookGlobals;
73
- this.#unsubscribe = subscribeOn('shutdown', () => {
74
- void this.closeBrowser();
75
- });
76
94
  }
77
95
 
96
+ // TODO Expose #browser and #context in tests
78
97
  get browser() {
79
98
  return this.#page;
80
99
  }
@@ -87,19 +106,21 @@ export class InternalBrowser {
87
106
  if (this.#isShuttingDown) return;
88
107
 
89
108
  this.#isShuttingDown = true;
90
- this.#unsubscribe();
91
109
 
92
110
  try {
111
+ if (this.#debug) await this.#context.tracing.stop({ path: path.join(this.#traceDir, 'trace.zip') });
93
112
  await this.#page.close();
113
+ if (this.#debug) await this.#page.video()?.saveAs(path.join(this.#traceDir, 'video.webm'));
114
+ await this.#context.close();
94
115
  await this.#browser.close();
95
- } catch (_) {
116
+ } catch {
96
117
  /* noop */
118
+ } finally {
119
+ this.#closeBrowser();
97
120
  }
98
121
  }
99
122
 
100
123
  async takeScreenshot(captureElement?: string | null, ignoreElements?: string | string[] | null): Promise<Buffer> {
101
- // TODO Implement features from selenium `takeScreenshot`
102
- // TODO Do we need scroll bar hack from selenium?
103
124
  const ignore = Array.isArray(ignoreElements) ? ignoreElements : ignoreElements ? [ignoreElements] : [];
104
125
  const mask = ignore.map((el) => this.#page.locator(el));
105
126
  if (captureElement) {
@@ -186,14 +207,24 @@ export class InternalBrowser {
186
207
  seleniumCapabilities,
187
208
  playwrightOptions,
188
209
  } = browserConfig;
210
+ const parsedUrl = new URL(gridUrl);
211
+ const tracesDir = path.join(
212
+ playwrightOptions?.tracesDir ?? path.join(config.reportDir, 'traces'),
213
+ process.pid.toString(),
214
+ );
215
+ const cacheDir = await getCreeveyCache();
216
+
217
+ assert(cacheDir, "Couldn't get cache directory");
189
218
 
190
219
  let browser: Browser | null = null;
191
220
 
192
- const parsedUrl = new URL(gridUrl);
193
221
  if (parsedUrl.protocol === 'ws:') {
194
222
  browser = await tryConnect(browsers[resolvePlaywrightBrowserType(browserConfig.browserName)], gridUrl);
195
223
  } else if (parsedUrl.protocol === 'creevey:') {
196
- browser = await browsers[resolvePlaywrightBrowserType(browserConfig.browserName)].launch(playwrightOptions);
224
+ browser = await browsers[resolvePlaywrightBrowserType(browserConfig.browserName)].launch({
225
+ ...playwrightOptions,
226
+ tracesDir: path.join(cacheDir, `${process.pid}`),
227
+ });
197
228
  } else {
198
229
  if (browserConfig.browserName !== 'chrome') {
199
230
  logger().error("Playwright's Selenium Grid feature supports only chrome browser");
@@ -203,20 +234,29 @@ export class InternalBrowser {
203
234
  process.env.SELENIUM_REMOTE_URL = gridUrl;
204
235
  process.env.SELENIUM_REMOTE_CAPABILITIES = JSON.stringify(seleniumCapabilities);
205
236
 
206
- browser = await chromium.launch(playwrightOptions);
237
+ browser = await chromium.launch({ ...playwrightOptions, tracesDir: path.join(cacheDir, `${process.pid}`) });
207
238
  }
208
239
 
209
240
  if (!browser) {
210
241
  return null;
211
242
  }
212
243
 
213
- // TODO Record video
214
- const page = await browser.newPage();
215
- // TODO Support tracing
216
- // if (playwrightOptions?.trace) {
217
- // const context = page.context();
218
- // await context.tracing.start(playwrightOptions.trace);
219
- // }
244
+ const context = await browser.newContext({
245
+ recordVideo: options.debug
246
+ ? {
247
+ dir: path.join(cacheDir, `${process.pid}`),
248
+ size: viewport,
249
+ }
250
+ : undefined,
251
+ screen: viewport,
252
+ viewport,
253
+ });
254
+ const page = await context.newPage();
255
+ if (options.debug) {
256
+ await context.tracing.start(
257
+ Object.assign({ screenshots: true, snapshots: true, sources: true }, playwrightOptions?.trace),
258
+ );
259
+ }
220
260
 
221
261
  if (logger().getLevel() <= Logger.levels.DEBUG) {
222
262
  page.on('console', (msg) => {
@@ -224,15 +264,20 @@ export class InternalBrowser {
224
264
  });
225
265
  }
226
266
 
227
- // TODO Add debug output
228
-
229
- const internalBrowser = new InternalBrowser(browser, page, options.port, _storybookGlobals);
267
+ const internalBrowser = new InternalBrowser(
268
+ browser,
269
+ context,
270
+ page,
271
+ tracesDir,
272
+ options.port,
273
+ options.debug,
274
+ _storybookGlobals,
275
+ );
230
276
 
231
277
  try {
232
278
  if (isShuttingDown.current) return null;
233
279
  const done = await internalBrowser.init({
234
280
  browserName,
235
- viewport,
236
281
  storybookUrl: address,
237
282
  });
238
283
 
@@ -250,15 +295,7 @@ export class InternalBrowser {
250
295
  }
251
296
  }
252
297
 
253
- private async init({
254
- browserName,
255
- viewport,
256
- storybookUrl,
257
- }: {
258
- browserName: string;
259
- viewport?: { width: number; height: number };
260
- storybookUrl: string;
261
- }) {
298
+ private async init({ browserName, storybookUrl }: { browserName: string; storybookUrl: string }) {
262
299
  const sessionId = this.#sessionId;
263
300
 
264
301
  prefix.apply(logger(), {
@@ -278,7 +315,6 @@ export class InternalBrowser {
278
315
  () => this.updateStorybookGlobals(),
279
316
  () => this.resolveCreeveyHost(),
280
317
  () => this.updateBrowserGlobalVariables(),
281
- () => this.resizeViewport(viewport),
282
318
  ],
283
319
  () => !this.#isShuttingDown,
284
320
  );
@@ -291,7 +327,6 @@ export class InternalBrowser {
291
327
  }
292
328
 
293
329
  try {
294
- // TODO this.#page.setDefaultNavigationTimeout(10000);
295
330
  const resolvedUrl = await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
296
331
  await this.#page.goto(resolvedUrl);
297
332
  } catch (error) {
@@ -317,8 +352,7 @@ export class InternalBrowser {
317
352
  }
318
353
 
319
354
  private async waitForStorybook(): Promise<void> {
320
- // TODO Duplicated code with selenium
321
- logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
355
+ logger().debug('Waiting for Storybook to initiate');
322
356
 
323
357
  const isTimeout = await Promise.race([
324
358
  new Promise<boolean>((resolve) => {
@@ -331,7 +365,6 @@ export class InternalBrowser {
331
365
  do {
332
366
  try {
333
367
  // TODO Research a different way to ensure storybook is initiated
334
- // TODO Maybe use `__STORYBOOK_PREVIEW__.extract()`
335
368
  wait = await this.#page.evaluate((SET_GLOBALS: string) => {
336
369
  if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
337
370
  if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
@@ -347,10 +380,10 @@ export class InternalBrowser {
347
380
  })(),
348
381
  ]);
349
382
 
350
- // TODO Change the message to describe a reason why it might happen
351
- if (isTimeout) throw new Error('Failed to wait `setStories` event');
383
+ if (isTimeout) throw new Error('Failed to wait Storybook init');
352
384
  }
353
385
 
386
+ // TODO Doesn't work for some reason, maybe because of race-condition
354
387
  private async triggerViteReload(): Promise<void> {
355
388
  // NOTE: On the first load, Vite might try to optimize some dependencies and reload the page
356
389
  // We need to trigger reload earlier to avoid unnecessary reloads further
@@ -410,13 +443,6 @@ export class InternalBrowser {
410
443
  );
411
444
  }
412
445
 
413
- private async resizeViewport(viewport?: { width: number; height: number }): Promise<void> {
414
- if (!viewport) return;
415
-
416
- logger().debug('Resizing viewport to', viewport);
417
- await this.#page.setViewportSize(viewport);
418
- }
419
-
420
446
  private async resetMousePosition(): Promise<void> {
421
447
  logger().debug('Resetting mouse position to (0, 0)');
422
448
  await this.#page.mouse.move(0, 0);
@@ -21,7 +21,7 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
21
21
  this.#options = options;
22
22
 
23
23
  subscribeOn('shutdown', () => {
24
- void this.#browser?.closeBrowser().finally(() => process.exit());
24
+ void this.#browser?.closeBrowser().finally(() => setTimeout(() => process.exit(), 5_000));
25
25
  this.#browser = null;
26
26
  });
27
27
  }
@@ -32,7 +32,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
32
32
 
33
33
  getSessionId(): Promise<string> {
34
34
  if (!this.#browser) {
35
- // TODO Describe the error
36
35
  throw new Error('Browser is not initialized');
37
36
  }
38
37
 
@@ -79,7 +78,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
79
78
 
80
79
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
81
80
  if (!this.#browser) {
82
- // TODO Describe the error
83
81
  throw new Error('Browser is not initialized');
84
82
  }
85
83
 
@@ -95,7 +93,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
95
93
  ignoreElements?: string | string[] | null,
96
94
  ): Promise<Buffer> {
97
95
  if (!this.#browser) {
98
- // TODO Describe the error
99
96
  throw new Error('Browser is not initialized');
100
97
  }
101
98
 
@@ -104,7 +101,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
104
101
 
105
102
  protected waitForComplete(callback: (isCompleted: boolean) => void): void {
106
103
  if (!this.#browser) {
107
- // TODO Describe the error
108
104
  throw new Error('Browser is not initialized');
109
105
  }
110
106
 
@@ -113,7 +109,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
113
109
 
114
110
  protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
115
111
  if (!this.#browser) {
116
- // TODO Describe the error
117
112
  throw new Error('Browser is not initialized');
118
113
  }
119
114
 
@@ -122,7 +117,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
122
117
 
123
118
  protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
124
119
  if (!this.#browser) {
125
- // TODO Describe the error
126
120
  throw new Error('Browser is not initialized');
127
121
  }
128
122
 
@@ -249,7 +249,7 @@ export class InternalBrowser {
249
249
  const rects = await this.#browser.executeScript<
250
250
  { elementRect: ElementRect; windowRect: ElementRect } | undefined
251
251
  >(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
252
- window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition`
252
+ window.scrollTo(0, 0);
253
253
  // eslint-disable-next-line no-var
254
254
  var element = document.querySelector(selector);
255
255
  if (!element) return;
@@ -297,12 +297,10 @@ export class InternalBrowser {
297
297
  // ? context
298
298
  // ? await context.captureElementScreenshot(await element.getId())
299
299
  // : await browser.findElement(By.css(captureElement)).takeScreenshot()
300
- // : // TODO pointer-events: none, need to research
301
- // await takeCompositeScreenshot(browser, windowRect, elementRect);
300
+ // : await takeCompositeScreenshot(browser, windowRect, elementRect);
302
301
  screenshot = isFitIntoViewport
303
302
  ? await this.#browser.findElement(By.css(captureElement)).takeScreenshot()
304
- : // TODO pointer-events: none, need to research
305
- await this.takeCompositeScreenshot(windowRect, elementRect);
303
+ : await this.takeCompositeScreenshot(windowRect, elementRect);
306
304
 
307
305
  logger().debug(`${chalk.cyan(captureElement)} is captured`);
308
306
  }
@@ -503,7 +501,6 @@ export class InternalBrowser {
503
501
  }
504
502
 
505
503
  try {
506
- // TODO Pageload timeout 10s
507
504
  // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
508
505
  await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
509
506
  } catch (error) {
@@ -541,7 +538,7 @@ export class InternalBrowser {
541
538
  }
542
539
 
543
540
  private async waitForStorybook(): Promise<void> {
544
- logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
541
+ logger().debug('Waiting for Storybook to initiate');
545
542
 
546
543
  const isTimeout = await Promise.race([
547
544
  new Promise<boolean>((resolve) => {
@@ -554,9 +551,6 @@ export class InternalBrowser {
554
551
  do {
555
552
  // TODO Research a different way to ensure storybook is initiated
556
553
  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
554
  if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
561
555
  if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
562
556
  return false;
@@ -566,8 +560,7 @@ export class InternalBrowser {
566
560
  })(),
567
561
  ]);
568
562
 
569
- // TODO Change the message to describe a reason why it might happen
570
- if (isTimeout) throw new Error('Failed to wait `setStories` event');
563
+ if (isTimeout) throw new Error('Failed to wait Storybook init');
571
564
  }
572
565
 
573
566
  private async updateStorybookGlobals(): Promise<void> {
@@ -42,7 +42,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
42
42
 
43
43
  getSessionId(): Promise<string> {
44
44
  if (!this.#browser) {
45
- // TODO Describe the error
46
45
  throw new Error('Browser is not initialized');
47
46
  }
48
47
 
@@ -89,7 +88,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
89
88
 
90
89
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
91
90
  if (!this.#browser) {
92
- // TODO Describe the error
93
91
  throw new Error('Browser is not initialized');
94
92
  }
95
93
 
@@ -98,7 +96,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
98
96
 
99
97
  afterTest(test: ServerTest): Promise<void> {
100
98
  if (!this.#browser) {
101
- // TODO Describe the error
102
99
  throw new Error('Browser is not initialized');
103
100
  }
104
101
 
@@ -110,7 +107,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
110
107
  ignoreElements?: string | string[] | null,
111
108
  ): Promise<Buffer> {
112
109
  if (!this.#browser) {
113
- // TODO Describe the error
114
110
  throw new Error('Browser is not initialized');
115
111
  }
116
112
 
@@ -119,7 +115,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
119
115
 
120
116
  protected waitForComplete(callback: (isCompleted: boolean) => void): void {
121
117
  if (!this.#browser) {
122
- // TODO Describe the error
123
118
  throw new Error('Browser is not initialized');
124
119
  }
125
120
 
@@ -128,7 +123,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
128
123
 
129
124
  protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
130
125
  if (!this.#browser) {
131
- // TODO Describe the error
132
126
  throw new Error('Browser is not initialized');
133
127
  }
134
128
 
@@ -137,7 +131,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
137
131
 
138
132
  protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
139
133
  if (!this.#browser) {
140
- // TODO Describe the error
141
134
  throw new Error('Browser is not initialized');
142
135
  }
143
136