creevey 0.10.0-beta.42 → 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 (141) 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/decorator.d.ts +1 -1
  5. package/dist/client/addon/makeDecorator.d.ts +9 -0
  6. package/dist/client/addon/makeDecorator.js +48 -0
  7. package/dist/client/addon/makeDecorator.js.map +1 -0
  8. package/dist/client/addon/preview.d.ts +1 -1
  9. package/dist/client/addon/withCreevey.d.ts +2 -1
  10. package/dist/client/addon/withCreevey.js +3 -20
  11. package/dist/client/addon/withCreevey.js.map +1 -1
  12. package/dist/client/shared/components/PageHeader/PageHeader.js +13 -4
  13. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  14. package/dist/client/shared/creeveyClientApi.js +10 -0
  15. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  16. package/dist/client/web/CreeveyApp.d.ts +1 -0
  17. package/dist/client/web/CreeveyApp.js +1 -0
  18. package/dist/client/web/CreeveyApp.js.map +1 -1
  19. package/dist/client/web/CreeveyContext.d.ts +1 -0
  20. package/dist/client/web/CreeveyContext.js +1 -0
  21. package/dist/client/web/CreeveyContext.js.map +1 -1
  22. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +9 -8
  23. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  24. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +13 -3
  25. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  26. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +2 -3
  27. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  28. package/dist/client/web/CreeveyView/SideBar/TestLink.js +2 -3
  29. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  30. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +1 -0
  31. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  32. package/dist/client/web/assets/{index-C47njyZV.js → index-BU4jjKVC.js} +68 -68
  33. package/dist/client/web/index.html +1 -1
  34. package/dist/client/web/index.js +8 -3
  35. package/dist/client/web/index.js.map +1 -1
  36. package/dist/creevey.d.ts +1 -1
  37. package/dist/creevey.js +1 -22
  38. package/dist/creevey.js.map +1 -1
  39. package/dist/playwright-reporter.d.ts +2 -0
  40. package/dist/playwright-reporter.js +5 -0
  41. package/dist/playwright-reporter.js.map +1 -0
  42. package/dist/playwright.d.ts +1 -1
  43. package/dist/server/config.js +8 -1
  44. package/dist/server/config.js.map +1 -1
  45. package/dist/server/index.js +10 -3
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/master/api.d.ts +15 -5
  48. package/dist/server/master/api.js +89 -27
  49. package/dist/server/master/api.js.map +1 -1
  50. package/dist/server/master/handlers/capture-handler.d.ts +2 -0
  51. package/dist/server/master/handlers/capture-handler.js +35 -0
  52. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  53. package/dist/server/master/handlers/index.d.ts +4 -0
  54. package/dist/server/master/handlers/index.js +21 -0
  55. package/dist/server/master/handlers/index.js.map +1 -0
  56. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  57. package/dist/server/master/handlers/ping-handler.js +7 -0
  58. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  59. package/dist/server/master/handlers/static-handler.d.ts +2 -0
  60. package/dist/server/master/handlers/static-handler.js +32 -0
  61. package/dist/server/master/handlers/static-handler.js.map +1 -0
  62. package/dist/server/master/handlers/stories-handler.d.ts +2 -0
  63. package/dist/server/master/handlers/stories-handler.js +38 -0
  64. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  65. package/dist/server/master/master.js +7 -24
  66. package/dist/server/master/master.js.map +1 -1
  67. package/dist/server/master/runner.d.ts +4 -6
  68. package/dist/server/master/runner.js +30 -127
  69. package/dist/server/master/runner.js.map +1 -1
  70. package/dist/server/master/server.js +77 -87
  71. package/dist/server/master/server.js.map +1 -1
  72. package/dist/server/master/start.d.ts +1 -2
  73. package/dist/server/master/start.js +11 -29
  74. package/dist/server/master/start.js.map +1 -1
  75. package/dist/server/master/testsManager.d.ts +81 -0
  76. package/dist/server/master/testsManager.js +281 -0
  77. package/dist/server/master/testsManager.js.map +1 -0
  78. package/dist/server/playwright/reporter.d.ts +87 -0
  79. package/dist/server/playwright/reporter.js +351 -0
  80. package/dist/server/playwright/reporter.js.map +1 -0
  81. package/dist/server/selenium/internal.js +20 -2
  82. package/dist/server/selenium/internal.js.map +1 -1
  83. package/dist/server/selenium/selenoid.js +4 -0
  84. package/dist/server/selenium/selenoid.js.map +1 -1
  85. package/dist/server/shutdown.d.ts +1 -0
  86. package/dist/server/shutdown.js +23 -0
  87. package/dist/server/shutdown.js.map +1 -0
  88. package/dist/server/stories.d.ts +0 -1
  89. package/dist/server/stories.js +0 -12
  90. package/dist/server/stories.js.map +1 -1
  91. package/dist/server/ui-update.d.ts +10 -0
  92. package/dist/server/ui-update.js +39 -0
  93. package/dist/server/ui-update.js.map +1 -0
  94. package/dist/server/utils.d.ts +6 -0
  95. package/dist/server/utils.js +39 -8
  96. package/dist/server/utils.js.map +1 -1
  97. package/dist/server/worker/start.js +1 -1
  98. package/dist/server/worker/start.js.map +1 -1
  99. package/dist/types.d.ts +14 -8
  100. package/dist/types.js.map +1 -1
  101. package/docs/examples/playwright-reporter-example.ts +202 -0
  102. package/docs/migration-0.9-to-0.10.md +144 -0
  103. package/docs/playwright-reporter.md +357 -0
  104. package/package.json +9 -13
  105. package/src/client/addon/controller.ts +1 -1
  106. package/src/client/addon/makeDecorator.ts +69 -0
  107. package/src/client/addon/withCreevey.ts +4 -17
  108. package/src/client/shared/components/PageHeader/PageHeader.tsx +18 -4
  109. package/src/client/shared/creeveyClientApi.ts +10 -0
  110. package/src/client/web/CreeveyApp.tsx +2 -0
  111. package/src/client/web/CreeveyContext.tsx +2 -0
  112. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +19 -17
  113. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +18 -3
  114. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +9 -7
  115. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +8 -6
  116. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +1 -0
  117. package/src/client/web/index.tsx +8 -3
  118. package/src/creevey.ts +1 -24
  119. package/src/playwright-reporter.ts +3 -0
  120. package/src/server/config.ts +9 -1
  121. package/src/server/index.ts +11 -4
  122. package/src/server/master/api.ts +95 -26
  123. package/src/server/master/handlers/capture-handler.ts +39 -0
  124. package/src/server/master/handlers/index.ts +4 -0
  125. package/src/server/master/handlers/ping-handler.ts +5 -0
  126. package/src/server/master/handlers/static-handler.ts +29 -0
  127. package/src/server/master/handlers/stories-handler.ts +48 -0
  128. package/src/server/master/master.ts +10 -27
  129. package/src/server/master/runner.ts +38 -132
  130. package/src/server/master/server.ts +93 -97
  131. package/src/server/master/start.ts +17 -41
  132. package/src/server/master/testsManager.ts +315 -0
  133. package/src/server/playwright/reporter.ts +386 -0
  134. package/src/server/selenium/internal.ts +23 -3
  135. package/src/server/selenium/selenoid.ts +5 -0
  136. package/src/server/shutdown.ts +19 -0
  137. package/src/server/stories.ts +1 -12
  138. package/src/server/ui-update.ts +46 -0
  139. package/src/server/utils.ts +40 -9
  140. package/src/server/worker/start.ts +1 -1
  141. package/src/types.ts +14 -8
