creevey 0.10.0-beta.43 → 0.10.0-beta.44

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 (134) hide show
  1. package/CHANGELOG.md +282 -0
  2. package/dist/client/addon/controller.js +1 -1
  3. package/dist/client/addon/controller.js.map +1 -1
  4. package/dist/client/addon/withCreevey.js +1 -18
  5. package/dist/client/addon/withCreevey.js.map +1 -1
  6. package/dist/client/shared/components/PageHeader/PageHeader.js +13 -4
  7. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  8. package/dist/client/shared/creeveyClientApi.js +10 -0
  9. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  10. package/dist/client/web/CreeveyApp.d.ts +1 -0
  11. package/dist/client/web/CreeveyApp.js +1 -0
  12. package/dist/client/web/CreeveyApp.js.map +1 -1
  13. package/dist/client/web/CreeveyContext.d.ts +1 -0
  14. package/dist/client/web/CreeveyContext.js +1 -0
  15. package/dist/client/web/CreeveyContext.js.map +1 -1
  16. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +9 -8
  17. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  18. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +13 -3
  19. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  20. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +2 -3
  21. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  22. package/dist/client/web/CreeveyView/SideBar/TestLink.js +2 -3
  23. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  24. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +1 -0
  25. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  26. package/dist/client/web/assets/{index-C47njyZV.js → index-BU4jjKVC.js} +68 -68
  27. package/dist/client/web/index.html +1 -1
  28. package/dist/client/web/index.js +8 -3
  29. package/dist/client/web/index.js.map +1 -1
  30. package/dist/creevey.d.ts +1 -1
  31. package/dist/creevey.js +1 -22
  32. package/dist/creevey.js.map +1 -1
  33. package/dist/playwright-reporter.d.ts +2 -0
  34. package/dist/playwright-reporter.js +5 -0
  35. package/dist/playwright-reporter.js.map +1 -0
  36. package/dist/playwright.d.ts +1 -1
  37. package/dist/server/config.js +8 -1
  38. package/dist/server/config.js.map +1 -1
  39. package/dist/server/index.js +10 -3
  40. package/dist/server/index.js.map +1 -1
  41. package/dist/server/master/api.d.ts +15 -5
  42. package/dist/server/master/api.js +89 -27
  43. package/dist/server/master/api.js.map +1 -1
  44. package/dist/server/master/handlers/capture-handler.d.ts +2 -0
  45. package/dist/server/master/handlers/capture-handler.js +35 -0
  46. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  47. package/dist/server/master/handlers/index.d.ts +4 -0
  48. package/dist/server/master/handlers/index.js +21 -0
  49. package/dist/server/master/handlers/index.js.map +1 -0
  50. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  51. package/dist/server/master/handlers/ping-handler.js +7 -0
  52. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  53. package/dist/server/master/handlers/static-handler.d.ts +2 -0
  54. package/dist/server/master/handlers/static-handler.js +32 -0
  55. package/dist/server/master/handlers/static-handler.js.map +1 -0
  56. package/dist/server/master/handlers/stories-handler.d.ts +2 -0
  57. package/dist/server/master/handlers/stories-handler.js +38 -0
  58. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  59. package/dist/server/master/master.js +7 -24
  60. package/dist/server/master/master.js.map +1 -1
  61. package/dist/server/master/runner.d.ts +4 -6
  62. package/dist/server/master/runner.js +30 -127
  63. package/dist/server/master/runner.js.map +1 -1
  64. package/dist/server/master/server.js +77 -87
  65. package/dist/server/master/server.js.map +1 -1
  66. package/dist/server/master/start.d.ts +1 -2
  67. package/dist/server/master/start.js +11 -29
  68. package/dist/server/master/start.js.map +1 -1
  69. package/dist/server/master/testsManager.d.ts +81 -0
  70. package/dist/server/master/testsManager.js +281 -0
  71. package/dist/server/master/testsManager.js.map +1 -0
  72. package/dist/server/playwright/reporter.d.ts +87 -0
  73. package/dist/server/playwright/reporter.js +351 -0
  74. package/dist/server/playwright/reporter.js.map +1 -0
  75. package/dist/server/selenium/internal.js +20 -2
  76. package/dist/server/selenium/internal.js.map +1 -1
  77. package/dist/server/selenium/selenoid.js +4 -0
  78. package/dist/server/selenium/selenoid.js.map +1 -1
  79. package/dist/server/shutdown.d.ts +1 -0
  80. package/dist/server/shutdown.js +23 -0
  81. package/dist/server/shutdown.js.map +1 -0
  82. package/dist/server/stories.d.ts +0 -1
  83. package/dist/server/stories.js +0 -12
  84. package/dist/server/stories.js.map +1 -1
  85. package/dist/server/ui-update.d.ts +10 -0
  86. package/dist/server/ui-update.js +39 -0
  87. package/dist/server/ui-update.js.map +1 -0
  88. package/dist/server/utils.d.ts +6 -0
  89. package/dist/server/utils.js +39 -8
  90. package/dist/server/utils.js.map +1 -1
  91. package/dist/server/worker/start.js +1 -1
  92. package/dist/server/worker/start.js.map +1 -1
  93. package/dist/types.d.ts +14 -8
  94. package/dist/types.js.map +1 -1
  95. package/docs/examples/playwright-reporter-example.ts +202 -0
  96. package/docs/migration-0.9-to-0.10.md +144 -0
  97. package/docs/playwright-reporter.md +357 -0
  98. package/package.json +9 -13
  99. package/src/client/addon/controller.ts +1 -1
  100. package/src/client/addon/withCreevey.ts +2 -16
  101. package/src/client/shared/components/PageHeader/PageHeader.tsx +18 -4
  102. package/src/client/shared/creeveyClientApi.ts +10 -0
  103. package/src/client/web/CreeveyApp.tsx +2 -0
  104. package/src/client/web/CreeveyContext.tsx +2 -0
  105. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +19 -17
  106. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +18 -3
  107. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +9 -7
  108. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +8 -6
  109. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +1 -0
  110. package/src/client/web/index.tsx +8 -3
  111. package/src/creevey.ts +1 -24
  112. package/src/playwright-reporter.ts +3 -0
  113. package/src/server/config.ts +9 -1
  114. package/src/server/index.ts +11 -4
  115. package/src/server/master/api.ts +95 -26
  116. package/src/server/master/handlers/capture-handler.ts +39 -0
  117. package/src/server/master/handlers/index.ts +4 -0
  118. package/src/server/master/handlers/ping-handler.ts +5 -0
  119. package/src/server/master/handlers/static-handler.ts +29 -0
  120. package/src/server/master/handlers/stories-handler.ts +48 -0
  121. package/src/server/master/master.ts +10 -27
  122. package/src/server/master/runner.ts +38 -132
  123. package/src/server/master/server.ts +93 -97
  124. package/src/server/master/start.ts +17 -41
  125. package/src/server/master/testsManager.ts +315 -0
  126. package/src/server/playwright/reporter.ts +386 -0
  127. package/src/server/selenium/internal.ts +23 -3
  128. package/src/server/selenium/selenoid.ts +5 -0
  129. package/src/server/shutdown.ts +19 -0
  130. package/src/server/stories.ts +1 -12
  131. package/src/server/ui-update.ts +46 -0
  132. package/src/server/utils.ts +40 -9
  133. package/src/server/worker/start.ts +1 -1
  134. package/src/types.ts +14 -8
