@alwaysmeticulous/cli 2.36.0 → 2.37.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/commands/create-test/create-test.command.js +0 -3
- package/dist/commands/replay/replay.command.d.ts +5 -20
- package/dist/commands/replay/replay.command.js +10 -53
- 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 -5
- package/dist/commands/run-all-tests/run-all-tests.command.js +6 -9
- 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 {};
|
|
@@ -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
|
};
|
|
@@ -105,12 +104,10 @@ headless, screenshotSelector, padTime, shiftTime, networkStubbing, moveBeforeCli
|
|
|
105
104
|
moveBeforeClick,
|
|
106
105
|
cookiesFile,
|
|
107
106
|
skipPauses,
|
|
108
|
-
save: false,
|
|
109
107
|
// We replay against the original recorded URL
|
|
110
108
|
appUrl: null,
|
|
111
109
|
simulationIdForAssets: null,
|
|
112
110
|
// We don't try comparing to the original screenshot, so just set these to their defaults
|
|
113
|
-
baseSimulationId: null,
|
|
114
111
|
diffThreshold: common_options_1.OPTIONS.diffThreshold.default,
|
|
115
112
|
diffPixelThreshold: common_options_1.OPTIONS.diffPixelThreshold.default,
|
|
116
113
|
// 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, padTime, 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;
|
|
@@ -149,15 +143,6 @@ export declare const replayCommand: import("yargs").CommandModule<unknown, impor
|
|
|
149
143
|
string: true;
|
|
150
144
|
description: string;
|
|
151
145
|
};
|
|
152
|
-
baseSimulationId: {
|
|
153
|
-
string: true;
|
|
154
|
-
description: string;
|
|
155
|
-
alias: string;
|
|
156
|
-
};
|
|
157
|
-
save: {
|
|
158
|
-
boolean: true;
|
|
159
|
-
description: string;
|
|
160
|
-
};
|
|
161
146
|
debugger: {
|
|
162
147
|
boolean: true;
|
|
163
148
|
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,7 +232,7 @@ 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, padTime, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, debugger: enableStepThroughDebugger, }) => {
|
|
240
236
|
const executionOptions = {
|
|
241
237
|
headless,
|
|
242
238
|
devTools,
|
|
@@ -265,7 +261,7 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
|
|
|
265
261
|
}
|
|
266
262
|
: { enabled: false };
|
|
267
263
|
const replayEventsDependencies = await (0, replay_assets_1.loadReplayEventsDependencies)();
|
|
268
|
-
const { replay
|
|
264
|
+
const { replay } = await (0, exports.replayCommandHandler)({
|
|
269
265
|
replayTarget: (0, exports.getReplayTarget)({
|
|
270
266
|
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
271
267
|
simulationIdForAssets: simulationIdForAssets !== null && simulationIdForAssets !== void 0 ? simulationIdForAssets : null,
|
|
@@ -276,43 +272,13 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
|
|
|
276
272
|
commitSha,
|
|
277
273
|
cookiesFile,
|
|
278
274
|
sessionId,
|
|
279
|
-
baseSimulationId,
|
|
280
275
|
generatedBy: generatedByOption,
|
|
276
|
+
baseTestRunId: null,
|
|
281
277
|
testRunId: null,
|
|
282
278
|
replayEventsDependencies,
|
|
283
279
|
debugger: enableStepThroughDebugger,
|
|
284
280
|
suppressScreenshotDiffLogging: false,
|
|
285
281
|
});
|
|
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
282
|
return replay;
|
|
317
283
|
};
|
|
318
284
|
exports.rawReplayCommandHandler = rawReplayCommandHandler;
|
|
@@ -356,15 +322,6 @@ exports.replayCommand = (0, command_builder_1.buildCommand)("simulate")
|
|
|
356
322
|
string: true,
|
|
357
323
|
description: "Query selector to screenshot a specific DOM element instead of the whole page",
|
|
358
324
|
},
|
|
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
325
|
debugger: {
|
|
369
326
|
boolean: true,
|
|
370
327
|
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`);
|
|
@@ -87,11 +87,6 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
87
87
|
readonly string: true;
|
|
88
88
|
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
89
|
};
|
|
90
|
-
readonly useAssetsSnapshottedInBaseSimulation: {
|
|
91
|
-
readonly boolean: true;
|
|
92
|
-
readonly description: string;
|
|
93
|
-
readonly default: false;
|
|
94
|
-
};
|
|
95
90
|
readonly githubSummary: {
|
|
96
91
|
readonly boolean: true;
|
|
97
92
|
readonly description: "Outputs a summary page for GitHub actions";
|
|
@@ -126,6 +121,10 @@ export declare const runAllTestsCommand: import("yargs").CommandModule<unknown,
|
|
|
126
121
|
readonly string: true;
|
|
127
122
|
readonly description: string;
|
|
128
123
|
};
|
|
124
|
+
readonly baseTestRunId: {
|
|
125
|
+
readonly string: true;
|
|
126
|
+
readonly description: "The id of a test run to compare screenshots against.";
|
|
127
|
+
};
|
|
129
128
|
readonly moveBeforeClick: {
|
|
130
129
|
readonly boolean: true;
|
|
131
130
|
readonly description: "Simulate mouse movement before clicking";
|
|
@@ -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,
|
|
10
|
+
const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, padTime, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_, deflake, maxRetriesOnFailure, useCache, testsFile, disableRemoteFonts, noSandbox, skipPauses, moveBeforeClick, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, baseTestRunId, }) => {
|
|
11
11
|
const executionOptions = {
|
|
12
12
|
headless,
|
|
13
13
|
devTools,
|
|
@@ -45,8 +45,8 @@ const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl,
|
|
|
45
45
|
apiToken: apiToken !== null && apiToken !== void 0 ? apiToken : null,
|
|
46
46
|
commitSha,
|
|
47
47
|
baseCommitSha: baseCommitSha !== null && baseCommitSha !== void 0 ? baseCommitSha : null,
|
|
48
|
+
baseTestRunId: baseTestRunId !== null && baseTestRunId !== void 0 ? baseTestRunId : null,
|
|
48
49
|
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
49
|
-
useAssetsSnapshottedInBaseSimulation,
|
|
50
50
|
parallelTasks: parrelelTasks !== null && parrelelTasks !== void 0 ? parrelelTasks : null,
|
|
51
51
|
deflake,
|
|
52
52
|
maxRetriesOnFailure,
|
|
@@ -70,13 +70,6 @@ exports.runAllTestsCommand = (0, command_builder_1.buildCommand)("run-all-tests"
|
|
|
70
70
|
string: true,
|
|
71
71
|
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.",
|
|
72
72
|
},
|
|
73
|
-
useAssetsSnapshottedInBaseSimulation: {
|
|
74
|
-
boolean: true,
|
|
75
|
-
description: "If present will run each session against a local server serving up previously snapshotted assets (HTML, JS, CSS etc.)" +
|
|
76
|
-
" from the base simulation/replay the test is comparing against. The sessions will then be replayed against those local urls." +
|
|
77
|
-
" This is an alternative to specifying an appUrl.",
|
|
78
|
-
default: false,
|
|
79
|
-
},
|
|
80
73
|
githubSummary: {
|
|
81
74
|
boolean: true,
|
|
82
75
|
description: "Outputs a summary page for GitHub actions",
|
|
@@ -117,6 +110,10 @@ exports.runAllTestsCommand = (0, command_builder_1.buildCommand)("run-all-tests"
|
|
|
117
110
|
description: "The path to the meticulous.json file containing the list of tests you want to run." +
|
|
118
111
|
" If not set a search will be performed to find a meticulous.json file in the current directory or the nearest parent directory.",
|
|
119
112
|
},
|
|
113
|
+
baseTestRunId: {
|
|
114
|
+
string: true,
|
|
115
|
+
description: "The id of a test run to compare screenshots against.",
|
|
116
|
+
},
|
|
120
117
|
moveBeforeClick: common_options_1.OPTIONS.moveBeforeClick,
|
|
121
118
|
...common_options_1.COMMON_REPLAY_OPTIONS,
|
|
122
119
|
...common_options_1.SCREENSHOT_DIFF_OPTIONS,
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/// <reference types="yargs" />
|
|
2
2
|
import { ScreenshotDiffOptions, ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
3
|
-
import { AxiosInstance } from "axios";
|
|
4
3
|
import log from "loglevel";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { IdentifiedScreenshotFile } from "../../local-data/replays";
|
|
5
|
+
export declare const diffScreenshots: ({ headReplayId, baseReplayId, baseScreenshotsDir, headScreenshotsDir, baseReplayScreenshots, headReplayScreenshots, diffOptions, }: {
|
|
7
6
|
baseReplayId: string;
|
|
8
7
|
headReplayId: string;
|
|
9
8
|
baseScreenshotsDir: string;
|
|
10
9
|
headScreenshotsDir: string;
|
|
10
|
+
baseReplayScreenshots: IdentifiedScreenshotFile[];
|
|
11
|
+
headReplayScreenshots: IdentifiedScreenshotFile[];
|
|
11
12
|
diffOptions: ScreenshotDiffOptions;
|
|
12
|
-
logger: log.Logger;
|
|
13
13
|
}) => Promise<ScreenshotDiffResult[]>;
|
|
14
|
-
export declare const
|
|
15
|
-
baseReplayId: string;
|
|
16
|
-
headReplayId: string;
|
|
14
|
+
export declare const logDifferences: ({ results, diffOptions, logger, }: {
|
|
17
15
|
results: ScreenshotDiffResult[];
|
|
18
16
|
diffOptions: ScreenshotDiffOptions;
|
|
19
17
|
logger: log.Logger;
|
|
20
|
-
}) =>
|
|
18
|
+
}) => void;
|
|
21
19
|
export type ScreenshotDiffsSummary = HasDiffsScreenshotDiffsResult | NoDiffsScreenshotDiffsResult;
|
|
22
20
|
export interface HasDiffsScreenshotDiffsResult {
|
|
23
21
|
hasDiffs: true;
|