@@ -0,0 +1,39 @@
1
+ import { Request, Response } from 'hyper-express';
2
+ import cluster from 'cluster';
3
+ import { subscribeOnWorker, sendStoriesMessage } from '../../messages.js';
4
+ import { CaptureOptions, isDefined } from '../../../types.js';
5
+
6
+ export async function captureHandler(request: Request, response: Response): Promise<void> {
7
+ const { workerId, options } = await request.json<
8
+ { workerId: number; options?: CaptureOptions },
9
+ {
10
+ workerId: number;
11
+ options?: CaptureOptions;
12
+ }
13
+ >({
14
+ workerId: 0,
15
+ options: undefined,
16
+ });
17
+
18
+ const worker = Object.values(cluster.workers ?? {})
19
+ .filter(isDefined)
20
+ .find((worker) => worker.process.pid == workerId);
21
+
22
+ // NOTE: Hypothetical case when someone send to us capture req and we don't have a worker with browser session for it
23
+ if (!worker) {
24
+ response.send();
25
+ return;
26
+ }
27
+
28
+ await new Promise<void>((resolve) => {
29
+ const unsubscribe = subscribeOnWorker(worker, 'stories', (message) => {
30
+ if (message.type != 'capture') return;
31
+ unsubscribe();
32
+ resolve();
33
+ });
34
+ sendStoriesMessage(worker, { type: 'capture', payload: options });
35
+ });
36
+
37
+ // TODO Pass screenshot result to show it in inspector
38
+ response.send('Ok');
39
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ping-handler.js';
2
+ export * from './stories-handler.js';
3
+ export * from './capture-handler.js';
4
+ export * from './static-handler.js';
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from 'hyper-express';
2
+
3
+ export function pingHandler(_request: Request, response: Response): void {
4
+ response.send('pong');
5
+ }
@@ -0,0 +1,29 @@
1
+ import { Request, Response } from 'hyper-express';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { logger } from '../../logger.js';
5
+
6
+ export function createStaticFileHandler(baseDir: string, pathPrefix?: string) {
7
+ return (request: Request, response: Response): void => {
8
+ try {
9
+ const decodedPath = decodeURIComponent(request.path);
10
+ const relativePath = pathPrefix ? decodedPath.replace(pathPrefix, '') : decodedPath;
11
+ let filePath = path.join(baseDir, relativePath || 'index.html');
12
+
13
+ // If the path points to a directory, append index.html
14
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
15
+ filePath = path.join(filePath, 'index.html');
16
+ }
17
+
18
+ if (!fs.existsSync(filePath)) {
19
+ response.status(404).send('File not found');
20
+ return;
21
+ }
22
+
23
+ response.sendFile(filePath);
24
+ } catch (error) {
25
+ logger().error('Error serving file', error);
26
+ response.status(500).send('Internal server error');
27
+ }
28
+ };
29
+ }
@@ -0,0 +1,48 @@
1
+ import { Request, Response } from 'hyper-express';
2
+ import cluster from 'cluster';
3
+ import { emitStoriesMessage, sendStoriesMessage } from '../../messages.js';
4
+ import { isDefined, StoryInput } from '../../../types.js';
5
+ import { deserializeStory } from '../../../shared/index.js';
6
+
7
+ export function createStoriesHandler() {
8
+ let setStoriesCounter = 0;
9
+
10
+ // TODO We need this handler for getting stories updates from a browser
11
+ return async (request: Request, response: Response): Promise<void> => {
12
+ const { setStoriesCounter: counter, stories } = await request.json<
13
+ {
14
+ setStoriesCounter: number;
15
+ stories: [string, StoryInput[]][];
16
+ },
17
+ {
18
+ setStoriesCounter: number;
19
+ stories: [string, StoryInput[]][];
20
+ }
21
+ >({
22
+ setStoriesCounter: 0,
23
+ stories: [],
24
+ });
25
+
26
+ if (setStoriesCounter >= counter) {
27
+ response.send();
28
+ return;
29
+ }
30
+
31
+ const deserializedStories = stories.map<[string, StoryInput[]]>(([file, stories]) => [
32
+ file,
33
+ stories.map(deserializeStory),
34
+ ]);
35
+
36
+ setStoriesCounter = counter;
37
+ emitStoriesMessage({ type: 'update', payload: deserializedStories });
38
+
39
+ Object.values(cluster.workers ?? {})
40
+ .filter(isDefined)
41
+ .filter((worker) => worker.isConnected())
42
+ .forEach((worker) => {
43
+ sendStoriesMessage(worker, { type: 'update', payload: deserializedStories });
44
+ });
45
+
46
+ response.send();
47
+ };
48
+ }
@@ -1,44 +1,27 @@
1
- import path from 'path';
2
- import { Config, TestData, isDefined, ServerTest } from '../../types.js';
3
- import { loadTestsFromStories, saveTestsJson } from '../stories.js';
1
+ import { Config } from '../../types.js';
2
+ import { loadTestsFromStories } from '../stories.js';
4
3
  import Runner from './runner.js';
