@alwaysmeticulous/cli 2.36.0 → 2.38.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/api/replay.api.d.ts +0 -1
- package/dist/api/replay.api.js +1 -13
- package/dist/api/test-run.api.d.ts +8 -2
- package/dist/api/test-run.api.js +21 -3
- package/dist/api/types.d.ts +9 -0
- package/dist/command-utils/common-options.d.ts +0 -10
- package/dist/command-utils/common-options.js +0 -6
- package/dist/commands/create-test/create-test.command.d.ts +0 -5
- package/dist/commands/create-test/create-test.command.js +1 -5
- package/dist/commands/replay/replay.command.d.ts +5 -25
- package/dist/commands/replay/replay.command.js +10 -54
- package/dist/commands/replay/utils/compute-diff.d.ts +3 -6
- package/dist/commands/replay/utils/compute-diff.js +83 -18
- package/dist/commands/replay/utils/exit-early-if-skip-upload-env-var-set.d.ts +1 -1
- package/dist/commands/replay/utils/exit-early-if-skip-upload-env-var-set.js +3 -3
- package/dist/commands/run-all-tests/run-all-tests.command.d.ts +4 -10
- package/dist/commands/run-all-tests/run-all-tests.command.js +6 -10
- package/dist/commands/screenshot-diff/screenshot-diff.command.d.ts +6 -8
- package/dist/commands/screenshot-diff/screenshot-diff.command.js +50 -97
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.d.ts +2 -0
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.js +18 -0
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.d.ts +2 -0
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.js +24 -0
- package/dist/commands/screenshot-diff/utils/has-notable-differences.d.ts +2 -0
- package/dist/commands/screenshot-diff/utils/has-notable-differences.js +10 -0
- package/dist/config/config.js +2 -3
- package/dist/config/config.types.d.ts +1 -1
- package/dist/deflake-tests/deflake-tests.handler.d.ts +1 -0
- package/dist/deflake-tests/deflake-tests.handler.js +9 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -3
- package/dist/local-data/replays.d.ts +6 -1
- package/dist/local-data/replays.js +11 -3
- package/dist/local-data/screenshot-diffs.js +1 -1
- package/dist/local-data/serve-assets-from-simulation.js +1 -1
- package/dist/parallel-tests/__tests__/mock-test-results.js +3 -1
- package/dist/parallel-tests/__tests__/parrallel-tests.handler.spec.js +1 -1
- package/dist/parallel-tests/merge-test-results.js +18 -9
- package/dist/parallel-tests/parallel-tests.handler.js +2 -1
- package/dist/parallel-tests/run-all-tests.d.ts +2 -2
- package/dist/parallel-tests/run-all-tests.js +50 -23
- package/dist/parallel-tests/screenshot-diff-results.utils.d.ts +7 -0
- package/dist/parallel-tests/screenshot-diff-results.utils.js +20 -0
- package/dist/utils/config.utils.d.ts +1 -2
- package/dist/utils/config.utils.js +1 -10
- package/dist/utils/run-all-tests.utils.d.ts +0 -1
- package/dist/utils/run-all-tests.utils.js +3 -50
- package/package.json +6 -4
package/dist/api/replay.api.d.ts
CHANGED
|
@@ -34,4 +34,3 @@ export interface ScreenshotDiffStats {
|
|
|
34
34
|
}
|
|
35
35
|
export declare const postScreenshotDiffStats: (client: AxiosInstance, options: ScreenshotDiffStats) => Promise<void>;
|
|
36
36
|
export declare const getReplayUrl: (replay: any) => string;
|
|
37
|
-
export declare const getDiffUrl: (client: AxiosInstance, baseReplayId: string, headReplayId: string) => Promise<string>;
|
package/dist/api/replay.api.js
CHANGED
|
@@ -3,9 +3,8 @@ 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.
|
|
6
|
+
exports.getReplayUrl = exports.postScreenshotDiffStats = exports.getReplayCommandId = exports.putReplayPushedStatus = exports.getReplayDownloadUrl = exports.getReplayPushUrl = exports.createReplay = exports.getReplay = void 0;
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const project_api_1 = require("./project.api");
|
|
9
8
|
const getReplay = async (client, replayId) => {
|
|
10
9
|
const { data } = await client.get(`replays/${replayId}`).catch((error) => {
|
|
11
10
|
var _a;
|
|
@@ -88,14 +87,3 @@ const getReplayUrl = (replay) => {
|
|
|
88
87
|
return replayUrl;
|
|
89
88
|
};
|
|
90
89
|
exports.getReplayUrl = getReplayUrl;
|
|
91
|
-
const getDiffUrl = async (client, baseReplayId, headReplayId) => {
|
|
92
|
-
const project = await (0, project_api_1.getProject)(client);
|
|
93
|
-
if (!project) {
|
|
94
|
-
throw new Error(`Unexpected error: could not retrieve project data`);
|
|
95
|
-
}
|
|
96
|
-
const organizationName = encodeURIComponent(project.organization.name);
|
|
97
|
-
const projectName = encodeURIComponent(project.name);
|
|
98
|
-
const diffUrl = `https://app.meticulous.ai/projects/${organizationName}/${projectName}/simulations/${headReplayId}/diff/${baseReplayId}`;
|
|
99
|
-
return diffUrl;
|
|
100
|
-
};
|
|
101
|
-
exports.getDiffUrl = getDiffUrl;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TestRunConfigData } from "@alwaysmeticulous/api";
|
|
2
2
|
import { AxiosInstance } from "axios";
|
|
3
3
|
import { TestCaseResult } from "../config/config.types";
|
|
4
|
-
import { TestRun, TestRunStatus } from "./types";
|
|
4
|
+
import { ScreenshotLocator, TestRun, TestRunStatus } from "./types";
|
|
5
5
|
export declare const getTestRun: (options: {
|
|
6
6
|
client: AxiosInstance;
|
|
7
7
|
testRunId: string;
|
|
@@ -26,4 +26,10 @@ export interface GetLatestTestRunResultsOptions {
|
|
|
26
26
|
commitSha: string;
|
|
27
27
|
}
|
|
28
28
|
export declare const getCachedTestRunResults: ({ client, commitSha, }: GetLatestTestRunResultsOptions) => Promise<TestCaseResult[]>;
|
|
29
|
-
export declare const
|
|
29
|
+
export declare const getLatestTestRunId: (opts: GetLatestTestRunResultsOptions) => Promise<string | null>;
|
|
30
|
+
export interface GetBaseScreenshotLocatorsOptions {
|
|
31
|
+
client: AxiosInstance;
|
|
32
|
+
testRunId: string;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const getBaseScreenshots: ({ client, testRunId, sessionId, }: GetBaseScreenshotLocatorsOptions) => Promise<ScreenshotLocator[]>;
|
package/dist/api/test-run.api.js
CHANGED
|
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.
|
|
29
|
+
exports.getBaseScreenshots = exports.getLatestTestRunId = exports.getCachedTestRunResults = exports.getTestRunUrl = exports.putTestRunResults = exports.createTestRun = exports.getTestRun = void 0;
|
|
30
30
|
const common_1 = require("@alwaysmeticulous/common");
|
|
31
31
|
const axios_1 = __importStar(require("axios"));
|
|
32
32
|
const loglevel_1 = __importDefault(require("loglevel"));
|
|
@@ -75,7 +75,7 @@ const getCachedTestRunResults = async ({ client, commitSha, }) => {
|
|
|
75
75
|
logger.warn("Test run cache not supported: no commit hash");
|
|
76
76
|
return [];
|
|
77
77
|
}
|
|
78
|
-
const results = (_c = (_b = (_a = (await
|
|
78
|
+
const results = (_c = (_b = (_a = (await getLatestTestRunResults({ client, commitSha }))) === null || _a === void 0 ? void 0 : _a.resultData) === null || _b === void 0 ? void 0 : _b.results) !== null && _c !== void 0 ? _c : [];
|
|
79
79
|
// Only return passing tests
|
|
80
80
|
return results.filter(({ result }) => result === "pass");
|
|
81
81
|
};
|
|
@@ -93,4 +93,22 @@ const getLatestTestRunResults = async ({ client, commitSha, }) => {
|
|
|
93
93
|
});
|
|
94
94
|
return (_a = data) !== null && _a !== void 0 ? _a : null;
|
|
95
95
|
};
|
|
96
|
-
|
|
96
|
+
const getLatestTestRunId = async (opts) => {
|
|
97
|
+
var _a, _b;
|
|
98
|
+
return (_b = (_a = (await getLatestTestRunResults(opts))) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
99
|
+
};
|
|
100
|
+
exports.getLatestTestRunId = getLatestTestRunId;
|
|
101
|
+
const getBaseScreenshots = async ({ client, testRunId, sessionId, }) => {
|
|
102
|
+
var _a;
|
|
103
|
+
const { data } = await client
|
|
104
|
+
.get(`test-runs/${encodeURIComponent(testRunId)}/base-screenshots?sessionId=${encodeURIComponent(sessionId)}`)
|
|
105
|
+
.catch((error) => {
|
|
106
|
+
var _a;
|
|
107
|
+
if (error instanceof axios_1.AxiosError && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
|
|
108
|
+
return { data: null };
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
return (_a = data) !== null && _a !== void 0 ? _a : [];
|
|
113
|
+
};
|
|
114
|
+
exports.getBaseScreenshots = getBaseScreenshots;
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ScreenshotIdentifier } from "@alwaysmeticulous/api";
|
|
1
2
|
import { TestCaseResult } from "../config/config.types";
|
|
2
3
|
interface Organization {
|
|
3
4
|
id: string;
|
|
@@ -27,4 +28,12 @@ export interface TestRun {
|
|
|
27
28
|
};
|
|
28
29
|
[key: string]: any;
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* All the information required to locate & download a screenshot of a replay.
|
|
33
|
+
*/
|
|
34
|
+
export interface ScreenshotLocator {
|
|
35
|
+
replayId: string;
|
|
36
|
+
screenshotIdentifier: ScreenshotIdentifier;
|
|
37
|
+
screenshotUrl: string;
|
|
38
|
+
}
|
|
30
39
|
export {};
|
|
@@ -20,11 +20,6 @@ export declare const OPTIONS: {
|
|
|
20
20
|
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
21
21
|
readonly default: false;
|
|
22
22
|
};
|
|
23
|
-
readonly padTime: {
|
|
24
|
-
readonly boolean: true;
|
|
25
|
-
readonly description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.";
|
|
26
|
-
readonly default: true;
|
|
27
|
-
};
|
|
28
23
|
readonly shiftTime: {
|
|
29
24
|
readonly boolean: true;
|
|
30
25
|
readonly description: "Shift time during simulation to be set as the recording time";
|
|
@@ -120,11 +115,6 @@ export declare const COMMON_REPLAY_OPTIONS: {
|
|
|
120
115
|
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
121
116
|
readonly default: false;
|
|
122
117
|
};
|
|
123
|
-
padTime: {
|
|
124
|
-
readonly boolean: true;
|
|
125
|
-
readonly description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.";
|
|
126
|
-
readonly default: true;
|
|
127
|
-
};
|
|
128
118
|
shiftTime: {
|
|
129
119
|
readonly boolean: true;
|
|
130
120
|
readonly description: "Shift time during simulation to be set as the recording time";
|
|
@@ -33,11 +33,6 @@ exports.OPTIONS = {
|
|
|
33
33
|
description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)",
|
|
34
34
|
default: false,
|
|
35
35
|
},
|
|
36
|
-
padTime: {
|
|
37
|
-
boolean: true,
|
|
38
|
-
description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.",
|
|
39
|
-
default: true,
|
|
40
|
-
},
|
|
41
36
|
shiftTime: {
|
|
42
37
|
boolean: true,
|
|
43
38
|
description: "Shift time during simulation to be set as the recording time",
|
|
@@ -109,7 +104,6 @@ exports.COMMON_REPLAY_OPTIONS = {
|
|
|
109
104
|
headless: exports.OPTIONS.headless,
|
|
110
105
|
devTools: exports.OPTIONS.devTools,
|
|
111
106
|
bypassCSP: exports.OPTIONS.bypassCSP,
|
|
112
|
-
padTime: exports.OPTIONS.padTime,
|
|
113
107
|
shiftTime: exports.OPTIONS.shiftTime,
|
|
114
108
|
networkStubbing: exports.OPTIONS.networkStubbing,
|
|
115
109
|
skipPauses: exports.OPTIONS.skipPauses,
|
|
@@ -20,11 +20,6 @@ export declare const createTestCommand: import("yargs").CommandModule<unknown, i
|
|
|
20
20
|
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
21
21
|
readonly default: false;
|
|
22
22
|
};
|
|
23
|
-
padTime: {
|
|
24
|
-
readonly boolean: true;
|
|
25
|
-
readonly description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.";
|
|
26
|
-
readonly default: true;
|
|
27
|
-
};
|
|
28
23
|
shiftTime: {
|
|
29
24
|
readonly boolean: true;
|
|
30
25
|
readonly description: "Shift time during simulation to be set as the recording time";
|
|
@@ -48,7 +48,6 @@ const handleTestCreation = async (replay, sessionId) => {
|
|
|
48
48
|
await (0, config_utils_1.addTestCase)({
|
|
49
49
|
title: testNameResponse.name,
|
|
50
50
|
sessionId,
|
|
51
|
-
baseReplayId: replay.id,
|
|
52
51
|
});
|
|
53
52
|
logger.info(chalk_1.default.bold.white(`Test saved to ${chalk_1.default.green("meticulous.json")}.`));
|
|
54
53
|
};
|
|
@@ -60,7 +59,7 @@ apiToken, commitSha, devTools, bypassCSP,
|
|
|
60
59
|
// Record options
|
|
61
60
|
width, height, uploadIntervalMs, incognito, trace,
|
|
62
61
|
// Replay options
|
|
63
|
-
headless, screenshotSelector,
|
|
62
|
+
headless, screenshotSelector, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, }) => {
|
|
64
63
|
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
65
64
|
logger.info("Creating a new Meticulous test");
|
|
66
65
|
logger.info("Step 1: record a new test");
|
|
@@ -97,7 +96,6 @@ headless, screenshotSelector, padTime, shiftTime, networkStubbing, moveBeforeCli
|
|
|
97
96
|
bypassCSP,
|
|
98
97
|
screenshot: true,
|
|
99
98
|
screenshotSelector,
|
|
100
|
-
padTime,
|
|
101
99
|
shiftTime,
|
|
102
100
|
disableRemoteFonts,
|
|
103
101
|
noSandbox,
|
|
@@ -105,12 +103,10 @@ headless, screenshotSelector, padTime, shiftTime, networkStubbing, moveBeforeCli
|
|
|
105
103
|
moveBeforeClick,
|
|
106
104
|
cookiesFile,
|
|
107
105
|
skipPauses,
|
|
108
|
-
save: false,
|
|
109
106
|
// We replay against the original recorded URL
|
|
110
107
|
appUrl: null,
|
|
111
108
|
simulationIdForAssets: null,
|
|
112
109
|
// We don't try comparing to the original screenshot, so just set these to their defaults
|
|
113
|
-
baseSimulationId: null,
|
|
114
110
|
diffThreshold: common_options_1.OPTIONS.diffThreshold.default,
|
|
115
111
|
diffPixelThreshold: common_options_1.OPTIONS.diffPixelThreshold.default,
|
|
116
112
|
// We don't expose these options
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
3
3
|
import { GeneratedBy, Replay, ReplayEventsDependencies, ReplayExecutionOptions, ReplayTarget } from "@alwaysmeticulous/common";
|
|
4
4
|
import { ScreenshotAssertionsOptions, ScreenshotDiffOptions } from "../../command-utils/common-types";
|
|
5
|
-
import { ScreenshotDiffsSummary } from "../screenshot-diff/screenshot-diff.command";
|
|
6
5
|
export interface ReplayOptions extends AdditionalReplayOptions {
|
|
7
6
|
replayTarget: ReplayTarget;
|
|
8
7
|
executionOptions: ReplayExecutionOptions;
|
|
@@ -17,14 +16,10 @@ export interface ReplayResult {
|
|
|
17
16
|
/**
|
|
18
17
|
* Empty if screenshottingOptions.enabled was false.
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Returned as { hasDiffs: false } if screenshottingOptions.enabled was false.
|
|
23
|
-
*/
|
|
24
|
-
screenshotDiffsSummary: ScreenshotDiffsSummary;
|
|
19
|
+
screenshotDiffResultsByBaseReplayId: Record<string, ScreenshotDiffResult[]>;
|
|
25
20
|
}
|
|
26
|
-
export declare const replayCommandHandler: ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_,
|
|
27
|
-
export interface RawReplayCommandHandlerOptions extends ScreenshotDiffOptions, Omit<ReplayExecutionOptions, "maxDurationMs" | "maxEventCount">, AdditionalReplayOptions {
|
|
21
|
+
export declare const replayCommandHandler: ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, baseTestRunId, cookiesFile, generatedBy, testRunId, replayEventsDependencies, debugger: enableStepThroughDebugger, suppressScreenshotDiffLogging, }: ReplayOptions) => Promise<ReplayResult>;
|
|
22
|
+
export interface RawReplayCommandHandlerOptions extends ScreenshotDiffOptions, Omit<ReplayExecutionOptions, "maxDurationMs" | "maxEventCount">, Omit<AdditionalReplayOptions, "baseTestRunId"> {
|
|
28
23
|
screenshot: boolean;
|
|
29
24
|
appUrl: string | null | undefined;
|
|
30
25
|
simulationIdForAssets: string | null | undefined;
|
|
@@ -32,17 +27,16 @@ export interface RawReplayCommandHandlerOptions extends ScreenshotDiffOptions, O
|
|
|
32
27
|
maxDurationMs: number | null | undefined;
|
|
33
28
|
maxEventCount: number | null | undefined;
|
|
34
29
|
storyboard: boolean;
|
|
35
|
-
save: boolean | null | undefined;
|
|
36
30
|
}
|
|
37
31
|
interface AdditionalReplayOptions {
|
|
38
32
|
apiToken: string | null | undefined;
|
|
39
33
|
commitSha: string | null | undefined;
|
|
40
34
|
sessionId: string;
|
|
41
|
-
|
|
35
|
+
baseTestRunId: string | null | undefined;
|
|
42
36
|
cookiesFile: string | null | undefined;
|
|
43
37
|
debugger: boolean;
|
|
44
38
|
}
|
|
45
|
-
export declare const rawReplayCommandHandler: ({ apiToken, commitSha, sessionId, appUrl, simulationIdForAssets, headless, devTools, bypassCSP, screenshot, screenshotSelector,
|
|
39
|
+
export declare const rawReplayCommandHandler: ({ apiToken, commitSha, sessionId, appUrl, simulationIdForAssets, headless, devTools, bypassCSP, screenshot, screenshotSelector, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, debugger: enableStepThroughDebugger, }: RawReplayCommandHandlerOptions) => Promise<Replay>;
|
|
46
40
|
export declare const getReplayTarget: ({ appUrl, simulationIdForAssets, }: {
|
|
47
41
|
appUrl: string | null;
|
|
48
42
|
simulationIdForAssets: string | null;
|
|
@@ -78,11 +72,6 @@ export declare const replayCommand: import("yargs").CommandModule<unknown, impor
|
|
|
78
72
|
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
79
73
|
readonly default: false;
|
|
80
74
|
};
|
|
81
|
-
padTime: {
|
|
82
|
-
readonly boolean: true;
|
|
83
|
-
readonly description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.";
|
|
84
|
-
readonly default: true;
|
|
85
|
-
};
|
|
86
75
|
shiftTime: {
|
|
87
76
|
readonly boolean: true;
|
|
88
77
|
readonly description: "Shift time during simulation to be set as the recording time";
|
|
@@ -149,15 +138,6 @@ export declare const replayCommand: import("yargs").CommandModule<unknown, impor
|
|
|
149
138
|
string: true;
|
|
150
139
|
description: string;
|
|
151
140
|
};
|
|
152
|
-
baseSimulationId: {
|
|
153
|
-
string: true;
|
|
154
|
-
description: string;
|
|
155
|
-
alias: string;
|
|
156
|
-
};
|
|
157
|
-
save: {
|
|
158
|
-
boolean: true;
|
|
159
|
-
description: string;
|
|
160
|
-
};
|
|
161
141
|
debugger: {
|
|
162
142
|
boolean: true;
|
|
163
143
|
description: string;
|
|
@@ -34,7 +34,6 @@ const Sentry = __importStar(require("@sentry/node"));
|
|
|
34
34
|
const loglevel_1 = __importDefault(require("loglevel"));
|
|
35
35
|
const luxon_1 = require("luxon");
|
|
36
36
|
const client_1 = require("../../api/client");
|
|
37
|
-
const replay_diff_api_1 = require("../../api/replay-diff.api");
|
|
38
37
|
const replay_api_1 = require("../../api/replay.api");
|
|
39
38
|
const upload_1 = require("../../api/upload");
|
|
40
39
|
const archive_1 = require("../../archive/archive");
|
|
@@ -45,11 +44,10 @@ const replay_assets_1 = require("../../local-data/replay-assets");
|
|
|
45
44
|
const serve_assets_from_simulation_1 = require("../../local-data/serve-assets-from-simulation");
|
|
46
45
|
const sessions_1 = require("../../local-data/sessions");
|
|
47
46
|
const commit_sha_utils_1 = require("../../utils/commit-sha.utils");
|
|
48
|
-
const config_utils_1 = require("../../utils/config.utils");
|
|
49
47
|
const version_utils_1 = require("../../utils/version.utils");
|
|
50
48
|
const compute_diff_1 = require("./utils/compute-diff");
|
|
51
49
|
const exit_early_if_skip_upload_env_var_set_1 = require("./utils/exit-early-if-skip-upload-env-var-set");
|
|
52
|
-
const replayCommandHandler = async ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_,
|
|
50
|
+
const replayCommandHandler = async ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, baseTestRunId, cookiesFile, generatedBy, testRunId, replayEventsDependencies, debugger: enableStepThroughDebugger, suppressScreenshotDiffLogging, }) => {
|
|
53
51
|
var _a;
|
|
54
52
|
if (executionOptions.headless === true &&
|
|
55
53
|
enableStepThroughDebugger === true) {
|
|
@@ -137,7 +135,7 @@ const replayCommandHandler = async ({ replayTarget, executionOptions, screenshot
|
|
|
137
135
|
closeServer === null || closeServer === void 0 ? void 0 : closeServer();
|
|
138
136
|
const endTime = luxon_1.DateTime.utc();
|
|
139
137
|
logger.info(`Simulation time: ${endTime.diff(startTime).as("seconds")} seconds`);
|
|
140
|
-
(0, exit_early_if_skip_upload_env_var_set_1.exitEarlyIfSkipUploadEnvVarSet)(
|
|
138
|
+
(0, exit_early_if_skip_upload_env_var_set_1.exitEarlyIfSkipUploadEnvVarSet)(baseTestRunId);
|
|
141
139
|
logger.info("Sending simulation results to Meticulous");
|
|
142
140
|
// 8. Create a Zip archive containing the replay files
|
|
143
141
|
const createReplayArchiveSpan = transaction.startChild({
|
|
@@ -195,21 +193,19 @@ const replayCommandHandler = async ({ replayTarget, executionOptions, screenshot
|
|
|
195
193
|
else {
|
|
196
194
|
computeDiffsLogger.setLevel(logger.getLevel(), false);
|
|
197
195
|
}
|
|
198
|
-
const
|
|
196
|
+
const screenshotDiffResultsByBaseReplayId = screenshottingOptions.enabled && baseTestRunId
|
|
199
197
|
? await (0, compute_diff_1.computeDiff)({
|
|
200
198
|
client,
|
|
201
|
-
|
|
199
|
+
baseTestRunId,
|
|
200
|
+
sessionId,
|
|
202
201
|
headReplayId: replay.id,
|
|
203
202
|
tempDir,
|
|
204
203
|
screenshottingOptions,
|
|
205
204
|
logger: computeDiffsLogger,
|
|
206
205
|
})
|
|
207
|
-
: {
|
|
208
|
-
screenshotDiffResults: [],
|
|
209
|
-
screenshotDiffsSummary: { hasDiffs: false },
|
|
210
|
-
};
|
|
206
|
+
: {};
|
|
211
207
|
computeDiffSpan.finish();
|
|
212
|
-
return { replay,
|
|
208
|
+
return { replay, screenshotDiffResultsByBaseReplayId };
|
|
213
209
|
}
|
|
214
210
|
finally {
|
|
215
211
|
await (0, archive_1.deleteArchive)(archivePath);
|
|
@@ -236,12 +232,11 @@ const serveOrGetAppUrl = async (client, replayTarget) => {
|
|
|
236
232
|
const unknownReplayTargetType = (replayTarget) => {
|
|
237
233
|
throw new Error(`Unknown type of replay target: ${JSON.stringify(replayTarget)}`);
|
|
238
234
|
};
|
|
239
|
-
const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl, simulationIdForAssets, headless, devTools, bypassCSP, screenshot, screenshotSelector,
|
|
235
|
+
const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl, simulationIdForAssets, headless, devTools, bypassCSP, screenshot, screenshotSelector, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, debugger: enableStepThroughDebugger, }) => {
|
|
240
236
|
const executionOptions = {
|
|
241
237
|
headless,
|
|
242
238
|
devTools,
|
|
243
239
|
bypassCSP,
|
|
244
|
-
padTime,
|
|
245
240
|
shiftTime,
|
|
246
241
|
networkStubbing,
|
|
247
242
|
skipPauses,
|
|
@@ -265,7 +260,7 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
|
|
|
265
260
|
}
|
|
266
261
|
: { enabled: false };
|
|
267
262
|
const replayEventsDependencies = await (0, replay_assets_1.loadReplayEventsDependencies)();
|
|
268
|
-
const { replay
|
|
263
|
+
const { replay } = await (0, exports.replayCommandHandler)({
|
|
269
264
|
replayTarget: (0, exports.getReplayTarget)({
|
|
270
265
|
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
271
266
|
simulationIdForAssets: simulationIdForAssets !== null && simulationIdForAssets !== void 0 ? simulationIdForAssets : null,
|
|
@@ -276,43 +271,13 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
|
|
|
276
271
|
commitSha,
|
|
277
272
|
cookiesFile,
|
|
278
273
|
sessionId,
|
|
279
|
-
baseSimulationId,
|
|
280
274
|
generatedBy: generatedByOption,
|
|
275
|
+
baseTestRunId: null,
|
|
281
276
|
testRunId: null,
|
|
282
277
|
replayEventsDependencies,
|
|
283
278
|
debugger: enableStepThroughDebugger,
|
|
284
279
|
suppressScreenshotDiffLogging: false,
|
|
285
280
|
});
|
|
286
|
-
// Add test case to meticulous.json if --save option is passed
|
|
287
|
-
if (save) {
|
|
288
|
-
if (!screenshottingOptions.enabled) {
|
|
289
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
290
|
-
logger.error("Warning: saving a new test case without screenshot enabled.");
|
|
291
|
-
}
|
|
292
|
-
await (0, config_utils_1.addTestCase)({
|
|
293
|
-
title: `${sessionId} | ${replay.id}`,
|
|
294
|
-
sessionId,
|
|
295
|
-
baseReplayId: replay.id,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
if (screenshotDiffsSummary.hasDiffs) {
|
|
299
|
-
const client = (0, client_1.createClient)({ apiToken });
|
|
300
|
-
if (baseSimulationId == null) {
|
|
301
|
-
throw new Error("baseSimulationId must have been defined if there are diffs, but was null-ish");
|
|
302
|
-
}
|
|
303
|
-
// Store the diff
|
|
304
|
-
await (0, replay_diff_api_1.createReplayDiff)({
|
|
305
|
-
client,
|
|
306
|
-
headReplayId: replay.id,
|
|
307
|
-
baseReplayId: baseSimulationId,
|
|
308
|
-
testRunId: null,
|
|
309
|
-
data: {
|
|
310
|
-
screenshotAssertionsOptions: screenshottingOptions,
|
|
311
|
-
screenshotDiffResults,
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
|
316
281
|
return replay;
|
|
317
282
|
};
|
|
318
283
|
exports.rawReplayCommandHandler = rawReplayCommandHandler;
|
|
@@ -356,15 +321,6 @@ exports.replayCommand = (0, command_builder_1.buildCommand)("simulate")
|
|
|
356
321
|
string: true,
|
|
357
322
|
description: "Query selector to screenshot a specific DOM element instead of the whole page",
|
|
358
323
|
},
|
|
359
|
-
baseSimulationId: {
|
|
360
|
-
string: true,
|
|
361
|
-
description: "Base simulation id to diff the final state screenshot against",
|
|
362
|
-
alias: "baseReplayId",
|
|
363
|
-
},
|
|
364
|
-
save: {
|
|
365
|
-
boolean: true,
|
|
366
|
-
description: "Adds the simulation to the list of test cases in meticulous.json",
|
|
367
|
-
},
|
|
368
324
|
debugger: {
|
|
369
325
|
boolean: true,
|
|
370
326
|
description: "Opens a step through debugger to advance through the replay event by event",
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { ScreenshotAssertionsEnabledOptions, ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
2
2
|
import { AxiosInstance } from "axios";
|
|
3
3
|
import log from "loglevel";
|
|
4
|
-
import { ScreenshotDiffsSummary } from "../../screenshot-diff/screenshot-diff.command";
|
|
5
4
|
export interface ComputeAndSaveDiffOptions {
|
|
6
5
|
client: AxiosInstance;
|
|
7
|
-
|
|
6
|
+
baseTestRunId: string;
|
|
7
|
+
sessionId: string;
|
|
8
8
|
headReplayId: string;
|
|
9
9
|
tempDir: string;
|
|
10
10
|
screenshottingOptions: ScreenshotAssertionsEnabledOptions;
|
|
11
11
|
logger: log.Logger;
|
|
12
12
|
}
|
|
13
|
-
export declare const computeDiff: ({ client,
|
|
14
|
-
screenshotDiffResults: ScreenshotDiffResult[];
|
|
15
|
-
screenshotDiffsSummary: ScreenshotDiffsSummary;
|
|
16
|
-
}>;
|
|
13
|
+
export declare const computeDiff: ({ client, baseTestRunId, sessionId, tempDir, headReplayId, screenshottingOptions, logger, }: ComputeAndSaveDiffOptions) => Promise<Record<string, ScreenshotDiffResult[]>>;
|
|
@@ -1,30 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.computeDiff = void 0;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const download_1 = require("../../../api/download");
|
|
7
|
+
const test_run_api_1 = require("../../../api/test-run.api");
|
|
4
8
|
const replays_1 = require("../../../local-data/replays");
|
|
5
9
|
const screenshot_diff_command_1 = require("../../screenshot-diff/screenshot-diff.command");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
10
|
+
const get_screenshot_filename_1 = require("../../screenshot-diff/utils/get-screenshot-filename");
|
|
11
|
+
const computeDiff = async ({ client, baseTestRunId, sessionId, tempDir, headReplayId, screenshottingOptions, logger, }) => {
|
|
12
|
+
var _a;
|
|
13
|
+
logger.info(`Diffing screenshots against replays of session ${sessionId} in test run ${baseTestRunId}`);
|
|
14
|
+
const baseScreenshotsDir = (0, path_1.join)((0, replays_1.getScreenshotsDir)(tempDir), "base-screenshots");
|
|
15
|
+
await (0, promises_1.mkdir)(baseScreenshotsDir, { recursive: true });
|
|
16
|
+
const baseScreenshots = await (0, test_run_api_1.getBaseScreenshots)({
|
|
13
17
|
client,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
baseScreenshotsDir: baseReplayScreenshotsDir,
|
|
17
|
-
headScreenshotsDir: headReplayScreenshotsDir,
|
|
18
|
-
diffOptions: screenshottingOptions.diffOptions,
|
|
19
|
-
logger,
|
|
18
|
+
testRunId: baseTestRunId,
|
|
19
|
+
sessionId,
|
|
20
20
|
});
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const screenshotIdentifiersByBaseReplayId = groupByBaseReplayId(baseScreenshots);
|
|
22
|
+
await downloadScreenshots(baseScreenshots, baseScreenshotsDir);
|
|
23
|
+
logger.debug(`Downloaded ${baseScreenshots.length} base screenshots to ${baseScreenshotsDir}}`);
|
|
24
|
+
const headReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)(tempDir);
|
|
25
|
+
const headReplayScreenshots = await (0, replays_1.getScreenshotFiles)(headReplayScreenshotsDir);
|
|
26
|
+
const screenshotDiffResultsByBaseReplayId = {};
|
|
27
|
+
// Diff the screenshots for each base replay
|
|
28
|
+
for (const baseReplayId of screenshotIdentifiersByBaseReplayId.keys()) {
|
|
29
|
+
const baseScreenshotIdentifiers = (_a = screenshotIdentifiersByBaseReplayId.get(baseReplayId)) !== null && _a !== void 0 ? _a : [];
|
|
30
|
+
const baseReplayScreenshots = baseScreenshotIdentifiers.map((identifier) => ({
|
|
31
|
+
identifier,
|
|
32
|
+
fileName: (0, get_screenshot_filename_1.getScreenshotFilename)(identifier),
|
|
33
|
+
}));
|
|
34
|
+
const resultsForBaseReplay = await (0, screenshot_diff_command_1.diffScreenshots)({
|
|
35
|
+
baseReplayId,
|
|
36
|
+
headReplayId,
|
|
37
|
+
baseScreenshotsDir: baseScreenshotsDir,
|
|
38
|
+
headScreenshotsDir: headReplayScreenshotsDir,
|
|
39
|
+
headReplayScreenshots,
|
|
40
|
+
baseReplayScreenshots,
|
|
41
|
+
diffOptions: screenshottingOptions.diffOptions,
|
|
42
|
+
});
|
|
43
|
+
screenshotDiffResultsByBaseReplayId[baseReplayId] = resultsForBaseReplay;
|
|
44
|
+
}
|
|
45
|
+
(0, screenshot_diff_command_1.logDifferences)({
|
|
46
|
+
results: Object.values(screenshotDiffResultsByBaseReplayId).flat(),
|
|
25
47
|
diffOptions: screenshottingOptions.diffOptions,
|
|
26
48
|
logger,
|
|
27
49
|
});
|
|
28
|
-
|
|
50
|
+
// Delete base screenshots directory and all files inside it
|
|
51
|
+
// (we don't want to upload it to the backend)
|
|
52
|
+
await deleteScreenshotsDirectory(baseScreenshotsDir);
|
|
53
|
+
return screenshotDiffResultsByBaseReplayId;
|
|
29
54
|
};
|
|
30
55
|
exports.computeDiff = computeDiff;
|
|
56
|
+
const deleteScreenshotsDirectory = async (directory) => {
|
|
57
|
+
const files = await (0, promises_1.opendir)(directory);
|
|
58
|
+
for await (const file of files) {
|
|
59
|
+
if (file.isFile()) {
|
|
60
|
+
await (0, promises_1.rm)((0, path_1.join)(directory, file.name));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
throw new Error(`Expected screenshots directory to only contain screenshot files, but it contained: ${file.name}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await (0, promises_1.rmdir)(directory);
|
|
67
|
+
};
|
|
68
|
+
const groupByBaseReplayId = (screenshots) => {
|
|
69
|
+
const screenshotIdentifiersByBaseReplayId = new Map();
|
|
70
|
+
screenshots.forEach(({ replayId, screenshotIdentifier }) => {
|
|
71
|
+
var _a;
|
|
72
|
+
const screenshotIdentifiers = (_a = screenshotIdentifiersByBaseReplayId.get(replayId)) !== null && _a !== void 0 ? _a : [];
|
|
73
|
+
screenshotIdentifiers.push(screenshotIdentifier);
|
|
74
|
+
screenshotIdentifiersByBaseReplayId.set(replayId, screenshotIdentifiers);
|
|
75
|
+
});
|
|
76
|
+
return screenshotIdentifiersByBaseReplayId;
|
|
77
|
+
};
|
|
78
|
+
const downloadScreenshots = async (screenshots, directory) => {
|
|
79
|
+
// Download in batches of 20
|
|
80
|
+
const screenshotBatches = chunk(screenshots, 20);
|
|
81
|
+
for (const screenshotBatch of screenshotBatches) {
|
|
82
|
+
const screenshotDownloads = screenshotBatch.map((screenshot) => {
|
|
83
|
+
const filePath = (0, path_1.join)(directory, (0, get_screenshot_filename_1.getScreenshotFilename)(screenshot.screenshotIdentifier));
|
|
84
|
+
return (0, download_1.downloadFile)(screenshot.screenshotUrl, filePath);
|
|
85
|
+
});
|
|
86
|
+
await Promise.all(screenshotDownloads);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const chunk = (array, size) => {
|
|
90
|
+
const chunkedArray = [];
|
|
91
|
+
for (let i = 0; i < array.length; i += size) {
|
|
92
|
+
chunkedArray.push(array.slice(i, i + size));
|
|
93
|
+
}
|
|
94
|
+
return chunkedArray;
|
|
95
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const exitEarlyIfSkipUploadEnvVarSet: (
|
|
1
|
+
export declare const exitEarlyIfSkipUploadEnvVarSet: (baseTestRunId: string | null | undefined) => void;
|
|
@@ -9,12 +9,12 @@ const loglevel_1 = __importDefault(require("loglevel"));
|
|
|
9
9
|
const METICULOUS_SKIP_UPLOAD_ENV_VAR = "METICULOUS_SKIP_UPLOAD";
|
|
10
10
|
// Uploading the zip can take a long time, so we expose a env variable to support skipping it
|
|
11
11
|
// We don't expose this as a first-class CLI option because we don't want to pollute the list of CLI options
|
|
12
|
-
const exitEarlyIfSkipUploadEnvVarSet = (
|
|
12
|
+
const exitEarlyIfSkipUploadEnvVarSet = (baseTestRunId) => {
|
|
13
13
|
if (!shouldSkipUpload()) {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
if (
|
|
17
|
-
throw new Error(`Cannot specify
|
|
16
|
+
if (baseTestRunId != null) {
|
|
17
|
+
throw new Error(`Cannot specify baseTestRunId and compare to base results when ${METICULOUS_SKIP_UPLOAD_ENV_VAR} is set to true`);
|
|
18
18
|
}
|
|
19
19
|
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
20
20
|
logger.info(`Skipping upload / exiting early since ${METICULOUS_SKIP_UPLOAD_ENV_VAR} is set to true`);
|
|
@@ -30,11 +30,6 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
30
30
|
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
31
31
|
readonly default: false;
|
|
32
32
|
};
|
|
33
|
-
readonly padTime: {
|
|
34
|
-
readonly boolean: true;
|
|
35
|
-
readonly description: "Pad replay time according to recording duration. Please note this option will be ignored if running with the '--skipPauses' option.";
|
|
36
|
-
readonly default: true;
|
|
37
|
-
};
|
|
38
33
|
readonly shiftTime: {
|
|
39
34
|
readonly boolean: true;
|
|
40
35
|
readonly description: "Shift time during simulation to be set as the recording time";
|
|
@@ -87,11 +82,6 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
87
82
|
readonly string: true;
|
|
88
83
|
readonly description: "The URL to execute the tests against. If left absent here and in the test cases file, then will use the URL the test was originally recorded against.";
|
|
89
84
|
};
|
|
90
|
-
readonly useAssetsSnapshottedInBaseSimulation: {
|
|
91
|
-
readonly boolean: true;
|
|
92
|
-
readonly description: string;
|
|
93
|
-
readonly default: false;
|
|
94
|
-
};
|
|
95
85
|
readonly githubSummary: {
|
|
96
86
|
readonly boolean: true;
|
|
97
87
|
readonly description: "Outputs a summary page for GitHub actions";
|
|
@@ -126,6 +116,10 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
126
116
|
readonly string: true;
|
|
127
117
|
readonly description: string;
|
|
128
118
|
};
|
|
119
|
+
readonly baseTestRunId: {
|
|
120
|
+
readonly string: true;
|
|
121
|
+
readonly description: "The id of a test run to compare screenshots against.";
|
|
122
|
+
};
|
|
129
123
|
readonly moveBeforeClick: {
|
|
130
124
|
readonly boolean: true;
|
|
131
125
|
readonly description: "Simulate mouse movement before clicking";
|