@alwaysmeticulous/cli 2.21.1 → 2.21.3-alpha.1

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.
@@ -51,8 +51,8 @@ const diffScreenshots = async ({ client, headReplayId, baseReplayId, headScreens
51
51
  const headScreenshotFile = (0, path_1.join)(headScreenshotsDir, screenshotFileName);
52
52
  const baseScreenshot = await (0, io_utils_1.readPng)(baseScreenshotFile);
53
53
  const headScreenshot = await (0, io_utils_1.readPng)(headScreenshotFile);
54
- if (baseScreenshot.width !== baseScreenshot.width ||
55
- headScreenshot.height !== headScreenshot.height) {
54
+ if (baseScreenshot.width !== headScreenshot.width ||
55
+ baseScreenshot.height !== headScreenshot.height) {
56
56
  return [
57
57
  {
58
58
  identifier,
@@ -1,191 +1,102 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const merge_test_results_1 = require("../merge-test-results");
4
+ const mock_test_results_1 = require("./mock-test-results");
4
5
  describe("mergeResults", () => {
5
6
  it("keeps the result as a failure when all retried screenshots are 'identical'", () => {
6
- const currentResult = testResult("fail", [diff(0), noDiff(1)]);
7
- const comparisonToHeadReplay = testResult("pass", [noDiff(0), noDiff(1)]);
7
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.noDiff)(1)]);
8
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.noDiff)(1)]);
8
9
  const mergedResult = (0, merge_test_results_1.mergeResults)({
9
10
  currentResult,
10
11
  comparisonToHeadReplay,
11
12
  });
12
- expect(mergedResult).toEqual(testResult("fail", [diff(0), noDiff(1)]));
13
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.noDiff)(1)]));
13
14
  });
14
15
  it("ignores diffs to screenshots which originally passed", () => {
15
- const currentResult = testResult("pass", [noDiff(0), noDiff(1)]);
16
- const comparisonToHeadReplay = testResult("fail", [noDiff(0), diff(1)]);
16
+ const currentResult = (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.noDiff)(1)]);
17
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1)]);
17
18
  const mergedResult = (0, merge_test_results_1.mergeResults)({
18
19
  currentResult,
19
20
  comparisonToHeadReplay,
20
21
  });
21
- expect(mergedResult).toEqual(testResult("pass", [noDiff(0), noDiff(1)]));
22
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.noDiff)(1)]));
22
23
  });
23
24
  it("marks screenshots as flakes if the screenshot comparison originally failed, but the second retry gives a different screenshot again", () => {
24
- const currentResult = testResult("fail", [noDiff(0), diff(1), diff(2)]);
25
- const comparisonToHeadReplay = testResult("fail", [
26
- noDiff(0),
27
- diff(1),
28
- noDiff(2),
25
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1), (0, mock_test_results_1.diff)(2)]);
26
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("fail", [
27
+ (0, mock_test_results_1.noDiff)(0),
28
+ (0, mock_test_results_1.diff)(1),
29
+ (0, mock_test_results_1.noDiff)(2),
29
30
  ]);
30
31
  const mergedResult = (0, merge_test_results_1.mergeResults)({
31
32
  currentResult,
32
33
  comparisonToHeadReplay,
33
34
  });
34
- expect(mergedResult).toEqual(testResult("fail", [noDiff(0), flake(1, diff(), [diff()]), diff(2)]));
35
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.diff)()]), (0, mock_test_results_1.diff)(2)]));
35
36
  });
36
37
  it("marks overall test as a flake if there are only flakey screenshots and no failed screenshots", () => {
37
- const currentResult = testResult("fail", [noDiff(0), diff(1), diff(2)]);
38
- const comparisonToHeadReplay = testResult("fail", [
39
- noDiff(0),
40
- diff(1),
41
- diff(2),
38
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1), (0, mock_test_results_1.diff)(2)]);
39
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("fail", [
40
+ (0, mock_test_results_1.noDiff)(0),
41
+ (0, mock_test_results_1.diff)(1),
42
+ (0, mock_test_results_1.diff)(2),
42
43
  ]);
43
44
  const mergedResult = (0, merge_test_results_1.mergeResults)({
44
45
  currentResult,
45
46
  comparisonToHeadReplay,
46
47
  });
47
- expect(mergedResult).toEqual(testResult("flake", [
48
- noDiff(0),
49
- flake(1, diff(), [diff()]),
50
- flake(2, diff(), [diff()]),
48
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("flake", [
49
+ (0, mock_test_results_1.noDiff)(0),
50
+ (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.diff)()]),
51
+ (0, mock_test_results_1.flake)(2, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.diff)()]),
51
52
  ]));