5
- import { tryToLoadTestsData } from '../utils.js';
6
-
7
- function mergeTests(
8
- testsWithReports: Partial<Record<string, TestData>>,
9
- testsFromStories: Partial<Record<string, ServerTest>>,
10
- ): Partial<Record<string, ServerTest>> {
11
- Object.values(testsFromStories)
12
- .filter(isDefined)
13
- .forEach((test) => {
14
- const testWithReport = testsWithReports[test.id];
15
- if (!testWithReport) return;
16
- test.retries = testWithReport.retries;
17
- if (testWithReport.status == 'success' || testWithReport.status == 'failed') test.status = testWithReport.status;
18
- test.results = testWithReport.results;
19
- test.approved = testWithReport.approved;
20
- });
21
- return testsFromStories;
22
- }
4
+ import { TestsManager } from './testsManager.js';
23
5
 
24
6
  export default async function master(config: Config, gridUrl?: string): Promise<Runner> {
25
- const runner = new Runner(config, gridUrl);
26
- const reportDataPath = path.join(config.reportDir, 'data.js');
27
- const testsFromReport = tryToLoadTestsData(reportDataPath) ?? {};
7
+ // Create TestsManager instance
8
+ const testsManager = new TestsManager(config.screenDir, config.reportDir);
9
+
10
+ // Create Runner with TestsManager
11
+ const runner = new Runner(config, testsManager, gridUrl);
28
12
 
29
13
  await runner.init();
30
14
 
15
+ // Load tests from stories and update TestsManager
31
16
  const tests = await loadTestsFromStories(
32
17
  Object.keys(config.browsers),
33
18
  (listener) => config.storiesProvider(config, listener),
34
19
  (testsDiff) => {
35
20
  runner.updateTests(testsDiff);
36
- saveTestsJson(runner.tests, config.reportDir);
37
21
  },
38
22
  );
39
23
 
40
- runner.tests = mergeTests(testsFromReport, tests);
41
- saveTestsJson(runner.tests, config.reportDir);
24
+ testsManager.updateTests(testsManager.loadAndMergeTests(tests));
42
25
 
43
26
  return runner;
44
27
  }