@@ -373,12 +373,32 @@ export class InternalBrowser {
373
373
  }
374
374
 
375
375
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
376
- const stories = await this.#browser.executeAsyncScript<StoriesRaw | undefined>(function (
377
- callback: (stories: StoriesRaw | undefined) => void,
376
+ const result = await this.#browser.executeAsyncScript<
377
+ [error?: { message: string; stack?: string } | null, stories?: StoriesRaw]
378
+ >(function (
379
+ callback: (response: [error?: { message: string; stack?: string } | null, stories?: StoriesRaw]) => void,
378
380
  ) {
379
- void window.__CREEVEY_GET_STORIES__().then(callback);
381
+ window
382
+ .__CREEVEY_GET_STORIES__()
383
+ .then((stories) => {
384
+ callback([null, stories]);
385
+ })
386
+ .catch((error: unknown) => {
387
+ const errorInfo = {
388
+ message: error instanceof Error ? error.message : String(error),
389
+ stack: error instanceof Error ? error.stack : undefined,
390
+ };
391
+ callback([errorInfo]);
392
+ });
380
393
  });
381
394
 
395
+ const [error, stories] = result;
396
+
397
+ if (error) {
398
+ const errorObj = new Error(error.message);
399
+ if (error.stack) errorObj.stack = error.stack;
400
+ throw errorObj;
401
+ }
382
402
  if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