52
53
  });
53
54
  it("adds to diffsToHeadScreenshotOnRetries for existing flakes", () => {
54
- const currentResult = testResult("fail", [
55
- diff(0),
56
- flake(1, diff(), [missingHead()]),
57
- flake(2, differentSize(), [diff()]),
58
- flake(3, missingBase(), [diff(), diff()]),
55
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [
56
+ (0, mock_test_results_1.diff)(0),
57
+ (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.missingHead)()]),
58
+ (0, mock_test_results_1.flake)(2, (0, mock_test_results_1.differentSize)(), [(0, mock_test_results_1.diff)()]),
59
+ (0, mock_test_results_1.flake)(3, (0, mock_test_results_1.missingBase)(), [(0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)()]),
59
60
  ]);
60
- const comparisonToHeadReplay = testResult("fail", [
61
- noDiff(0),
62
- diff(1),
63
- differentSize(2),
64
- missingHead(3),
61
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("fail", [
62
+ (0, mock_test_results_1.noDiff)(0),
63
+ (0, mock_test_results_1.diff)(1),
64
+ (0, mock_test_results_1.differentSize)(2),
65
+ (0, mock_test_results_1.missingHead)(3),
65
66
  ]);
66
67
  const mergedResult = (0, merge_test_results_1.mergeResults)({
67
68
  currentResult,
68
69
  comparisonToHeadReplay,
69
70
  });
70
- expect(mergedResult).toEqual(testResult("fail", [
71
- diff(0),
72
- flake(1, diff(), [missingHead(), diff()]),
73
- flake(2, differentSize(), [diff(), differentSize()]),
74
- flake(3, missingBase(), [diff(), diff(), missingHead()]),
71
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("fail", [
72
+ (0, mock_test_results_1.diff)(0),
73
+ (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.missingHead)(), (0, mock_test_results_1.diff)()]),
74
+ (0, mock_test_results_1.flake)(2, (0, mock_test_results_1.differentSize)(), [(0, mock_test_results_1.diff)(), (0, mock_test_results_1.differentSize)()]),
75
+ (0, mock_test_results_1.flake)(3, (0, mock_test_results_1.missingBase)(), [(0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)(), (0, mock_test_results_1.missingHead)()]),
75
76
  ]));
76
77
  });
77
78
  it("keeps a missing-head as is, if there is no corresponding retry screenshot", () => {
78
- const currentResult = testResult("fail", [missingHead(0)]);
79
- const comparisonToHeadReplay = testResult("pass", []);
79
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.missingHead)(0)]);
80
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("pass", []);
80
81
  const mergedResult = (0, merge_test_results_1.mergeResults)({
81
82
  currentResult,
82
83
  comparisonToHeadReplay,
83
84
  });
84
- expect(mergedResult).toEqual(testResult("fail", [missingHead(0)]));
85
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.missingHead)(0)]));
85
86
  });
