@alwaysmeticulous/cli 2.38.0 → 2.39.0
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.
- package/dist/commands/bootstrap/bootstrap.command.js +1 -1
- package/dist/commands/run-all-tests/run-all-tests.command.d.ts +5 -5
- package/dist/commands/run-all-tests/run-all-tests.command.js +7 -7
- package/dist/parallel-tests/__tests__/merge-test-results.spec.js +4 -2
- package/dist/parallel-tests/__tests__/parrallel-tests.handler.spec.js +30 -0
- package/dist/parallel-tests/merge-test-results.d.ts +3 -1
- package/dist/parallel-tests/merge-test-results.js +20 -8
- package/dist/parallel-tests/messages.types.d.ts +2 -2
- package/dist/parallel-tests/parallel-replay.handler.d.ts +3 -0
- package/dist/{deflake-tests/deflake-tests.handler.js → parallel-tests/parallel-replay.handler.js} +2 -21
- package/dist/{deflake-tests/deflake-tests.handler.d.ts → parallel-tests/parallel-replay.types.d.ts} +1 -7
- package/dist/parallel-tests/parallel-replay.types.js +2 -0
- package/dist/parallel-tests/parallel-tests.handler.d.ts +1 -0
- package/dist/parallel-tests/parallel-tests.handler.js +30 -7
- package/dist/parallel-tests/run-all-tests.d.ts +11 -2
- package/dist/parallel-tests/run-all-tests.js +3 -7
- package/dist/parallel-tests/task.handler.js +2 -2
- package/package.json +4 -4
|
@@ -22,7 +22,7 @@ const handler = async () => {
|
|
|
22
22
|
logger.info(`Setting up ${chalk_1.default.green("test:meticulous")} script...`);
|
|
23
23
|
await (0, npm_set_script_utils_1.npmSetScript)({
|
|
24
24
|
script: "test:meticulous",
|
|
25
|
-
command: "meticulous run-all-tests --headless --parallelize
|
|
25
|
+
command: "meticulous run-all-tests --headless --parallelize",
|
|
26
26
|
});
|
|
27
27
|
};
|
|
28
28
|
exports.bootstrapCommand = (0, command_builder_1.buildCommand)("bootstrap")
|
|
@@ -97,16 +97,16 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
97
97
|
readonly description: "Number of tasks to run in parallel (defaults to two per CPU)";
|
|
98
98
|
readonly coerce: (value: number | null | undefined) => number | null | undefined;
|
|
99
99
|
};
|
|
100
|
-
readonly deflake: {
|
|
101
|
-
readonly boolean: true;
|
|
102
|
-
readonly description: "Attempt to deflake failing tests";
|
|
103
|
-
readonly default: false;
|
|
104
|
-
};
|
|
105
100
|
readonly maxRetriesOnFailure: {
|
|
106
101
|
readonly number: true;
|
|
107
102
|
readonly description: "If set to a value greater than 0 then will re-run any replays that give a screenshot diff and mark them as a flake if the screenshot generated on one of the retryed replays differs from that in the first replay.";
|
|
108
103
|
readonly default: 0;
|
|
109
104
|
};
|
|
105
|
+
readonly rerunTestsNTimes: {
|
|
106
|
+
readonly number: true;
|
|
107
|
+
readonly description: "If set to a value greater than 0 then will re-run all replays the specified number of times and mark them as a flake if the screenshot generated on one of the retryed replays differs from that in the first replay.";
|
|
108
|
+
readonly default: 0;
|
|
109
|
+
};
|
|
110
110
|
readonly useCache: {
|
|
111
111
|
readonly boolean: true;
|
|
112
112
|
readonly description: "Use result cache";
|
|
@@ -7,7 +7,7 @@ const command_builder_1 = require("../../command-utils/command-builder");
|
|
|
7
7
|
const common_options_1 = require("../../command-utils/common-options");
|
|
8
8
|
const run_all_tests_1 = require("../../parallel-tests/run-all-tests");
|
|
9
9
|
const commit_sha_utils_1 = require("../../utils/commit-sha.utils");
|
|
10
|
-
const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_,
|
|
10
|
+
const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_, maxRetriesOnFailure, rerunTestsNTimes, useCache, testsFile, disableRemoteFonts, noSandbox, skipPauses, moveBeforeClick, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, baseTestRunId, }) => {
|
|
11
11
|
const executionOptions = {
|
|
12
12
|
headless,
|
|
13
13
|
devTools,
|
|
@@ -47,8 +47,8 @@ const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl,
|
|
|
47
47
|
baseTestRunId: baseTestRunId !== null && baseTestRunId !== void 0 ? baseTestRunId : null,
|
|
48
48
|
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
49
49
|
parallelTasks: parrelelTasks !== null && parrelelTasks !== void 0 ? parrelelTasks : null,
|
|
50
|
-
deflake,
|
|
51
50
|
maxRetriesOnFailure,
|
|
51
|
+
rerunTestsNTimes,
|
|
52
52
|
cachedTestRunResults,
|
|
53
53
|
githubSummary,
|
|
54
54
|
});
|
|
@@ -89,16 +89,16 @@ exports.runAllTestsCommand = (0, command_builder_1.buildCommand)("run-all-tests"
|
|
|
89
89
|
return value;
|
|
90
90
|
},
|
|
91
91
|
},
|
|
92
|
-
deflake: {
|
|
93
|
-
boolean: true,
|
|
94
|
-
description: "Attempt to deflake failing tests",
|
|
95
|
-
default: false,
|
|
96
|
-
},
|
|
97
92
|
maxRetriesOnFailure: {
|
|
98
93
|
number: true,
|
|
99
94
|
description: "If set to a value greater than 0 then will re-run any replays that give a screenshot diff and mark them as a flake if the screenshot generated on one of the retryed replays differs from that in the first replay.",
|
|
100
95
|
default: 0,
|
|
101
96
|
},
|
|
97
|
+
rerunTestsNTimes: {
|
|
98
|
+
number: true,
|
|
99
|
+
description: "If set to a value greater than 0 then will re-run all replays the specified number of times and mark them as a flake if the screenshot generated on one of the retryed replays differs from that in the first replay.",
|
|
100
|
+
default: 0,
|
|
101
|
+
},
|
|
102
102
|
useCache: {
|
|
103
103
|
boolean: true,
|
|
104
104
|
description: "Use result cache",
|
|
@@ -12,14 +12,16 @@ describe("mergeResults", () => {
|
|
|
12
12
|
});
|
|
13
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)]));
|
|
14
14
|
});
|
|
15
|
-
it("
|
|
15
|
+
it("doesn't ignore diffs to screenshots which originally passed", () => {
|
|
16
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
17
|
const comparisonToHeadReplay = (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.diff)(1)]);
|
|
18
18
|
const mergedResult = (0, merge_test_results_1.mergeResults)({
|
|
19
19
|
currentResult,
|
|
20
20
|
comparisonToHeadReplay,
|
|
21
21
|
});
|
|
22
|
-
expect(mergedResult).toEqual(
|
|
22
|
+
expect(mergedResult).toEqual(
|
|
23
|
+
// All screenshots are either "no-diff" or "flake" thus the overall result is "flake"
|
|
24
|
+
(0, mock_test_results_1.testResult)("flake", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.noDiff)(), [(0, mock_test_results_1.diff)()])]));
|
|
23
25
|
});
|
|
24
26
|
it("marks screenshots as flakes if the screenshot comparison originally failed, but the second retry gives a different screenshot again", () => {
|
|
25
27
|
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)]);
|
|
@@ -17,6 +17,7 @@ describe("runAllTestsInParallel", () => {
|
|
|
17
17
|
testsToRun: [testCase(0), testCase(1), testCase(2), testCase(3)],
|
|
18
18
|
parallelTasks: 2,
|
|
19
19
|
maxRetriesOnFailure: 0,
|
|
20
|
+
rerunTestsNTimes: 0,
|
|
20
21
|
executeTest: async (testCase) => {
|
|
21
22
|
var _a;
|
|
22
23
|
testsStarted++;
|
|
@@ -77,6 +78,7 @@ describe("runAllTestsInParallel", () => {
|
|
|
77
78
|
testsToRun: [testCase(0), testCase(1), testCase(2), testCase(3)],
|
|
78
79
|
parallelTasks: 2,
|
|
79
80
|
maxRetriesOnFailure: 0,
|
|
81
|
+
rerunTestsNTimes: 0,
|
|
80
82
|
executeTest: async (testCase) => {
|
|
81
83
|
var _a;
|
|
82
84
|
await testRunPromises[parseInt((_a = testCase.title) !== null && _a !== void 0 ? _a : "")].promise;
|
|
@@ -109,6 +111,7 @@ describe("runAllTestsInParallel", () => {
|
|
|
109
111
|
testsToRun: [testCase(0)],
|
|
110
112
|
parallelTasks: 2,
|
|
111
113
|
maxRetriesOnFailure: 0,
|
|
114
|
+
rerunTestsNTimes: 0,
|
|
112
115
|
executeTest: async (testCase) => {
|
|
113
116
|
return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0)], testCase);
|
|
114
117
|
},
|
|
@@ -121,6 +124,7 @@ describe("runAllTestsInParallel", () => {
|
|
|
121
124
|
testsToRun: [testCase(0)],
|
|
122
125
|
parallelTasks: 2,
|
|
123
126
|
maxRetriesOnFailure: 10,
|
|
127
|
+
rerunTestsNTimes: 0,
|
|
124
128
|
executeTest: async (testCase, isRetry) => {
|
|
125
129
|
if (!isRetry) {
|
|
126
130
|
return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.diff)(1)], testCase);
|
|
@@ -145,6 +149,7 @@ describe("runAllTestsInParallel", () => {
|
|
|
145
149
|
testsToRun: [testCase(0)],
|
|
146
150
|
parallelTasks: 2,
|
|
147
151
|
maxRetriesOnFailure: 3,
|
|
152
|
+
rerunTestsNTimes: 0,
|
|
148
153
|
executeTest: async (testCase, isRetry) => {
|
|
149
154
|
if (!isRetry) {
|
|
150
155
|
return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.diff)(1)], testCase);
|
|
@@ -156,6 +161,31 @@ describe("runAllTestsInParallel", () => {
|
|
|
156
161
|
(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
162
|
]);
|
|
158
163
|
});
|
|
164
|
+
it("retries N times if rerunTestsNTimes > 0", async () => {
|
|
165
|
+
let rerunNum = 0;
|
|
166
|
+
const results = await (0, parallel_tests_handler_1.runAllTestsInParallel)({
|
|
167
|
+
testsToRun: [testCase(0)],
|
|
168
|
+
parallelTasks: 2,
|
|
169
|
+
maxRetriesOnFailure: 0,
|
|
170
|
+
rerunTestsNTimes: 3,
|
|
171
|
+
executeTest: async (testCase, isRetry) => {
|
|
172
|
+
if (!isRetry) {
|
|
173
|
+
return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.diff)(0), (0, mock_test_results_1.noDiff)(1)], testCase);
|
|
174
|
+
}
|
|
175
|
+
if (rerunNum === 2) {
|
|
176
|
+
return (0, mock_test_results_1.testResult)("fail", [(0, mock_test_results_1.missingHead)(0), (0, mock_test_results_1.diff)(1)], testCase);
|
|
177
|
+
}
|
|
178
|
+
rerunNum++;
|
|
179
|
+
return (0, mock_test_results_1.testResult)("pass", [(0, mock_test_results_1.noDiff)(0), (0, mock_test_results_1.noDiff)(1)], testCase);
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
expect(rerunNum).toEqual(2);
|
|
183
|
+
expect(results).toEqual([
|
|
184
|
+
(0, mock_test_results_1.testResult)(
|
|
185
|
+
// Flake outcome is returned iff all screenshots are flaky.
|
|
186
|
+
"flake", [(0, mock_test_results_1.flake)(0, (0, mock_test_results_1.diff)(), [(0, mock_test_results_1.missingHead)()]), (0, mock_test_results_1.flake)(1, (0, mock_test_results_1.noDiff)(), [(0, mock_test_results_1.diff)()])], testCase(0)),
|
|
187
|
+
]);
|
|
188
|
+
});
|
|
159
189
|
});
|
|
160
190
|
const testCase = (num) => ({
|
|
161
191
|
sessionId: "mock-session-id",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
2
|
+
import { DetailedTestCaseResult, TestCaseResult } from "../config/config.types";
|
|
2
3
|
export interface ResultsToMerge {
|
|
3
4
|
currentResult: DetailedTestCaseResult;
|
|
4
5
|
/**
|
|
@@ -8,3 +9,4 @@ export interface ResultsToMerge {
|
|
|
8
9
|
comparisonToHeadReplay: DetailedTestCaseResult;
|
|
9
10
|
}
|
|
10
11
|
export declare const mergeResults: ({ currentResult, comparisonToHeadReplay, }: ResultsToMerge) => DetailedTestCaseResult;
|
|
12
|
+
export declare const testRunOutcomeFromDiffResults: (currentResult: TestCaseResult["result"], newScreenshotDiffResults: ScreenshotDiffResult[]) => TestCaseResult["result"];
|
|
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.mergeResults = void 0;
|
|
6
|
+
exports.testRunOutcomeFromDiffResults = exports.mergeResults = void 0;
|
|
7
7
|
const utils_1 = require("@sentry/utils");
|
|
8
8
|
const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify"));
|
|
9
|
+
const has_notable_differences_1 = require("../commands/screenshot-diff/utils/has-notable-differences");
|
|
9
10
|
const screenshot_diff_results_utils_1 = require("./screenshot-diff-results.utils");
|
|
10
11
|
const mergeResults = ({ currentResult, comparisonToHeadReplay, }) => {
|
|
11
12
|
// If any of the screenshots diffs in comparisonToHeadReplay show a diff against one
|
|
@@ -20,9 +21,6 @@ const mergeResults = ({ currentResult, comparisonToHeadReplay, }) => {
|
|
|
20
21
|
retryDiffById.set(hash, result);
|
|
21
22
|
});
|
|
22
23
|
const newScreenshotDiffResults = (0, screenshot_diff_results_utils_1.flattenScreenshotDiffResults)(currentResult).map((currentDiff) => {
|
|
23
|
-
if (currentDiff.outcome === "no-diff") {
|
|
24
|
-
return currentDiff;
|
|
25
|
-
}
|
|
26
24
|
const hash = hashScreenshotIdentifier(currentDiff.identifier);
|
|
27
25
|
const diffWhenRetrying = retryDiffById.get(hash);
|
|
28
26
|
// diffWhenRetrying is null in the case that there is a base screenshot for the base replay,
|
|
@@ -75,12 +73,9 @@ const mergeResults = ({ currentResult, comparisonToHeadReplay, }) => {
|
|
|
75
73
|
};
|
|
76
74
|
}
|
|
77
75
|
});
|
|
78
|
-
const noLongerHasFailures = newScreenshotDiffResults.every(({ outcome }) => outcome === "flake" || outcome === "no-diff");
|
|
79
76
|
return {
|
|
80
77
|
...currentResult,
|
|
81
|
-
result: currentResult.result
|
|
82
|
-
? "flake"
|
|
83
|
-
: currentResult.result,
|
|
78
|
+
result: (0, exports.testRunOutcomeFromDiffResults)(currentResult.result, newScreenshotDiffResults),
|
|
84
79
|
screenshotDiffResultsByBaseReplayId: (0, screenshot_diff_results_utils_1.groupScreenshotDiffResults)(newScreenshotDiffResults),
|
|
85
80
|
};
|
|
86
81
|
};
|
|
@@ -108,3 +103,20 @@ const hashScreenshotIdentifier = (identifier) => {
|
|
|
108
103
|
const unknownScreenshotIdentifierType = (identifier) => {
|
|
109
104
|
utils_1.logger.error(`Unknown type of screenshot identifier: ${JSON.stringify(identifier)}`);
|
|
110
105
|
};
|
|
106
|
+
const testRunOutcomeFromDiffResults = (currentResult, newScreenshotDiffResults) => {
|
|
107
|
+
// If a test run is already flakey, we don't want to overwrite that with a 'fail' result.
|
|
108
|
+
if (currentResult === "flake") {
|
|
109
|
+
return "flake";
|
|
110
|
+
}
|
|
111
|
+
const hasOnlyFlakes = newScreenshotDiffResults.every(({ outcome }) => outcome === "flake" || outcome === "no-diff");
|
|
112
|
+
if (hasOnlyFlakes) {
|
|
113
|
+
return "flake";
|
|
114
|
+
}
|
|
115
|
+
// If we've had a test run has already failed, we don't want to overwrite that with a 'pass' result.
|
|
116
|
+
// Otherwise, if there are any notable differences, we want to fail the test run.
|
|
117
|
+
return currentResult === "fail" ||
|
|
118
|
+
(0, has_notable_differences_1.hasNotableDifferences)(newScreenshotDiffResults)
|
|
119
|
+
? "fail"
|
|
120
|
+
: "pass";
|
|
121
|
+
};
|
|
122
|
+
exports.testRunOutcomeFromDiffResults = testRunOutcomeFromDiffResults;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import log from "loglevel";
|
|
2
2
|
import { DetailedTestCaseResult } from "../config/config.types";
|
|
3
|
-
import {
|
|
3
|
+
import { ParallelTestsReplayOptions } from "./parallel-replay.types";
|
|
4
4
|
export interface InitMessage {
|
|
5
5
|
kind: "init";
|
|
6
6
|
data: {
|
|
7
7
|
logLevel: log.LogLevel[keyof log.LogLevel];
|
|
8
8
|
dataDir: string;
|
|
9
|
-
replayOptions:
|
|
9
|
+
replayOptions: ParallelTestsReplayOptions;
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
export interface ResultMessage {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DetailedTestCaseResult } from "../config/config.types";
|
|
2
|
+
import { ParallelTestsReplayOptions } from "./parallel-replay.types";
|
|
3
|
+
export declare const handleReplay: ({ testCase, replayTarget, executionOptions, screenshottingOptions, apiToken, commitSha, generatedBy, testRunId, replayEventsDependencies, suppressScreenshotDiffLogging, baseTestRunId, }: ParallelTestsReplayOptions) => Promise<DetailedTestCaseResult>;
|
package/dist/{deflake-tests/deflake-tests.handler.js → parallel-tests/parallel-replay.handler.js}
RENAMED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
3
|
+
exports.handleReplay = void 0;
|
|
9
4
|
const replay_command_1 = require("../commands/replay/replay.command");
|
|
10
5
|
const has_notable_differences_1 = require("../commands/screenshot-diff/utils/has-notable-differences");
|
|
11
6
|
const handleReplay = async ({ testCase, replayTarget, executionOptions, screenshottingOptions, apiToken, commitSha, generatedBy, testRunId, replayEventsDependencies, suppressScreenshotDiffLogging, baseTestRunId, }) => {
|
|
@@ -35,21 +30,7 @@ const handleReplay = async ({ testCase, replayTarget, executionOptions, screensh
|
|
|
35
30
|
screenshotDiffResultsByBaseReplayId,
|
|
36
31
|
};
|
|
37
32
|
};
|
|
38
|
-
|
|
39
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
40
|
-
const firstResult = await handleReplay(otherOptions);
|
|
41
|
-
if (firstResult.result === "pass" || !deflake) {
|
|
42
|
-
return firstResult;
|
|
43
|
-
}
|
|
44
|
-
const secondResult = await handleReplay(otherOptions);
|
|
45
|
-
if (secondResult.result === "fail") {
|
|
46
|
-
return secondResult;
|
|
47
|
-
}
|
|
48
|
-
const thirdResult = await handleReplay(otherOptions);
|
|
49
|
-
logger.info(`FLAKE: ${thirdResult.title} => ${thirdResult.result}`);
|
|
50
|
-
return thirdResult;
|
|
51
|
-
};
|
|
52
|
-
exports.deflakeReplayCommandHandler = deflakeReplayCommandHandler;
|
|
33
|
+
exports.handleReplay = handleReplay;
|
|
53
34
|
const applyTestCaseExecutionOptionOverrides = (executionOptionsFromCliFlags, overridesFromTestCase) => {
|
|
54
35
|
var _a;
|
|
55
36
|
// Options specified in the test case override those passed as CLI flags
|
package/dist/{deflake-tests/deflake-tests.handler.d.ts → parallel-tests/parallel-replay.types.d.ts}
RENAMED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { TestCase } from "@alwaysmeticulous/api";
|
|
2
2
|
import { GeneratedBy, ReplayEventsDependencies, ReplayExecutionOptions, ReplayTarget } from "@alwaysmeticulous/common";
|
|
3
3
|
import { ScreenshotAssertionsEnabledOptions } from "../command-utils/common-types";
|
|
4
|
-
|
|
5
|
-
export interface DeflakeReplayCommandHandlerOptions extends HandleReplayOptions {
|
|
6
|
-
deflake: boolean;
|
|
7
|
-
}
|
|
8
|
-
interface HandleReplayOptions {
|
|
4
|
+
export interface ParallelTestsReplayOptions {
|
|
9
5
|
replayTarget: ReplayTarget;
|
|
10
6
|
executionOptions: ReplayExecutionOptions;
|
|
11
7
|
screenshottingOptions: ScreenshotAssertionsEnabledOptions;
|
|
@@ -18,5 +14,3 @@ interface HandleReplayOptions {
|
|
|
18
14
|
replayEventsDependencies: ReplayEventsDependencies;
|
|
19
15
|
suppressScreenshotDiffLogging: boolean;
|
|
20
16
|
}
|
|
21
|
-
export declare const deflakeReplayCommandHandler: ({ deflake, ...otherOptions }: DeflakeReplayCommandHandlerOptions) => Promise<DetailedTestCaseResult>;
|
|
22
|
-
export {};
|
|
@@ -5,6 +5,7 @@ export interface RunAllTestsInParallelOptions {
|
|
|
5
5
|
testsToRun: TestCase[];
|
|
6
6
|
parallelTasks: number | null;
|
|
7
7
|
maxRetriesOnFailure: number;
|
|
8
|
+
rerunTestsNTimes: number;
|
|
8
9
|
executeTest: (testCase: TestCase, isRetry: boolean) => Promise<DetailedTestCaseResult>;
|
|
9
10
|
onTestFinished?: (progress: TestRunProgress, resultsSoFar: DetailedTestCaseResult[]) => Promise<void>;
|
|
10
11
|
onTestFailedToRun?: (progress: TestRunProgress) => Promise<void>;
|
|
@@ -10,7 +10,10 @@ const loglevel_1 = __importDefault(require("loglevel"));
|
|
|
10
10
|
const merge_test_results_1 = require("./merge-test-results");
|
|
11
11
|
const screenshot_diff_results_utils_1 = require("./screenshot-diff-results.utils");
|
|
12
12
|
/** Handler for running Meticulous tests in parallel using child processes */
|
|
13
|
-
const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFailure, executeTest, onTestFinished, onTestFailedToRun, }) => {
|
|
13
|
+
const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFailure, rerunTestsNTimes, executeTest, onTestFinished, onTestFailedToRun, }) => {
|
|
14
|
+
if (maxRetriesOnFailure && rerunTestsNTimes) {
|
|
15
|
+
throw new Error("maxRetriesOnFailure and rerunTestsNTimes are mutually exclusive.");
|
|
16
|
+
}
|
|
14
17
|
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
15
18
|
let nextId = 0;
|
|
16
19
|
let queue = testsToRun.map((test) => ({
|
|
@@ -47,8 +50,16 @@ const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFa
|
|
|
47
50
|
.then(async (result) => {
|
|
48
51
|
var _a;
|
|
49
52
|
const resultsForTestCase = resultsByTestId.get(id);
|
|
53
|
+
// Re-run the test if needed, comparing to the base replay.
|
|
54
|
+
if (!isRetry) {
|
|
55
|
+
queue.push(...Array.from(new Array(rerunTestsNTimes)).map(() => ({
|
|
56
|
+
...testCase,
|
|
57
|
+
id,
|
|
58
|
+
baseReplayId: result === null || result === void 0 ? void 0 : result.headReplayId,
|
|
59
|
+
})));
|
|
60
|
+
}
|
|
50
61
|
if (resultsForTestCase != null && result != null) {
|
|
51
|
-
logRetrySummary(testName({ id, ...testCase }), result);
|
|
62
|
+
logRetrySummary(testName({ id, ...testCase }), result, resultsForTestCase.currentResult);
|
|
52
63
|
}
|
|
53
64
|
if (resultsForTestCase != null &&
|
|
54
65
|
resultsForTestCase.currentResult.result === "flake") {
|
|
@@ -81,8 +92,15 @@ const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFa
|
|
|
81
92
|
// Our work is done for this test case if the first result was a pass,
|
|
82
93
|
// we've performed all the retries, or one of the retries already proved
|
|
83
94
|
// the result as flakey
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
let isFinalResult;
|
|
96
|
+
if (rerunTestsNTimes > 0) {
|
|
97
|
+
isFinalResult = numberOfRetriesExecuted >= rerunTestsNTimes;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
isFinalResult =
|
|
101
|
+
mergedResult.result !== "fail" ||
|
|
102
|
+
numberOfRetriesExecuted >= maxRetriesOnFailure;
|
|
103
|
+
}
|
|
86
104
|
if (isFinalResult) {
|
|
87
105
|
// Cancel any replays that are still scheduled
|
|
88
106
|
queue = queue.filter((otherReplay) => otherReplay.id !== id);
|
|
@@ -119,7 +137,12 @@ const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFa
|
|
|
119
137
|
}
|
|
120
138
|
++inProgress;
|
|
121
139
|
if (resultsByTestId.has(testCase.id)) {
|
|
122
|
-
|
|
140
|
+
if (maxRetriesOnFailure > 0) {
|
|
141
|
+
logger.info(`Test ${testName(testCase)} failed. Retrying to check for flakes...`);
|
|
142
|
+
}
|
|
143
|
+
else if (rerunTestsNTimes > 0) {
|
|
144
|
+
logger.info(`Re-running ${testName(testCase)} to check for flakes...`);
|
|
145
|
+
}
|
|
123
146
|
}
|
|
124
147
|
startTask(testCase);
|
|
125
148
|
process.nextTick(checkNextTask);
|
|
@@ -129,10 +152,10 @@ const runAllTestsInParallel = async ({ testsToRun, parallelTasks, maxRetriesOnFa
|
|
|
129
152
|
return finalResults;
|
|
130
153
|
};
|
|
131
154
|
exports.runAllTestsInParallel = runAllTestsInParallel;
|
|
132
|
-
const logRetrySummary = (nameOfTest, retryResult) => {
|
|
155
|
+
const logRetrySummary = (nameOfTest, retryResult, resultSoFar) => {
|
|
133
156
|
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
134
157
|
if (retryResult.result === "pass") {
|
|
135
|
-
logger.info(`Retried taking screenshots for failed test ${nameOfTest}, but got the same results`);
|
|
158
|
+
logger.info(`Retried taking screenshots for${resultSoFar.result == "fail" ? " failed" : ""} test ${nameOfTest}, but got the same results`);
|
|
136
159
|
}
|
|
137
160
|
else {
|
|
138
161
|
const numDifferingScreenshots = (0, screenshot_diff_results_utils_1.flattenScreenshotDiffResults)(retryResult).filter((result) => result.outcome !== "no-diff").length;
|
|
@@ -25,13 +25,22 @@ export interface Options {
|
|
|
25
25
|
* Set to 1 to disable parralelism.
|
|
26
26
|
*/
|
|
27
27
|
parallelTasks: number | null;
|
|
28
|
-
deflake: boolean;
|
|
29
28
|
/**
|
|
30
29
|
* If set to a value greater than 1 then will re-run any replays that give a screenshot diff
|
|
31
30
|
* and mark them as a flake if the screenshot generated on one of the retryed replays differs from that
|
|
32
31
|
* in the first replay.
|
|
33
32
|
*/
|
|
34
33
|
maxRetriesOnFailure: number;
|
|
34
|
+
/**
|
|
35
|
+
* If set to a value greater than 0 then will re-run all replays the specified number of times
|
|
36
|
+
* and mark them as a flake if the screenshot generated on one of the retryed replays differs from that
|
|
37
|
+
* in the first replay.
|
|
38
|
+
*
|
|
39
|
+
* This is useful for checking flake rates.
|
|
40
|
+
*
|
|
41
|
+
* This option is mutually exclusive with maxRetriesOnFailure.
|
|
42
|
+
*/
|
|
43
|
+
rerunTestsNTimes: number;
|
|
35
44
|
githubSummary: boolean;
|
|
36
45
|
/**
|
|
37
46
|
* If provided it will incorportate the cachedTestRunResults in any calls to store
|
|
@@ -61,4 +70,4 @@ export interface RunAllTestsResult {
|
|
|
61
70
|
* Runs all the test cases in the provided file.
|
|
62
71
|
* @returns The results of the tests that were executed (note that this does not include results from any cachedTestRunResults passed in)
|
|
63
72
|
*/
|
|
64
|
-
export declare const runAllTests: ({ testsFile, apiToken, commitSha, baseCommitSha, appUrl, executionOptions, screenshottingOptions, parallelTasks,
|
|
73
|
+
export declare const runAllTests: ({ testsFile, apiToken, commitSha, baseCommitSha, appUrl, executionOptions, screenshottingOptions, parallelTasks, maxRetriesOnFailure, rerunTestsNTimes, cachedTestRunResults: cachedTestRunResults_, githubSummary, environment, baseTestRunId, onTestRunCreated, onTestFinished: onTestFinished_, }: Options) => Promise<RunAllTestsResult>;
|
|
@@ -12,21 +12,18 @@ const replay_diff_api_1 = require("../api/replay-diff.api");
|
|
|
12
12
|
const test_run_api_1 = require("../api/test-run.api");
|
|
13
13
|
const config_1 = require("../config/config");
|
|
14
14
|
const replay_assets_1 = require("../local-data/replay-assets");
|
|
15
|
-
const parallel_tests_handler_1 = require("../parallel-tests/parallel-tests.handler");
|
|
16
15
|
const config_utils_1 = require("../utils/config.utils");
|
|
17
16
|
const github_summary_utils_1 = require("../utils/github-summary.utils");
|
|
18
17
|
const run_all_tests_utils_1 = require("../utils/run-all-tests.utils");
|
|
19
18
|
const test_run_environment_utils_1 = require("../utils/test-run-environment.utils");
|
|
20
19
|
const version_utils_1 = require("../utils/version.utils");
|
|
21
20
|
const execute_test_in_child_process_1 = require("./execute-test-in-child-process");
|
|
21
|
+
const parallel_tests_handler_1 = require("./parallel-tests.handler");
|
|
22
22
|
/**
|
|
23
23
|
* Runs all the test cases in the provided file.
|
|
24
24
|
* @returns The results of the tests that were executed (note that this does not include results from any cachedTestRunResults passed in)
|
|
25
25
|
*/
|
|
26
|
-
const runAllTests = async ({ testsFile, apiToken, commitSha, baseCommitSha, appUrl, executionOptions, screenshottingOptions, parallelTasks,
|
|
27
|
-
if (deflake && maxRetriesOnFailure > 1) {
|
|
28
|
-
throw new Error("Arguments deflake and maxRetriesOnFailure are mutually exclusive");
|
|
29
|
-
}
|
|
26
|
+
const runAllTests = async ({ testsFile, apiToken, commitSha, baseCommitSha, appUrl, executionOptions, screenshottingOptions, parallelTasks, maxRetriesOnFailure, rerunTestsNTimes, cachedTestRunResults: cachedTestRunResults_, githubSummary, environment, baseTestRunId, onTestRunCreated, onTestFinished: onTestFinished_, }) => {
|
|
30
27
|
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
31
28
|
const client = (0, client_1.createClient)({ apiToken });
|
|
32
29
|
const project = await (0, project_api_1.getProject)(client);
|
|
@@ -60,7 +57,6 @@ const runAllTests = async ({ testsFile, apiToken, commitSha, baseCommitSha, appU
|
|
|
60
57
|
baseCommitSha,
|
|
61
58
|
appUrl,
|
|
62
59
|
parallelTasks,
|
|
63
|
-
deflake,
|
|
64
60
|
githubSummary,
|
|
65
61
|
},
|
|
66
62
|
environment: (0, test_run_environment_utils_1.getEnvironment)(environment),
|
|
@@ -145,6 +141,7 @@ const runAllTests = async ({ testsFile, apiToken, commitSha, baseCommitSha, appU
|
|
|
145
141
|
testsToRun,
|
|
146
142
|
parallelTasks,
|
|
147
143
|
maxRetriesOnFailure,
|
|
144
|
+
rerunTestsNTimes,
|
|
148
145
|
executeTest: (testCase, isRetry) => {
|
|
149
146
|
var _a;
|
|
150
147
|
const initMessage = {
|
|
@@ -156,7 +153,6 @@ const runAllTests = async ({ testsFile, apiToken, commitSha, baseCommitSha, appU
|
|
|
156
153
|
apiToken,
|
|
157
154
|
commitSha,
|
|
158
155
|
testCase,
|
|
159
|
-
deflake,
|
|
160
156
|
replayTarget: (0, config_utils_1.getReplayTargetForTestCase)({
|
|
161
157
|
appUrl,
|
|
162
158
|
testCase,
|
|
@@ -30,9 +30,9 @@ const common_1 = require("@alwaysmeticulous/common");
|
|
|
30
30
|
const Sentry = __importStar(require("@sentry/node"));
|
|
31
31
|
const loglevel_1 = __importDefault(require("loglevel"));
|
|
32
32
|
const luxon_1 = require("luxon");
|
|
33
|
-
const deflake_tests_handler_1 = require("../deflake-tests/deflake-tests.handler");
|
|
34
33
|
const logger_utils_1 = require("../utils/logger.utils");
|
|
35
34
|
const sentry_utils_1 = require("../utils/sentry.utils");
|
|
35
|
+
const parallel_replay_handler_1 = require("./parallel-replay.handler");
|
|
36
36
|
const INIT_TIMEOUT = luxon_1.Duration.fromObject({ second: 1 });
|
|
37
37
|
const waitForInitMessage = () => {
|
|
38
38
|
return Promise.race([
|
|
@@ -67,7 +67,7 @@ const main = async () => {
|
|
|
67
67
|
const { logLevel, dataDir, replayOptions } = initMessage.data;
|
|
68
68
|
logger.setLevel(logLevel);
|
|
69
69
|
(0, common_1.setMeticulousLocalDataDir)(dataDir);
|
|
70
|
-
const result = await (0,
|
|
70
|
+
const result = await (0, parallel_replay_handler_1.handleReplay)(replayOptions);
|
|
71
71
|
const resultMessage = {
|
|
72
72
|
kind: "result",
|
|
73
73
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwaysmeticulous/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.39.0",
|
|
4
4
|
"description": "The Meticulous CLI",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"test": "jest"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@alwaysmeticulous/common": "^2.
|
|
29
|
+
"@alwaysmeticulous/common": "^2.39.0",
|
|
30
30
|
"@sentry/node": "^7.36.0",
|
|
31
31
|
"@sentry/tracing": "^7.36.0",
|
|
32
32
|
"adm-zip": "^0.5.9",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"yargs": "^17.5.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@alwaysmeticulous/api": "^2.
|
|
48
|
+
"@alwaysmeticulous/api": "^2.39.0",
|
|
49
49
|
"@types/express": "^4.17.14",
|
|
50
50
|
"@types/proper-lockfile": "^4.1.2"
|
|
51
51
|
},
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"coverageDirectory": "../coverage",
|
|
96
96
|
"testEnvironment": "node"
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "b6488b15a8233fcd1380b1607ec254d5d3bc0378"
|
|
99
99
|
}
|