383
403
 
384
404
  return stories;
@@ -7,6 +7,7 @@ import { Config, BrowserConfigObject } from '../../types.js';
7
7
  import { downloadBinary, getCreeveyCache, killTree } from '../utils.js';
8
8
  import { pullImages, runImage } from '../docker.js';
9
9
  import { subscribeOn } from '../messages.js';
10
+ import { removeWorkerContainer } from '../worker/context.js';
10
11
 
11
12
  async function createSelenoidConfig(
12
13
  browsers: BrowserConfigObject[],
@@ -147,5 +148,9 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
147
148
  },
148
149
  };
149
150
 
151
+ subscribeOn('shutdown', () => {
152
+ void removeWorkerContainer();
153
+ });
154
+
150
155
  return runImage(selenoidImage, ['-limit', String(limit)], selenoidOptions, debug);
151
156
  }
@@ -0,0 +1,19 @@
1
+ import cluster from 'cluster';
2
+ import { subscribeOn } from './messages.js';
3
+ import { shutdownOnException, isShuttingDown } from './utils.js';
4
+
5
+ if (cluster.isWorker) {
6
+ subscribeOn('shutdown', () => {
7
+ isShuttingDown.current = true;
8
+ });
9
+ }
10
+
11
+ process.on('uncaughtException', shutdownOnException);
12
+ process.on('unhandledRejection', shutdownOnException);
13
+ // TODO SIGINT Stuck with selenium
14
+ process.on('SIGINT', () => {
15
+ if (isShuttingDown.current) {
16
+ process.exit(-1);
17
+ }
18
+ isShuttingDown.current = true;
19
+ });
@@ -1,5 +1,3 @@
1
- import path from 'path';
2
- import { mkdirSync, writeFileSync } from 'fs';
3
1
  import { createHash } from 'crypto';
4
2
  import _ from 'lodash';