86
87
  it("adds to diffsToHeadScreenshotOnRetries for a flakey missing-head, if there is no corresponding retry screenshot", () => {
87
- const currentResult = testResult("fail", [
88
- flake(0, missingHead(), [missingBase()]),
89
- diff(1),
88
+ const currentResult = (0, mock_test_results_1.testResult)("fail", [
89
+ (0, mock_test_results_1.flake)(0, (0, mock_test_results_1.missingHead)(), [(0, mock_test_results_1.missingBase)()]),
90
+ (0, mock_test_results_1.diff)(1),
90
91
  ]);
91
- const comparisonToHeadReplay = testResult("pass", [noDiff(1)]);
92
+ const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(1)]);
92
93
  const mergedResult = (0, merge_test_results_1.mergeResults)({
93
94
  currentResult,
94
95
  comparisonToHeadReplay,
95
96
  });
96
- expect(mergedResult).toEqual(testResult("fail", [
97
- flake(0, missingHead(), [missingBase(), missingBaseAndHead()]),
98
- diff(1),
97
+ expect(mergedResult).toEqual((0, mock_test_results_1.testResult)("fail", [
98
+ (0, mock_test_results_1.flake)(0, (0, mock_test_results_1.missingHead)(), [(0, mock_test_results_1.missingBase)(), (0, mock_test_results_1.missingBaseAndHead)()]),
99
+ (0, mock_test_results_1.diff)(1),
99
100
  ]));
100
101
  });
101
102
  });
102
- const id = (eventNumber = 0) => ({
103
- type: "after-event",
104
- eventNumber,
105
- });
106
- const testResult = (result, screenshotDiffResults) => {
107
- return {
108
- sessionId: "mock-session-id",
109
- headReplayId: "mock-head-replay-id",
110
- result,
111
- screenshotDiffResults,
112
- };
113
- };
114
- const diff = (eventNumber) => {
115
- return {
116
- identifier: id(eventNumber),
117
- outcome: "diff",
118
- headScreenshotFile: "mock-head-file",
119
- baseScreenshotFile: "mock-base-file",
120
- width: 1920,
121
- height: 1080,
122
- mismatchFraction: 0.01,
123
- mismatchPixels: 1000,
124
- };
125
- };
126
- const noDiff = (eventNumber) => {
127
- return {
128
- identifier: id(eventNumber),
129
- outcome: "no-diff",
130
- headScreenshotFile: "mock-head-file",
131
- baseScreenshotFile: "mock-base-file",
132
- width: 1920,
133
- height: 1080,
134
- mismatchFraction: 0.01,
135
- mismatchPixels: 1000,
136
- };
137
- };
138
- const flake = (eventNumber, diffToBaseScreenshot, diffsToHeadScreenshotOnRetries) => {
139
- return {
140
- identifier: id(eventNumber),
141
- outcome: "flake",
142
- diffToBaseScreenshot: asSingleTryDiff(diffToBaseScreenshot),
143
- diffsToHeadScreenshotOnRetries: diffsToHeadScreenshotOnRetries.map(asRetryDiff),
144
- };
145
- };
146
- const missingBase = (eventNumber) => {
147
- return {
148
- identifier: id(eventNumber),
149
- outcome: "missing-base",
150
- headScreenshotFile: "mock-head-file",
151
- };
152
- };
153
- const missingHead = (eventNumber) => {
154
- return {
155
- identifier: id(eventNumber),
156
- outcome: "missing-head",
157
- baseScreenshotFile: "mock-base-file",
158
- };
159
- };
160
- const missingBaseAndHead = (eventNumber) => {
161
- return {
162
- identifier: id(eventNumber),
163
- outcome: "missing-base-and-head",
164
- };
165
- };
166
- const differentSize = (eventNumber) => {
167
- return {
168
- identifier: id(eventNumber),
169
- outcome: "different-size",
170
- baseScreenshotFile: "mock-base-file",
171
- headScreenshotFile: "mock-head-file",
172
- baseWidth: 1920,
173
- baseHeight: 1080,
174
- headWidth: 10,
175
- headHeight: 5,
176
- };
177
- };
178
- const asSingleTryDiff = ({ identifier, // eslint-disable-line @typescript-eslint/no-unused-vars
179
- ...rest }) => {
180
- if (rest.outcome === "flake") {
181
- throw new Error("Must not be a diff with a flake outcome");
182
- }
183
- return rest;
184
- };
185
- const asRetryDiff = ({ identifier, // eslint-disable-line @typescript-eslint/no-unused-vars
186
- ...rest }) => {
187
- if (rest.outcome === "flake") {
188
- throw new Error("Must not be a diff with a flake outcome");
189
- }
190
- return rest;
191
- };
@@ -0,0 +1,17 @@
1
+ import { ScreenshotDiffResult, ScreenshotIdentifier, TestCase } from "@alwaysmeticulous/api";
2
+ import { DetailedTestCaseResult } from "../../config/config.types";
3
+ export declare const id: (eventNumber?: number) => ScreenshotIdentifier;
4
+ export declare const testResult: (result: "pass" | "fail" | "flake", screenshotDiffResults: ScreenshotDiffResult[], testCase?: TestCase) => DetailedTestCaseResult;
5
+ export declare const diff: (eventNumber?: number) => ScreenshotDiffResult;
6
+ export declare const noDiff: (eventNumber?: number) => ScreenshotDiffResult;
7
+ export declare const flake: (eventNumber: number, diffToBaseScreenshot: ScreenshotDiffResult, diffsToHeadScreenshotOnRetries: Array<ScreenshotDiffResult | {
8
+ identifier: ScreenshotIdentifier;
9
+ outcome: "missing-base-and-head";
10
+ }>) => ScreenshotDiffResult;
11
+ export declare const missingBase: (eventNumber?: number) => ScreenshotDiffResult;
12
+ export declare const missingHead: (eventNumber?: number) => ScreenshotDiffResult;
13
+ export declare const missingBaseAndHead: (eventNumber?: number) => {
14
+ readonly identifier: ScreenshotIdentifier;
15
+ readonly outcome: "missing-base-and-head";
16
+ };
17
+ export declare const differentSize: (eventNumber?: number) => ScreenshotDiffResult;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.differentSize = exports.missingBaseAndHead = exports.missingHead = exports.missingBase = exports.flake = exports.noDiff = exports.diff = exports.testResult = exports.id = void 0;
4
+ const id = (eventNumber = 0) => ({
5
+ type: "after-event",
6
+ eventNumber,
7
+ });
8
+ exports.id = id;
9
+ const testResult = (result, screenshotDiffResults, testCase) => {
10
+ var _a;
11
+ return {
12
+ ...testCase,
13
+ sessionId: (_a = testCase === null || testCase === void 0 ? void 0 : testCase.sessionId) !== null && _a !== void 0 ? _a : "mock-session-id",
14
+ headReplayId: "mock-head-replay-id",
15
+ result,
16
+ screenshotDiffResults,
17
+ };
18
+ };
19
+ exports.testResult = testResult;
20
+ const diff = (eventNumber) => {
21
+ return {
22
+ identifier: (0, exports.id)(eventNumber),
23
+ outcome: "diff",
24
+ headScreenshotFile: "mock-head-file",
25
+ baseScreenshotFile: "mock-base-file",
26
+ width: 1920,
27
+ height: 1080,
28
+ mismatchFraction: 0.01,
29
+ mismatchPixels: 1000,
30
+ };
31
+ };
32
+ exports.diff = diff;
33
+ const noDiff = (eventNumber) => {
34
+ return {
35
+ identifier: (0, exports.id)(eventNumber),
36
+ outcome: "no-diff",
37
+ headScreenshotFile: "mock-head-file",
38
+ baseScreenshotFile: "mock-base-file",
39
+ width: 1920,
40
+ height: 1080,
41
+ mismatchFraction: 0.01,
42
+ mismatchPixels: 1000,
43
+ };
44
+ };
45
+ exports.noDiff = noDiff;
46
+ const flake = (eventNumber, diffToBaseScreenshot, diffsToHeadScreenshotOnRetries) => {
47
+ return {
48
+ identifier: (0, exports.id)(eventNumber),
49
+ outcome: "flake",
50
+ diffToBaseScreenshot: asSingleTryDiff(diffToBaseScreenshot),
51
+ diffsToHeadScreenshotOnRetries: diffsToHeadScreenshotOnRetries.map(asRetryDiff),
52
+ };
53
+ };
54
+ exports.flake = flake;
55
+ const missingBase = (eventNumber) => {
56
+ return {
57
+ identifier: (0, exports.id)(eventNumber),
58
+ outcome: "missing-base",
59
+ headScreenshotFile: "mock-head-file",
60
+ };
61
+ };
62
+ exports.missingBase = missingBase;
63
+ const missingHead = (eventNumber) => {
64
+ return {
65
+ identifier: (0, exports.id)(eventNumber),
66
+ outcome: "missing-head",
67
+ baseScreenshotFile: "mock-base-file",
68
+ };
69
+ };
70
+ exports.missingHead = missingHead;
71
+ const missingBaseAndHead = (eventNumber) => {
72
+ return {
73
+ identifier: (0, exports.id)(eventNumber),
74
+ outcome: "missing-base-and-head",
75
+ };
76
+ };
77
+ exports.missingBaseAndHead = missingBaseAndHead;
78
+ const differentSize = (eventNumber) => {
79
+ return {
80
+ identifier: (0, exports.id)(eventNumber),
81
+ outcome: "different-size",
82
+ baseScreenshotFile: "mock-base-file",
83
+ headScreenshotFile: "mock-head-file",
84
+ baseWidth: 1920,
85
+ baseHeight: 1080,
86
+ headWidth: 10,
87
+ headHeight: 5,
88
+ };
89
+ };
90
+ exports.differentSize = differentSize;
91
+ const asSingleTryDiff = ({ identifier, // eslint-disable-line @typescript-eslint/no-unused-vars
92
+ ...rest }) => {
93
+ if (rest.outcome === "flake") {
94
+ throw new Error("Must not be a diff with a flake outcome");
95
+ }
96
+ return rest;
97
+ };
98
+ const asRetryDiff = ({ identifier, // eslint-disable-line @typescript-eslint/no-unused-vars
99
+ ...rest }) => {
100
+ if (rest.outcome === "flake") {
101
+ throw new Error("Must not be a diff with a flake outcome");
102
+ }
103
+ return rest;
104
+ };
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const common_1 = require("@alwaysmeticulous/common");
4
+ const parallel_tests_handler_1 = require("../parallel-tests.handler");
5
+ const mock_test_results_1 = require("./mock-test-results");
6
+ describe("runAllTestsInParallel", () => {
7
+ it("maximises the number of tests run in parralel, up to the provided limit", async () => {
8
+ let progress = {
9
+ runningTestCases: 4,
10
+ failedTestCases: 0,
11
+ flakedTestCases: 0,
12
+ passedTestCases: 0,
13
+ };
14
+ let testsStarted = 0;
15
+ const testRunPromises = [(0, common_1.defer)(), (0, common_1.defer)(), (0, common_1.defer)(), (0, common_1.defer)()];
16
+ const overallPromise = (0, parallel_tests_handler_1.runAllTestsInParallel)({
17
+ testsToRun: [testCase(0), testCase(1), testCase(2), testCase(3)],
18
+ parallelTasks: 2,
19
+ maxRetriesOnFailure: 0,
20
+ executeTest: async (testCase) => {
21
+ var _a;
22
+ testsStarted++;
23
+ await testRunPromises[parseInt((_a = testCase.title) !== null && _a !== void 0 ? _a : "")].promise;
24
+ return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0)], testCase);
25
+ },
26
+ onTestFinished: async (newProgress) => {
27
+ progress = newProgress;
28
+ },
29
+ });
30
+ await waitForPromisesToFlush();
31
+ expect(testsStarted).toEqual(2);
32
+ expect(progress).toEqual({
33
+ runningTestCases: 4,
34
+ failedTestCases: 0,
35
+ flakedTestCases: 0,
36
+ passedTestCases: 0,
37
+ });
38
+ testRunPromises[0].resolve();
39
+ await waitForPromisesToFlush();
40
+ expect(testsStarted).toEqual(3);
41
+ expect(progress).toEqual({
42
+ runningTestCases: 3,
43
+ failedTestCases: 0,
44
+ flakedTestCases: 0,
45
+ passedTestCases: 1,
46
+ });
47
+ testRunPromises[1].resolve();
48
+ testRunPromises[2].resolve();
49
+ await waitForPromisesToFlush();
50
+ expect(testsStarted).toEqual(4);
51
+ expect(progress).toEqual({
52
+ runningTestCases: 1,
53
+ failedTestCases: 0,
54
+ flakedTestCases: 0,
55
+ passedTestCases: 3,
56
+ });
57
+ await expectPromiseToNotHaveResolved(overallPromise);
58
+ testRunPromises[3].resolve();
59
+ await waitForPromisesToFlush();
60
+ expect(progress).toEqual({
61
+ runningTestCases: 0,
62
+ failedTestCases: 0,
63
+ flakedTestCases: 0,
64
+ passedTestCases: 4,
65
+ });
66
+ await overallPromise;
67
+ });
68
+ it("can handle a test failing to run", async () => {
69
+ let progress = {
70
+ runningTestCases: 4,
71
+ failedTestCases: 0,
72
+ flakedTestCases: 0,
73
+ passedTestCases: 0,
74
+ };
75
+ const testRunPromises = [(0, common_1.defer)(), (0, common_1.defer)(), (0, common_1.defer)(), (0, common_1.defer)()];
76
+ const overallPromise = (0, parallel_tests_handler_1.runAllTestsInParallel)({
77
+ testsToRun: [testCase(0), testCase(1), testCase(2), testCase(3)],
78
+ parallelTasks: 2,
79
+ maxRetriesOnFailure: 0,
80
+ executeTest: async (testCase) => {
81
+ var _a;
82
+ await testRunPromises[parseInt((_a = testCase.title) !== null && _a !== void 0 ? _a : "")].promise;
83
+ return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0)], testCase);
84
+ },
85
+ onTestFinished: async (newProgress) => {
86
+ progress = newProgress;
87
+ },
88
+ });
89
+ testRunPromises[0].resolve();
90
+ testRunPromises[1].reject();
91
+ testRunPromises[2].resolve();
92
+ testRunPromises[3].resolve();
93
+ const result = await overallPromise;
94
+ expect(progress).toEqual({
95
+ runningTestCases: 0,
96
+ failedTestCases: 1,
97
+ flakedTestCases: 0,
98
+ passedTestCases: 3,
99
+ });
100
+ // No result given for test 1 (it can't, because it doesn't have a headReplayId)
101
+ expect(result).toEqual([
102
+ (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0)], testCase(0)),
103
+ (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0)], testCase(2)),
104
+ (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0)], testCase(3)),
105
+ ]);
106
+ });
107
+ it("does not retry failed tests if maxRetriesOnFailure is 0", async () => {
108
+ const results = await (0, parallel_tests_handler_1.runAllTestsInParallel)({
109
+ testsToRun: [testCase(0)],
110
+ parallelTasks: 2,
111
+ maxRetriesOnFailure: 0,
112
+ executeTest: async (testCase) => {
113
+ return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0)], testCase);
114
+ },
115
+ });
116
+ expect(results).toEqual([(0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0)], testCase(0))]);
117
+ });
118
+ it("retries failed tests until maxRetriesOnFailure reached or first flake seen for all screenshots if maxRetriesOnFailure > 0", async () => {
119
+ let retryNum = 0;
120
+ const results = await (0, parallel_tests_handler_1.runAllTestsInParallel)({
121
+ testsToRun: [testCase(0)],
122
+ parallelTasks: 2,
123
+ maxRetriesOnFailure: 10,
124
+ executeTest: async (testCase, isRetry) => {
125
+ if (!isRetry) {
126
+ return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.diff)(1)], testCase);
127
+ }
128
+ if (retryNum === 3) {
129
+ return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.missingHead)(0), (0, mock_test_results_1.diff)(1)], testCase);
130
+ }
131
+ retryNum++;
132
+ return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1)], testCase);
133
+ },
134
+ });
135
+ expect(retryNum).toEqual(3);
136
+ expect(results).toEqual([
137
+ (0, mock_test_results_1.testResult)("flake", [
138
+ (0, mock_test_results_1.flake)(0, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.missingHead)()]),
139
+ (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)()]),
140
+ ], testCase(0)),
141
+ ]);
142
+ });
143
+ it("marks test as a failure if hit max number of retries and no flakes detected", async () => {
144
+ const results = await (0, parallel_tests_handler_1.runAllTestsInParallel)({
145
+ testsToRun: [testCase(0)],
146
+ parallelTasks: 2,
147
+ maxRetriesOnFailure: 3,
148
+ executeTest: async (testCase, isRetry) => {
149
+ if (!isRetry) {
150
+ return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.diff)(1)], testCase);
151
+ }
152
+ return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1)], testCase);
153
+ },
154
+ });
155
+ expect(results).toEqual([
156
+ (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)(), (0, mock_test_results_1.diff)()])], testCase(0)),
157
+ ]);
158
+ });
159
+ });
160
+ const testCase = (num) => ({
161
+ sessionId: "mock-session-id",
162
+ title: `${num}`,
163
+ baseReplayId: "mock-base-replay-id",
164
+ });
165
+ const expectPromiseToNotHaveResolved = async (promise) => {
166
+ let resolved = false;
167
+ promise.then(() => {
168
+ resolved = true;
169
+ }, () => {
170
+ resolved = true;
171
+ });
172
+ await waitForPromisesToFlush();
173
+ expect(resolved).toEqual(false);
174
+ };
175
+ const waitForPromisesToFlush = () => new Promise((resolve) => setTimeout(resolve, 0));
@@ -0,0 +1,3 @@
1
+ import { DetailedTestCaseResult } from "../config/config.types";
2
+ import { InitMessage } from "./messages.types";
3
+ export declare const executeTestInChildProcess: (initMessage: InitMessage) => Promise<DetailedTestCaseResult>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.executeTestInChildProcess = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = require("path");
9
+ const common_1 = require("@alwaysmeticulous/common");
10
+ const loglevel_1 = __importDefault(require("loglevel"));
11
+ const executeTestInChildProcess = (initMessage) => {
12
+ const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
13
+ const taskHandler = (0, path_1.join)(__dirname, "task.handler.js");
14
+ const deferredResult = (0, common_1.defer)();
15
+ const child = (0, child_process_1.fork)(taskHandler, [], { stdio: "inherit" });
16
+ const messageHandler = (message) => {
17
+ if (message &&
18
+ typeof message === "object" &&
19
+ message["kind"] === "result") {
20
+ const resultMessage = message;
21
+ deferredResult.resolve(resultMessage.data.result);
22
+ child.off("message", messageHandler);
23
+ }
24
+ };
25
+ child.on("error", (error) => {
26
+ if (deferredResult.getState() === "pending") {
27
+ deferredResult.reject(error);
28
+ }
29
+ });
30
+ child.on("exit", (code) => {
31
+ if (code) {
32
+ logger.debug(`child exited with code: ${code}`);
33
+ }
34
+ if (deferredResult.getState() === "pending") {
35
+ deferredResult.reject(new Error("No result"));
36
+ }
37
+ });
38
+ child.on("message", messageHandler);
39
+ // Send test case and arguments to child process
40
+ child.send(initMessage);
41
+ return deferredResult.promise;
42
+ };
43
+ exports.executeTestInChildProcess = executeTestInChildProcess;
@@ -1,24 +1,13 @@
1
1
  import { TestCase } from "@alwaysmeticulous/api";