@@ -1,5 +1,3 @@
1
- import path from 'path';
2
- import { copyFile, mkdir } from 'fs/promises';
3
1
  import { EventEmitter } from 'events';
4
2
  import {
5
3
  Config,
@@ -10,7 +8,6 @@ import {
10
8
  CreeveyUpdate,
11
9
  TestStatus,
12
10
  ServerTest,
13
- TestMeta,
14
11
  TEST_EVENTS,
15
12
  FakeSuite,
16
13
  FakeTest,
@@ -19,6 +16,7 @@ import Pool from './pool.js';
19
16
  import { WorkerQueue } from './queue.js';
20
17
  import { getTestPath } from '../utils.js';
21
18
  import { getReporter } from '../reporters/index.js';
19
+ import { TestsManager } from './testsManager.js';
22
20
 
23
21
  // NOTE: This is workaround to fix parallel tests running with mocha-junit-reporter
24
22
  let isJUnit = false;
@@ -33,24 +31,23 @@ class FakeRunner extends EventEmitter {
33
31
 
34
32
  export default class Runner extends EventEmitter {
35
33
  private failFast: boolean;
36
- private screenDir: string;
37
- private reportDir: string;
38
34
  private browsers: string[];
39
35
  private scheduler: WorkerQueue;
40
36
  private pools: Record<string, Pool> = {};
41
37
  private fakeRunner: FakeRunner;
42
38
  private config: Config;
43
- tests: Partial<Record<string, ServerTest>> = {};
39
+ public testsManager: TestsManager;
40
+
44
41
  public get isRunning(): boolean {
45
42
  return Object.values(this.pools).some((pool) => pool.isRunning);
46
43
  }
47
- constructor(config: Config, gridUrl?: string) {
44
+
45
+ constructor(config: Config, testsManager: TestsManager, gridUrl?: string) {
48
46
  super();
49
47
 
50
48
  this.config = config;
51
49
  this.failFast = config.failFast;
52
- this.screenDir = config.screenDir;
53
- this.reportDir = config.reportDir;
50
+ this.testsManager = testsManager;
54
51
  this.scheduler = new WorkerQueue(config.useWorkerQueue);
55
52
  this.browsers = Object.keys(config.browsers);
56
53
 
@@ -61,7 +58,7 @@ export default class Runner extends EventEmitter {
61
58
  isJUnit = true;
62
59
  }
63
60
 
64
- new Reporter(runner, { reportDir: this.reportDir, reporterOptions: config.reporterOptions });
61
+ new Reporter(runner, { reportDir: config.reportDir, reporterOptions: config.reporterOptions });
65
62
  this.fakeRunner = runner;
66
63
 
67
64
  this.browsers
@@ -71,10 +68,10 @@ export default class Runner extends EventEmitter {
71
68
 
72
69
  private handlePoolMessage = (message: { id: string; status: TestStatus; result?: TestResult }): void => {
73
70
  const { id, status, result } = message;
74
- const test = this.tests[id];
71
+ const test = this.testsManager.getTest(id);
75
72
 
76
73
  if (!test) return;
77
- const { browser, testName, storyPath, storyId } = test;
74
+ const { browser, testName } = test;
78
75
 
79
76
  const fakeSuite: FakeSuite = {
80
77
  title: test.storyPath.slice(0, -1).join('/'),
@@ -103,20 +100,14 @@ export default class Runner extends EventEmitter {
103
100
 
104
101
  fakeSuite.tests.push(fakeTest);
105
102
 
106
- // TODO Handle 'retrying' status
107
- test.status = status == 'retrying' ? 'failed' : status;
103
+ const update = this.testsManager.updateTestStatus(id, status, result);
104
+ if (!update) return;
105
+
108
106
  if (!result) {
109
- // NOTE: Running status
110
107
  this.fakeRunner.emit(TEST_EVENTS.TEST_BEGIN, fakeTest);
111
- this.sendUpdate({ tests: { [id]: { id, browser, testName, storyPath, status: test.status, storyId } } });
108
+ this.sendUpdate(update);
112
109
  return;
113
110
  }
114
- test.results ??= [];
115
- test.results.push(result);
116
-
117
- if (status == 'failed') {
118
- test.approved = null;
119
- }
120
111
 
121
112
  const { duration, attachments } = result;
122
113
 
@@ -146,20 +137,7 @@ export default class Runner extends EventEmitter {
146
137
 
147
138
  this.fakeRunner.emit(TEST_EVENTS.TEST_END, fakeTest);
148
139
 
149
- this.sendUpdate({
150
- tests: {
151
- [id]: {
152
- id,
153
- browser,
154
- testName,
155
- storyPath,
156
- status: test.status,
157
- approved: test.approved,
158
- results: [result],
159
- storyId,
160
- },
161
- },
162
- });
140
+ this.sendUpdate(update);
163
141
 
164
142
  if (this.failFast && status == 'failed') this.stop();
165
143
  };
@@ -177,31 +155,8 @@ export default class Runner extends EventEmitter {
177
155
  }
178
156
 
179
157
  public updateTests(testsDiff: Partial<Record<string, ServerTest>>): void {
180
- const tests: CreeveyStatus['tests'] = {};
181
- const removedTests: TestMeta[] = [];
182
- Object.entries(testsDiff).forEach(([id, newTest]) => {
183
- const oldTest = this.tests[id];
184
- if (newTest) {
185
- if (oldTest) {
186
- this.tests[id] = {
187
- ...newTest,
188
- retries: oldTest.retries,
189
- results: oldTest.results,
190
- approved: oldTest.approved,
191
- };
192
- } else this.tests[id] = newTest;
193
-
194
- const { story: _, fn: __, ...restTest } = newTest;
195
- tests[id] = { ...restTest, status: 'unknown' };
196
- } else if (oldTest) {
197
- const { id, browser, testName, storyPath, storyId } = oldTest;
198
- removedTests.push({ id, browser, testName, storyPath, storyId });
199
- // TODO Use Map instead
200
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
201
- delete this.tests[id];
202
- }
203
- });
204
- this.sendUpdate({ tests, removedTests });
158
+ const update = this.testsManager.updateTests(testsDiff);
159
+ if (update) this.sendUpdate(update);
205
160
  }
206
161
 
207
162
  public start(ids: string[]): void {
@@ -209,27 +164,32 @@ export default class Runner extends EventEmitter {
209
164
  if (this.isRunning) return;
210
165
 
211
166
  const testsToStart = ids
212
- .map((id) => this.tests[id])
167
+ .map((id) => this.testsManager.getTest(id))
213
168
  .filter(isDefined)
214
169
  .filter((test) => !test.skip);
215
170
 
216
171
  if (testsToStart.length == 0) return;
217
172
 
173
+ const pendingTests: CreeveyUpdate['tests'] = testsToStart.reduce(
174
+ (update: CreeveyUpdate['tests'], { id, storyId, browser, testName, storyPath }) => ({
175
+ ...update,
176
+ [id]: { id, browser, testName, storyPath, status: 'pending', storyId },
177
+ }),
178
+ {},
179
+ );
180
+
218
181
  this.sendUpdate({
219
182
  isRunning: true,
220
- tests: testsToStart.reduce(
221
- (update: CreeveyUpdate['tests'], { id, storyId, browser, testName, storyPath }) => ({
222
- ...update,
223
- [id]: { id, browser, testName, storyPath, status: 'pending', storyId },
224
- }),
225
- {},
226
- ),
183
+ tests: pendingTests,
227
184
  });
228
185
 
229
186
  const testsByBrowser: Partial<TestsByBrowser> = testsToStart.reduce((tests: Partial<TestsByBrowser>, test) => {
230
187
  const { id, browser, testName, storyPath } = test;
231
188
  const restPath = [...storyPath, testName].filter(isDefined);
232
- test.status = 'pending';
189
+
190
+ // Update status to pending in TestsManager
191
+ this.testsManager.updateTestStatus(id, 'pending');
192
+
233
193
  return {
234
194
  ...tests,
235
195
  [browser]: [...(tests[browser] ?? []), { id, path: restPath }],
@@ -255,78 +215,24 @@ export default class Runner extends EventEmitter {
255
215
  }
256
216
 
257
217
  public get status(): CreeveyStatus {
258
- const tests: CreeveyStatus['tests'] = {};
259
- Object.values(this.tests)
260
- .filter(isDefined)
261
-
262
- .forEach(({ story: _, fn: __, ...test }) => (tests[test.id] = test));
263
218
  return {
264
219
  isRunning: this.isRunning,
265
- tests,
220
+ tests: this.testsManager.getTestsData(),
266
221
  browsers: this.browsers,
222
+ isUpdateMode: false,
267
223
  };
268
224
  }
269
225
 
270
- private async copyImage(test: ServerTest, image: string, actual: string): Promise<void> {
271
- const { browser, testName, storyPath } = test;
272
- const restPath = [...storyPath, testName].filter(isDefined);
273
- const testPath = path.join(...restPath, image == browser ? '' : browser);
274
- const srcImagePath = path.join(this.reportDir, testPath, actual);
275
- const dstImagePath = path.join(this.screenDir, testPath, `${image}.png`);
276
- await mkdir(path.join(this.screenDir, testPath), { recursive: true });
277
- await copyFile(srcImagePath, dstImagePath);
278
- }
279
-
280
226
  public async approveAll(): Promise<void> {
281
- const updatedTests: NonNullable<CreeveyUpdate['tests']> = {};
282
- for (const test of Object.values(this.tests)) {
283
- if (!test?.results) continue;
284
- const retry = test.results.length - 1;
285
- const { images, status } = test.results.at(retry) ?? {};
286
- if (!images || status != 'failed') continue;
287
- for (const [name, image] of Object.entries(images)) {
288
- if (!image) continue;
289
- await this.copyImage(test, name, image.actual);
290
-
291
- test.approved ??= {};
292
- test.approved[name] = retry;
293
- test.status = 'approved';
294
-
295
- updatedTests[test.id] = {
296
- id: test.id,
297
- browser: test.browser,
298
- storyPath: test.storyPath,
299
- storyId: test.storyId,
300
- status: test.status,
301
- approved: { [name]: retry },
302
- };
303
- }
304
- }
305
- this.sendUpdate({ tests: updatedTests });
227
+ const update = await this.testsManager.approveAll();
228
+ this.sendUpdate(update);
306
229
  }
307
230
 
308
- public async approve({ id, retry, image }: ApprovePayload): Promise<void> {
309
- const test = this.tests[id];
310
- if (!test?.results) return;
311
- const result = test.results[retry];
312
- if (!result.images) return;
313
- const images = result.images[image];
314
- if (!images) return;
315
- test.approved ??= {};
316
- const { browser, testName, storyPath, storyId } = test;
317
-
318
- await this.copyImage(test, image, images.actual);
319
-
320
- test.approved[image] = retry;
321
-
322
- if (Object.keys(result.images).every((name) => typeof test.approved?.[name] == 'number')) {
323
- test.status = 'approved';
324
- }
325
-
326
- this.sendUpdate({
327
- tests: { [id]: { id, browser, testName, storyPath, status: test.status, approved: { [image]: retry }, storyId } },
328
- });
231
+ public async approve(payload: ApprovePayload): Promise<void> {
232
+ const update = await this.testsManager.approve(payload);
233
+ if (update) this.sendUpdate(update);
329
234
  }
235
+
330
236
  private sendUpdate(data: CreeveyUpdate): void {
331
237
  this.emit('update', data);
332
238
  }