5
3
  import type {
@@ -12,7 +10,7 @@ import type {
12
10
  CreeveyTestFunction,
13
11
  CreeveyTestContext,
14
12
  } from '../types.js';
15
- import { isDefined, isFunction } from '../types.js';
13
+ import { isDefined } from '../types.js';
16
14
  import { shouldSkip } from './utils.js';
17
15
 
18
16
  function storyTestFabric(delay?: number, testFn?: CreeveyTestFunction) {
@@ -129,12 +127,3 @@ export async function loadTestsFromStories(
129
127
 
130
128
  return tests;
131
129
  }
132
-
133
- export function saveTestsJson(tests: Record<string, unknown>, dstPath: string = process.cwd()): void {
134
- mkdirSync(dstPath, { recursive: true });
135
- writeFileSync(
136
- path.join(dstPath, 'tests.json'),
137
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
138
- JSON.stringify(tests, (_, value) => (isFunction(value) ? value.toString() : value), 2),
139
- );
140
- }
@@ -0,0 +1,46 @@
1
+ import { Config } from '../types.js';
2
+ import { logger } from './logger.js';
3
+ import { TestsManager } from './master/testsManager.js';
4
+ import { start as startServer } from './master/server.js';
5
+ import { CreeveyApi } from './master/api.js';
6
+
7
+ /**
8
+ * UI Update Mode implementation.
9
+ * This mode allows users to review and approve screenshots from the browser interface.
10
+ * It combines the functionality of both --ui and --update flags.
11
+ *
12
+ * @param config Creevey configuration
13
+ * @param port Port to run the server on
14
+ */
15
+ export async function uiUpdate(config: Config, port: number): Promise<void> {
16
+ logger().info('Starting UI Update Mode');
17
+
18
+ // Initialize TestsManager with the configured directories
19
+ const testsManager = new TestsManager(config.screenDir, config.reportDir);
20
+
21
+ // Load tests from the report
22
+ const testsFromReport = testsManager.loadTestsFromReport();
23
+
24
+ if (Object.keys(testsFromReport).length === 0) {
25
+ logger().warn('No tests found in report. Run tests first to generate report data.');
26
+ return;
27
+ }
28
+
29
+ // Set tests in the manager
30
+ testsManager.updateTests(testsFromReport);
31
+
32
+ // Start API server with UI enabled
33
+ const resolveApi = startServer(config.reportDir, port, true);
34
+
35
+ // Initialize API
36
+ const api = new CreeveyApi(testsManager);
37
+
38
+ // Resolve the API for the server
39
+ resolveApi(api);
40
+
41
+ // Save test data to make it available for the UI
42
+ await testsManager.saveTestData();
43
+
44
+ logger().info(`UI Update Mode started on http://localhost:${port}/`);
45
+ logger().info('You can now review and approve screenshots from the browser.');
46
+ }
@@ -1,16 +1,17 @@
1
1
  import fs from 'fs';
2
- import https from 'https';
2
+ import path from 'path';
3
3
  import http from 'http';
4
+ import https from 'https';
5
+ import assert from 'assert';
4
6
  import cluster from 'cluster';
5
- import { dirname } from 'path';
7
+ import pidtree from 'pidtree';
6
8
  import { fileURLToPath, pathToFileURL } from 'url';
7
9
  import { register as esmRegister } from 'tsx/esm/api';
8
10
  import { register as cjsRegister } from 'tsx/cjs/api';
9
11
  import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest, Worker } from '../types.js';
10
- import { emitShutdownMessage, sendShutdownMessage } from './messages.js';
12
+ import { emitShutdownMessage, emitWorkerMessage, sendShutdownMessage } from './messages.js';
11
13
  import { LOCALHOST_REGEXP } from './webdriver.js';
12
- import assert from 'assert';
13
- import pidtree from 'pidtree';
14
+ import { logger } from './logger.js';
14
15
 
15
16
  const importMetaUrl = pathToFileURL(__filename).href;
16
17
 
@@ -89,6 +90,18 @@ export function shouldSkipByOption(
89
90
  return skipByBrowser && skipByKind && skipByStory && skipByTest && reason;
90
91
  }
91
92
 
93
+ export function shutdownOnException(reason: unknown): void {
94
+ if (isShuttingDown.current) return;
95
+
96
+ const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
97
+
98
+ logger().error(error);
99
+
100
+ process.exitCode = -1;
101
+ if (cluster.isWorker) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error } });
102
+ if (cluster.isPrimary) void shutdownWorkers();
103
+ }
104
+
92
105
  export async function shutdownWorkers(): Promise<void> {
93
106
  isShuttingDown.current = true;
94
107
  await Promise.all(
@@ -151,7 +164,7 @@ export function resolvePlaywrightBrowserType(browserName: string): (typeof brows
151
164
 
152
165
  export async function getCreeveyCache(): Promise<string | undefined> {
153
166
  const { default: findCacheDir } = await import('find-cache-dir');
154
- return findCacheDir({ name: 'creevey', cwd: dirname(fileURLToPath(importMetaUrl)) });
167
+ return findCacheDir({ name: 'creevey', cwd: path.dirname(fileURLToPath(importMetaUrl)) });
155
168
  }
156
169
 
157
170
  export async function runSequence(seq: (() => unknown)[], predicate: () => boolean): Promise<boolean> {
@@ -242,8 +255,8 @@ const [nodeVersion] = process.versions.node.split('.').map(Number);
242
255
  export async function loadThroughTSX<T>(
243
256
  callback: (load: (modulePath: string) => Promise<T>) => Promise<T>,
244
257
  ): Promise<T> {
245
- // TODO Check if it work in node18 and type: 'module'
246
- const unregister = nodeVersion > 18 ? esmRegister() : cjsRegister();
258
+ const unregisterESM = nodeVersion > 18 ? esmRegister() : noop;
259
+ const unregisterCJS = cjsRegister();
247
260
 
248
261
  const result = await callback((modulePath) =>
249
262
  nodeVersion > 18
@@ -254,7 +267,9 @@ export async function loadThroughTSX<T>(
254
267
 
255
268
  // NOTE: `unregister` type is `(() => Promise<void>) | (() => void)`
256
269
  // eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
257
- await unregister();
270
+ await unregisterCJS();
271
+ // eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
272
+ await unregisterESM();
258
273
 
259
274
  return result;
260
275
  }
@@ -292,3 +307,19 @@ export function waitOnUrl(waitUrl: string, timeout: number, delay: number) {
292
307
  ),
293
308
  );
294
309
  }
310
+
311
+ /**
312
+ * Copies static assets to the report directory
313
+ * @param reportDir Directory where the report will be generated
314
+ */
315
+ export async function copyStatics(reportDir: string): Promise<void> {
316
+ const clientDir = path.join(path.dirname(fileURLToPath(importMetaUrl)), '../../dist/client/web');
317
+ const assets = (await fs.promises.readdir(path.join(clientDir, 'assets'), { withFileTypes: true }))
318
+ .filter((dirent) => dirent.isFile())
319
+ .map((dirent) => dirent.name);
320
+ await fs.promises.mkdir(path.join(reportDir, 'assets'), { recursive: true });
321
+ await fs.promises.copyFile(path.join(clientDir, 'index.html'), path.join(reportDir, 'index.html'));
322
+ for (const asset of assets) {
323
+ await fs.promises.copyFile(path.join(clientDir, 'assets', asset), path.join(reportDir, 'assets', asset));
324
+ }
325
+ }
@@ -218,7 +218,7 @@ export async function start(browser: string, gridUrl: string, config: Config, op
218
218
  browserName: baseContext.browserName,
219
219
  workerId: process.pid,
220
220
  images: imagesContext.images,
221
- error: serializeError(error),
221
+ error: error ? serializeError(error) : undefined,
222
222
  duration,
223
223
  attachments: imagesContext.attachments,
224
224
  retries: message.payload.retries,
package/src/types.ts CHANGED
@@ -268,20 +268,17 @@ export interface Config {
268
268
  /**
269
269
  * Creevey has two built-in stories providers.
270
270
  *
271
- * `nodejsStoriesProvider` - The first one is used by default except if CSFv3 is enabled in Storybook.
272
- * This provider builds and runs storybook in nodejs env, that allows write interaction tests by using Selenium API.
273
- * The downside is it depends from project build specific and slightly increases init time.
274
- *
275
- * `browserStoriesProvider` - The second one is used by default with CSFv3 storybook feature.
276
- * It load stories from storybook which is running in browser, like storyshots or loki do it.
271
+ * `browserStoriesProvider` - Extracts stories directly from the Storybook UI. It loads stories from storybook which is running in browser, like storyshots or loki do it.
277
272
  * The downside of this, you can't use interaction tests in Creevey, unless you use CSFv3.
278
273
  * Where you can define `play` method for each story
279
274
  *
275
+ * `hybridStoriesProvider` - Combines stories from Storybook with tests from separate files. This is the default provider used in the configuration.
276
+ *
280
277
  * Usage
281
278
  * ``` typescript
282
- * import { nodejsStoriesProvider as provider } from 'creevey'
283
- * // or
284
279
  * import { browserStoriesProvider as provider } from 'creevey'
280
+ * // or
281
+ * import { hybridStoriesProvider as provider } from 'creevey'
285
282
  *
286
283
  * // Creevey config
287
284
  * module.exports = {
@@ -353,7 +350,15 @@ export interface Options {
353
350
  _: string[];
354
351
  config?: string;
355
352
  port: number;
353
+ /**
354
+ * Run in UI mode with web interface for reviewing test results
355
+ * When used with `update` flag, enables UI Update Mode for approving screenshots
356
+ */
356
357
  ui: boolean;
358
+ /**
359
+ * Run in update mode to approve failed tests
360
+ * When used with `ui` flag, enables UI Update Mode for approving screenshots from browser
361
+ */
357
362
  update: boolean | string;
358
363
  debug: boolean;
359
364
  trace: boolean;
@@ -529,6 +534,7 @@ export interface CreeveyStatus {
529
534
  isRunning: boolean;
530
535
  tests: Partial<Record<string, TestData>>;
531
536
  browsers: string[];
537
+ isUpdateMode: boolean;
532
538
  }
533
539
 
534
540
  export interface CreeveyUpdate {