2
- import { ReplayEventsDependencies, ReplayExecutionOptions } from "@alwaysmeticulous/common";
3
- import { TestRun } from "../api/test-run.api";
4
- import { ScreenshotAssertionsEnabledOptions } from "../command-utils/common-types";
5
- import { DetailedTestCaseResult, MeticulousCliConfig } from "../config/config.types";
2
+ import { DetailedTestCaseResult } from "../config/config.types";
6
3
  import { TestRunProgress } from "./run-all-tests.types";
7
4
  export interface RunAllTestsInParallelOptions {
8
- config: MeticulousCliConfig;
9
- testRun: TestRun;
10
5
  testsToRun: TestCase[];
11
- executionOptions: ReplayExecutionOptions;
12
- screenshottingOptions: ScreenshotAssertionsEnabledOptions;
13
- apiToken: string | null;
14
- commitSha: string;
15
- appUrl: string | null;
16
- useAssetsSnapshottedInBaseSimulation: boolean;
17
6
  parallelTasks: number | null;
18
- deflake: boolean;
19
- replayEventsDependencies: ReplayEventsDependencies;
20
7
  maxRetriesOnFailure: number;
8
+ executeTest: (testCase: TestCase, isRetry: boolean) => Promise<DetailedTestCaseResult>;
21
9
  onTestFinished?: (progress: TestRunProgress, resultsSoFar: DetailedTestCaseResult[]) => Promise<void>;
10
+ onTestFailedToRun?: (progress: TestRunProgress) => Promise<void>;
22
11
  }
23
12
  /** Handler for running Meticulous tests in parallel using child processes */
24
13
  export declare const runAllTestsInParallel: (options: RunAllTestsInParallelOptions) => Promise<DetailedTestCaseResult[]>;