@alwaysmeticulous/cli 2.17.0 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,22 @@
1
1
  /// <reference types="yargs" />
2
+ import { ScreenshotDiffResult } from "@alwaysmeticulous/api";
2
3
  import { GeneratedBy, Replay, ReplayEventsDependencies, ReplayExecutionOptions, ReplayTarget } from "@alwaysmeticulous/common";
3
4
  import { ScreenshotAssertionsOptions, ScreenshotDiffOptions } from "../../command-utils/common-types";
5
+ import { ScreenshotDiffsSummary } from "../screenshot-diff/screenshot-diff.command";
4
6
  export interface ReplayOptions extends AdditionalReplayOptions {
5
7
  replayTarget: ReplayTarget;
6
8
  executionOptions: ReplayExecutionOptions;
7
9
  screenshottingOptions: ScreenshotAssertionsOptions;
8
- exitOnMismatch: boolean;
9
10
  generatedBy: GeneratedBy;
10
11
  testRunId: string | null;
11
12
  replayEventsDependencies: ReplayEventsDependencies;
12
13
  }
13
- export declare const replayCommandHandler: ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, save, exitOnMismatch, baseSimulationId: baseReplayId_, cookiesFile, generatedBy, testRunId, replayEventsDependencies, }: ReplayOptions) => Promise<Replay>;
14
+ export interface ReplayResult {
15
+ replay: Replay;
16
+ screenshotDiffResults: ScreenshotDiffResult[] | null;
17
+ screenshotDiffsSummary: ScreenshotDiffsSummary;
18
+ }
19
+ export declare const replayCommandHandler: ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, save, baseSimulationId: baseReplayId_, cookiesFile, generatedBy, testRunId, replayEventsDependencies, }: ReplayOptions) => Promise<ReplayResult>;
14
20
  export interface RawReplayCommandHandlerOptions extends ScreenshotDiffOptions, Omit<ReplayExecutionOptions, "maxDurationMs" | "maxEventCount">, AdditionalReplayOptions {
15
21
  screenshot: boolean;
16
22
  appUrl: string | null | undefined;
@@ -10,7 +10,6 @@ const common_1 = require("@alwaysmeticulous/common");
10
10
  const loglevel_1 = __importDefault(require("loglevel"));
11
11
  const luxon_1 = require("luxon");
12
12
  const client_1 = require("../../api/client");
13
- const replay_diff_api_1 = require("../../api/replay-diff.api");
14
13
  const replay_api_1 = require("../../api/replay.api");
15
14
  const upload_1 = require("../../api/upload");
16
15
  const archive_1 = require("../../archive/archive");
@@ -18,14 +17,13 @@ const command_builder_1 = require("../../command-utils/command-builder");
18
17
  const common_options_1 = require("../../command-utils/common-options");
19
18
  const local_data_utils_1 = require("../../local-data/local-data.utils");
20
19
  const replay_assets_1 = require("../../local-data/replay-assets");
21
- const replays_1 = require("../../local-data/replays");
22
20
  const serve_assets_from_simulation_1 = require("../../local-data/serve-assets-from-simulation");
23
21
  const sessions_1 = require("../../local-data/sessions");
24
22
  const commit_sha_utils_1 = require("../../utils/commit-sha.utils");
25
23
  const config_utils_1 = require("../../utils/config.utils");
26
24
  const version_utils_1 = require("../../utils/version.utils");
27
- const screenshot_diff_command_1 = require("../screenshot-diff/screenshot-diff.command");
28
- const replayCommandHandler = async ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, save, exitOnMismatch, baseSimulationId: baseReplayId_, cookiesFile, generatedBy, testRunId, replayEventsDependencies, }) => {
25
+ const compute_and_save_diff_1 = require("./utils/compute-and-save-diff");
26
+ const replayCommandHandler = async ({ replayTarget, executionOptions, screenshottingOptions, apiToken, sessionId, commitSha: commitSha_, save, baseSimulationId: baseReplayId_, cookiesFile, generatedBy, testRunId, replayEventsDependencies, }) => {
29
27
  const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
30
28
  const client = (0, client_1.createClient)({ apiToken });
31
29
  // 1. Check session files
@@ -82,93 +80,69 @@ const replayCommandHandler = async ({ replayTarget, executionOptions, screenshot
82
80
  logger.info("Sending simulation results to Meticulous");
83
81
  // 8. Create a Zip archive containing the replay files
84
82
  const archivePath = await (0, archive_1.createReplayArchive)(tempDir);
85
- // 9. Get upload URL
86
- const replay = await (0, replay_api_1.createReplay)({
87
- client,
88
- commitSha,
89
- sessionId,
90
- meticulousSha,
91
- version: "v2",
92
- metadata: { generatedBy },
93
- });
94
- const uploadUrlData = await (0, replay_api_1.getReplayPushUrl)(client, replay.id);
95
- if (!uploadUrlData) {
96
- logger.error("Error: Could not get a push URL from the Meticulous API");
97
- process.exit(1);
98
- }
99
- const uploadUrl = uploadUrlData.pushUrl;
100
- // 10. Send archive to S3
101
83
  try {
102
- await (0, upload_1.uploadArchive)(uploadUrl, archivePath);
103
- }
104
- catch (error) {
105
- await (0, replay_api_1.putReplayPushedStatus)(client, replay.id, "failure", replayCommandId).catch(logger.error);
106
- logger.error(error);
107
- process.exit(1);
108
- }
109
- // 11. Report successful upload to Meticulous
110
- const updatedProjectBuild = await (0, replay_api_1.putReplayPushedStatus)(client, replay.id, "success", replayCommandId);
111
- logger.info("Simulation artifacts successfully sent to Meticulous");
112
- logger.debug(updatedProjectBuild);
113
- const replayUrl = (0, replay_api_1.getReplayUrl)(replay);
114
- logger.info("=======");
115
- logger.info(`View simulation at: ${replayUrl}`);
116
- logger.info("=======");
117
- // 12. Diff against base replay screenshot if one is provided
118
- const baseReplayId = baseReplayId_ || "";
119
- if (screenshottingOptions.enabled && baseReplayId) {
120
- logger.info(`Diffing screenshots against replay ${baseReplayId}`);
121
- await (0, replays_1.getOrFetchReplay)(client, baseReplayId);
122
- await (0, replays_1.getOrFetchReplayArchive)(client, baseReplayId);
123
- const baseReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)((0, replays_1.getReplayDir)(baseReplayId));
124
- const headReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)(tempDir);
125
- const screenshotDiffResults = await (0, screenshot_diff_command_1.diffScreenshots)({
84
+ // 9. Get upload URL
85
+ const replay = await (0, replay_api_1.createReplay)({
126
86
  client,
127
- baseReplayId,
128
- headReplayId: replay.id,
129
- baseScreenshotsDir: baseReplayScreenshotsDir,
130
- headScreenshotsDir: headReplayScreenshotsDir,
131
- diffOptions: screenshottingOptions.diffOptions,
132
- });
133
- const replayDiff = await (0, replay_diff_api_1.createReplayDiff)({
134
- client,
135
- headReplayId: replay.id,
136
- baseReplayId,
137
- testRunId,
138
- data: {
139
- screenshotAssertionsOptions: screenshottingOptions,
140
- screenshotDiffResults,
141
- },
87
+ commitSha,
88
+ sessionId,
89
+ meticulousSha,
90
+ version: "v2",
91
+ metadata: { generatedBy },
142
92
  });
143
- logger.debug(replayDiff);
93
+ const uploadUrlData = await (0, replay_api_1.getReplayPushUrl)(client, replay.id);
94
+ if (!uploadUrlData) {
95
+ logger.error("Error: Could not get a push URL from the Meticulous API");
96
+ process.exit(1);
97
+ }
98
+ const uploadUrl = uploadUrlData.pushUrl;
99
+ // 10. Send archive to S3
144
100
  try {
145
- (0, screenshot_diff_command_1.checkScreenshotDiffResult)({
146
- baseReplayId,
147
- headReplayId: replay.id,
148
- results: screenshotDiffResults,
149
- diffOptions: screenshottingOptions.diffOptions,
150
- });
101
+ await (0, upload_1.uploadArchive)(uploadUrl, archivePath);
151
102
  }
152
103
  catch (error) {
153
- if (exitOnMismatch) {
154
- process.exit(1);
104
+ await (0, replay_api_1.putReplayPushedStatus)(client, replay.id, "failure", replayCommandId).catch(logger.error);
105
+ logger.error(error);
106
+ process.exit(1);
107
+ }
108
+ // 11. Report successful upload to Meticulous
109
+ const updatedProjectBuild = await (0, replay_api_1.putReplayPushedStatus)(client, replay.id, "success", replayCommandId);
110
+ logger.info("Simulation artifacts successfully sent to Meticulous");
111
+ logger.debug(updatedProjectBuild);
112
+ const replayUrl = (0, replay_api_1.getReplayUrl)(replay);
113
+ logger.info("=======");
114
+ logger.info(`View simulation at: ${replayUrl}`);
115
+ logger.info("=======");
116
+ // 12. Diff against base replay screenshot if one is provided
117
+ const { screenshotDiffResults, screenshotDiffsSummary } = screenshottingOptions.enabled && baseReplayId_
118
+ ? await (0, compute_and_save_diff_1.computeAndSaveDiff)({
119
+ client,
120
+ baseReplayId: baseReplayId_ !== null && baseReplayId_ !== void 0 ? baseReplayId_ : "",
121
+ headReplayId: replay.id,
122
+ tempDir,
123
+ screenshottingOptions,
124
+ testRunId,
125
+ })
126
+ : {
127
+ screenshotDiffResults: null,
128
+ screenshotDiffsSummary: { hasDiffs: false },
129
+ };
130
+ // 13. Add test case to meticulous.json if --save option is passed
131
+ if (save) {
132
+ if (!screenshottingOptions.enabled) {
133
+ logger.error("Warning: saving a new test case without screenshot enabled.");
155
134
  }
156
- throw error;
135
+ await (0, config_utils_1.addTestCase)({
136
+ title: `${sessionId} | ${replay.id}`,
137
+ sessionId,
138
+ baseReplayId: replay.id,
139
+ });
157
140
  }
141
+ return { replay, screenshotDiffResults, screenshotDiffsSummary };
158
142
  }
159
- // 13. Add test case to meticulous.json if --save option is passed
160
- if (save) {
161
- if (!screenshottingOptions.enabled) {
162
- logger.error("Warning: saving a new test case without screenshot enabled.");
163
- }
164
- await (0, config_utils_1.addTestCase)({
165
- title: `${sessionId} | ${replay.id}`,
166
- sessionId,
167
- baseReplayId: replay.id,
168
- });
143
+ finally {
144
+ await (0, archive_1.deleteArchive)(archivePath);
169
145
  }
170
- await (0, archive_1.deleteArchive)(archivePath);
171
- return replay;
172
146
  };
173
147
  exports.replayCommandHandler = replayCommandHandler;
174
148
  const serveOrGetAppUrl = async (client, replayTarget) => {
@@ -218,7 +192,7 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
218
192
  }
219
193
  : { enabled: false };
220
194
  const replayEventsDependencies = await (0, replay_assets_1.loadReplayEventsDependencies)();
221
- return (0, exports.replayCommandHandler)({
195
+ const { replay, screenshotDiffsSummary } = await (0, exports.replayCommandHandler)({
222
196
  replayTarget: (0, exports.getReplayTarget)({
223
197
  appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
224
198
  simulationIdForAssets: simulationIdForAssets !== null && simulationIdForAssets !== void 0 ? simulationIdForAssets : null,
@@ -231,11 +205,14 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
231
205
  sessionId,
232
206
  baseSimulationId,
233
207
  save,
234
- exitOnMismatch: true,
235
208
  generatedBy: generatedByOption,
236
209
  testRunId: null,
237
210
  replayEventsDependencies,
238
211
  });
212
+ if (screenshotDiffsSummary.hasDiffs) {
213
+ process.exit(1);
214
+ }
215
+ return replay;
239
216
  };
240
217
  exports.rawReplayCommandHandler = rawReplayCommandHandler;
241
218
  const getReplayTarget = ({ appUrl, simulationIdForAssets, }) => {
@@ -0,0 +1,15 @@
1
+ import { ScreenshotAssertionsEnabledOptions, ScreenshotDiffResult } from "@alwaysmeticulous/api";
2
+ import { AxiosInstance } from "axios";
3
+ import { ScreenshotDiffsSummary } from "../../screenshot-diff/screenshot-diff.command";
4
+ export interface ComputeAndSaveDiffOptions {
5
+ client: AxiosInstance;
6
+ testRunId: string | null;
7
+ baseReplayId: string;
8
+ headReplayId: string;
9
+ tempDir: string;
10
+ screenshottingOptions: ScreenshotAssertionsEnabledOptions;
11
+ }
12
+ export declare const computeAndSaveDiff: ({ client, baseReplayId, tempDir, headReplayId, screenshottingOptions, testRunId, }: ComputeAndSaveDiffOptions) => Promise<{
13
+ screenshotDiffResults: ScreenshotDiffResult[];
14
+ screenshotDiffsSummary: ScreenshotDiffsSummary;
15
+ }>;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.computeAndSaveDiff = void 0;
7
+ const common_1 = require("@alwaysmeticulous/common");
8
+ const loglevel_1 = __importDefault(require("loglevel"));
9
+ const replay_diff_api_1 = require("../../../api/replay-diff.api");
10
+ const replays_1 = require("../../../local-data/replays");
11
+ const screenshot_diff_command_1 = require("../../screenshot-diff/screenshot-diff.command");
12
+ const computeAndSaveDiff = async ({ client, baseReplayId, tempDir, headReplayId, screenshottingOptions, testRunId, }) => {
13
+ const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
14
+ logger.info(`Diffing screenshots against replay ${baseReplayId}`);
15
+ await (0, replays_1.getOrFetchReplay)(client, baseReplayId);
16
+ await (0, replays_1.getOrFetchReplayArchive)(client, baseReplayId);
17
+ const baseReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)((0, replays_1.getReplayDir)(baseReplayId));
18
+ const headReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)(tempDir);
19
+ const screenshotDiffResults = await (0, screenshot_diff_command_1.diffScreenshots)({
20
+ client,
21
+ baseReplayId,
22
+ headReplayId,
23
+ baseScreenshotsDir: baseReplayScreenshotsDir,
24
+ headScreenshotsDir: headReplayScreenshotsDir,
25
+ diffOptions: screenshottingOptions.diffOptions,
26
+ });
27
+ const replayDiff = await (0, replay_diff_api_1.createReplayDiff)({
28
+ client,
29
+ headReplayId,
30
+ baseReplayId,
31
+ testRunId,
32
+ data: {
33
+ screenshotAssertionsOptions: screenshottingOptions,
34
+ screenshotDiffResults,
35
+ },
36
+ });
37
+ logger.debug(replayDiff);
38
+ const screenshotDiffsSummary = (0, screenshot_diff_command_1.summarizeDifferences)({
39
+ baseReplayId,
40
+ headReplayId,
41
+ results: screenshotDiffResults,
42
+ diffOptions: screenshottingOptions.diffOptions,
43
+ });
44
+ return { screenshotDiffResults, screenshotDiffsSummary };
45
+ };
46
+ exports.computeAndSaveDiff = computeAndSaveDiff;
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runAllTestsCommand = void 0;
4
+ const client_1 = require("../../api/client");
5
+ const test_run_api_1 = require("../../api/test-run.api");
4
6
  const command_builder_1 = require("../../command-utils/command-builder");
5
7
  const common_options_1 = require("../../command-utils/common-options");
6
8
  const run_all_tests_1 = require("../../parallel-tests/run-all-tests");
7
- const handler = async ({ apiToken, commitSha, baseCommitSha, appUrl, useAssetsSnapshottedInBaseSimulation, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, padTime, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_, deflake, useCache, testsFile, disableRemoteFonts, noSandbox, skipPauses, moveBeforeClick, maxDurationMs, maxEventCount, storyboard, }) => {
9
+ const commit_sha_utils_1 = require("../../utils/commit-sha.utils");
10
+ const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl, useAssetsSnapshottedInBaseSimulation, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, padTime, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_, deflake, useCache, testsFile, disableRemoteFonts, noSandbox, skipPauses, moveBeforeClick, maxDurationMs, maxEventCount, storyboard, }) => {
8
11
  const executionOptions = {
9
12
  headless,
10
13
  devTools,
@@ -29,21 +32,26 @@ const handler = async ({ apiToken, commitSha, baseCommitSha, appUrl, useAssetsSn
29
32
  storyboardOptions,
30
33
  };
31
34
  const parrelelTasks = parallelize ? parrelelTasks_ : 1;
32
- const result = await (0, run_all_tests_1.runAllTests)({
35
+ const commitSha = (await (0, commit_sha_utils_1.getCommitSha)(commitSha_)) || "unknown";
36
+ const client = (0, client_1.createClient)({ apiToken });
37
+ const cachedTestRunResults = useCache
38
+ ? await (0, test_run_api_1.getCachedTestRunResults)({ client, commitSha })
39
+ : [];
40
+ const { testRun } = await (0, run_all_tests_1.runAllTests)({
33
41
  testsFile: testsFile !== null && testsFile !== void 0 ? testsFile : null,
34
42
  executionOptions,
35
43
  screenshottingOptions,
36
44
  apiToken: apiToken !== null && apiToken !== void 0 ? apiToken : null,
37
- commitSha: commitSha !== null && commitSha !== void 0 ? commitSha : null,
45
+ commitSha,
38
46
  baseCommitSha: baseCommitSha !== null && baseCommitSha !== void 0 ? baseCommitSha : null,
39
47
  appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
40
48
  useAssetsSnapshottedInBaseSimulation,
41
49
  parallelTasks: parrelelTasks !== null && parrelelTasks !== void 0 ? parrelelTasks : null,
42
50
  deflake,
43
- useCache,
51
+ cachedTestRunResults,
44
52
  githubSummary,
45
53
  });
46
- if (result.testRun.status === "Failure") {
54
+ if (testRun.status === "Failure") {
47
55
  process.exit(1);
48
56
  }
49
57
  };
@@ -9,21 +9,21 @@ export declare const diffScreenshots: ({ client, headReplayId, baseReplayId, hea
9
9
  headScreenshotsDir: string;
10
10
  diffOptions: ScreenshotDiffOptions;
11
11
  }) => Promise<ScreenshotDiffResult[]>;
12
- export declare const checkScreenshotDiffResult: ({ baseReplayId, headReplayId, results, diffOptions, }: {
12
+ export declare const summarizeDifferences: ({ baseReplayId, headReplayId, results, diffOptions, }: {
13
13
  baseReplayId: string;
14
14
  headReplayId: string;
15
15
  results: ScreenshotDiffResult[];
16
16
  diffOptions: ScreenshotDiffOptions;
17
- }) => void;
18
- export declare class ScreenshotDiffError extends Error {
19
- readonly extras?: {
20
- baseReplayId: string;
21
- headReplayId: string;
22
- } | undefined;
23
- constructor(message: string, extras?: {
24
- baseReplayId: string;
25
- headReplayId: string;
26
- } | undefined);
17
+ }) => ScreenshotDiffsSummary;
18
+ export declare type ScreenshotDiffsSummary = HasDiffsScreenshotDiffsResult | NoDiffsScreenshotDiffsResult;
19
+ export interface HasDiffsScreenshotDiffsResult {
20
+ hasDiffs: true;
21
+ summaryMessage: string;
22
+ baseReplayId: string;
23
+ headReplayId: string;
24
+ }
25
+ export interface NoDiffsScreenshotDiffsResult {
26
+ hasDiffs: false;
27
27
  }
28
28
  export declare const screenshotDiffCommand: import("yargs").CommandModule<unknown, import("yargs").InferredOptionTypes<{
29
29
  apiToken: {
@@ -3,7 +3,7 @@ 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.screenshotDiffCommand = exports.ScreenshotDiffError = exports.checkScreenshotDiffResult = exports.diffScreenshots = void 0;
6
+ exports.screenshotDiffCommand = exports.summarizeDifferences = exports.diffScreenshots = void 0;
7
7
  const path_1 = require("path");
8
8
  const common_1 = require("@alwaysmeticulous/common");
9
9
  const loglevel_1 = __importDefault(require("loglevel"));
@@ -99,7 +99,7 @@ const diffScreenshots = async ({ client, headReplayId, baseReplayId, headScreens
99
99
  return [...missingHeadImagesResults, ...headDiffResults];
100
100
  };
101
101
  exports.diffScreenshots = diffScreenshots;
102
- const checkScreenshotDiffResult = ({ baseReplayId, headReplayId, results, diffOptions, }) => {
102
+ const summarizeDifferences = ({ baseReplayId, headReplayId, results, diffOptions, }) => {
103
103
  const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
104
104
  const missingHeadImagesResults = results.flatMap((result) => result.outcome === "missing-head" ? [result] : []);
105
105
  if (missingHeadImagesResults.length) {
@@ -107,10 +107,12 @@ const checkScreenshotDiffResult = ({ baseReplayId, headReplayId, results, diffOp
107
107
  .map(({ baseScreenshotFile }) => (0, path_1.basename)(baseScreenshotFile))
108
108
  .sort()} => FAIL!`;
109
109
  logger.info(message);
110
- throw new ScreenshotDiffError(message, {
110
+ return {
111
+ hasDiffs: true,
112
+ summaryMessage: message,
111
113
  baseReplayId,
112
114
  headReplayId,
113
- });
115
+ };
114
116
  }
115
117
  const missingBaseImagesResults = results.flatMap((result) => result.outcome === "missing-base" ? [result] : []);
116
118
  if (missingHeadImagesResults.length) {
@@ -124,10 +126,12 @@ const checkScreenshotDiffResult = ({ baseReplayId, headReplayId, results, diffOp
124
126
  if (outcome === "different-size") {
125
127
  const message = `Screenshots ${(0, path_1.basename)(result.headScreenshotFile)} have different sizes => FAIL!`;
126
128
  logger.info(message);
127
- throw new ScreenshotDiffError(message, {
129
+ return {
130
+ hasDiffs: true,
131
+ summaryMessage: message,
128
132
  baseReplayId,
129
133
  headReplayId,
130
- });
134
+ };
131
135
  }
132
136
  if (outcome === "diff" || outcome === "no-diff") {
133
137
  const mismatch = (result.mismatchFraction * 100).toFixed(3);
@@ -135,22 +139,18 @@ const checkScreenshotDiffResult = ({ baseReplayId, headReplayId, results, diffOp
135
139
  const message = `${mismatch}% pixel mismatch for screenshot ${(0, path_1.basename)(result.headScreenshotFile)} (threshold is ${threshold}%) => ${outcome === "no-diff" ? "PASS" : "FAIL!"}`;
136
140
  logger.info(message);
137
141
  if (outcome === "diff") {
138
- throw new ScreenshotDiffError(message, {
142
+ return {
143
+ hasDiffs: true,
144
+ summaryMessage: message,
139
145
  baseReplayId,
140
146
  headReplayId,
141
- });
147
+ };
142
148
  }
143
149
  }
144
150
  });
151
+ return { hasDiffs: false };
145
152
  };
146
- exports.checkScreenshotDiffResult = checkScreenshotDiffResult;
147
- class ScreenshotDiffError extends Error {
148
- constructor(message, extras) {
149
- super(message);
150
- this.extras = extras;
151
- }
152
- }
153
- exports.ScreenshotDiffError = ScreenshotDiffError;
153
+ exports.summarizeDifferences = summarizeDifferences;
154
154
  const getScreenshotIdentifier = (filename) => {
155
155
  const name = (0, path_1.basename)(filename);
156
156
  if (name === "final-state.png") {
@@ -192,12 +192,15 @@ const handler = async ({ apiToken, baseSimulationId: baseReplayId, headSimulatio
192
192
  diffOptions,
193
193
  });
194
194
  logger.debug(results);
195
- (0, exports.checkScreenshotDiffResult)({
195
+ const diffSummary = (0, exports.summarizeDifferences)({
196
196
  baseReplayId,
197
197
  headReplayId,
198
198
  results,
199
199
  diffOptions,
200
200
  });
201
+ if (diffSummary.hasDiffs) {
202
+ process.exit(1);
203
+ }
201
204
  };
202
205
  exports.screenshotDiffCommand = (0, command_builder_1.buildCommand)("screenshot-diff")
203
206
  .details({ describe: "Diff two replay screenshots" })
@@ -1,3 +1,4 @@
1
+ import { ScreenshotDiffResult } from "@alwaysmeticulous/api";
1
2
  import { ScreenshotDiffOptions } from "../command-utils/common-types";
2
3
  export interface TestCaseReplayOptions extends Partial<ScreenshotDiffOptions> {
3
4
  appUrl?: string | null | undefined;
@@ -21,3 +22,6 @@ export interface TestCaseResult extends TestCase {
21
22
  headReplayId: string;
22
23
  result: "pass" | "fail";
23
24
  }
25
+ export interface DetailedTestCaseResult extends TestCaseResult {
26
+ screenshotDiffResults: ScreenshotDiffResult[];
27
+ }
@@ -1,6 +1,6 @@
1
1
  import { GeneratedBy, ReplayEventsDependencies, ReplayExecutionOptions, ReplayTarget } from "@alwaysmeticulous/common";
2
2
  import { ScreenshotAssertionsEnabledOptions } from "../command-utils/common-types";
3
- import { TestCase, TestCaseResult } from "../config/config.types";
3
+ import { DetailedTestCaseResult, TestCase } from "../config/config.types";
4
4
  export interface DeflakeReplayCommandHandlerOptions extends HandleReplayOptions {
5
5
  deflake: boolean;
6
6
  }
@@ -15,5 +15,5 @@ interface HandleReplayOptions {
15
15
  testRunId: string | null;
16
16
  replayEventsDependencies: ReplayEventsDependencies;
17
17
  }
18
- export declare const deflakeReplayCommandHandler: (options: DeflakeReplayCommandHandlerOptions) => Promise<TestCaseResult>;
18
+ export declare const deflakeReplayCommandHandler: ({ deflake, ...otherOptions }: DeflakeReplayCommandHandlerOptions) => Promise<DetailedTestCaseResult>;
19
19
  export {};
@@ -7,11 +7,9 @@ exports.deflakeReplayCommandHandler = void 0;
7
7
  const common_1 = require("@alwaysmeticulous/common");
8
8
  const loglevel_1 = __importDefault(require("loglevel"));
9
9
  const replay_command_1 = require("../commands/replay/replay.command");
10
- const screenshot_diff_command_1 = require("../commands/screenshot-diff/screenshot-diff.command");
11
10
  const handleReplay = async ({ testCase, replayTarget, executionOptions, screenshottingOptions, apiToken, commitSha, generatedBy, testRunId, replayEventsDependencies, }) => {
12
11
  var _a, _b;
13
- const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
14
- const replayPromise = (0, replay_command_1.replayCommandHandler)({
12
+ const { replay, screenshotDiffResults, screenshotDiffsSummary } = await (0, replay_command_1.replayCommandHandler)({
15
13
  replayTarget,
16
14
  executionOptions: applyTestCaseExecutionOptionOverrides(executionOptions, (_a = testCase.options) !== null && _a !== void 0 ? _a : {}),
17
15
  screenshottingOptions: applyTestCaseScreenshottingOptionsOverrides(screenshottingOptions, (_b = testCase.options) !== null && _b !== void 0 ? _b : {}),
@@ -20,30 +18,20 @@ const handleReplay = async ({ testCase, replayTarget, executionOptions, screensh
20
18
  sessionId: testCase.sessionId,
21
19
  baseSimulationId: testCase.baseReplayId,
22
20
  save: false,
23
- exitOnMismatch: false,
24
21
  cookiesFile: null,
25
22
  generatedBy,
26
23
  testRunId,
27
24
  replayEventsDependencies,
28
25
  });
29
- const result = await replayPromise
30
- .then((replay) => ({
26
+ if (screenshotDiffResults == null) {
27
+ throw new Error(`replayCommandHandler returned a null screenshotDiffResults, but was called with screenshottingOptions.enabled = true`);
28
+ }
29
+ return {
31
30
  ...testCase,
32
31
  headReplayId: replay.id,
33
- result: "pass",
34
- }))
35
- .catch((error) => {
36
- if (error instanceof screenshot_diff_command_1.ScreenshotDiffError && error.extras) {
37
- return {
38
- ...testCase,
39
- headReplayId: error.extras.headReplayId,
40
- result: "fail",
41
- };
42
- }
43
- logger.error(error);
44
- return { ...testCase, headReplayId: "", result: "fail" };
45
- });
46
- return result;
32
+ result: screenshotDiffsSummary.hasDiffs ? "pass" : "fail",
33
+ screenshotDiffResults,
34
+ };
47
35
  };
48
36
  const deflakeReplayCommandHandler = async ({ deflake, ...otherOptions }) => {
49
37
  const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
package/dist/index.d.ts CHANGED
@@ -9,4 +9,5 @@ export { runAllTestsCommand } from "./commands/run-all-tests/run-all-tests.comma
9
9
  export { screenshotDiffCommand } from "./commands/screenshot-diff/screenshot-diff.command";
10
10
  export { showProjectCommand } from "./commands/show-project/show-project.command";
11
11
  export { updateTestsCommand } from "./commands/update-tests/update-tests.command";
12
- export { runAllTests, TestRun, RunAllTestsResult, } from "./parallel-tests/run-all-tests";
12
+ export { runAllTests, RunAllTestsResult, TestRun, } from "./parallel-tests/run-all-tests";
13
+ export { initLogger, setLogLevel } from "./utils/logger.utils";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runAllTests = exports.updateTestsCommand = exports.showProjectCommand = exports.screenshotDiffCommand = exports.runAllTestsCommand = exports.replayCommand = exports.recordCommand = exports.downloadSessionCommand = exports.downloadReplayCommand = exports.debugReplayCommand = exports.createTestCommand = exports.bootstrapCommand = void 0;
3
+ exports.setLogLevel = exports.initLogger = exports.runAllTests = exports.updateTestsCommand = exports.showProjectCommand = exports.screenshotDiffCommand = exports.runAllTestsCommand = exports.replayCommand = exports.recordCommand = exports.downloadSessionCommand = exports.downloadReplayCommand = exports.debugReplayCommand = exports.createTestCommand = exports.bootstrapCommand = void 0;
4
4
  var bootstrap_command_1 = require("./commands/bootstrap/bootstrap.command");
5
5
  Object.defineProperty(exports, "bootstrapCommand", { enumerable: true, get: function () { return bootstrap_command_1.bootstrapCommand; } });
6
6
  var create_test_command_1 = require("./commands/create-test/create-test.command");
@@ -25,3 +25,6 @@ var update_tests_command_1 = require("./commands/update-tests/update-tests.comma
25
25
  Object.defineProperty(exports, "updateTestsCommand", { enumerable: true, get: function () { return update_tests_command_1.updateTestsCommand; } });
26
26
  var run_all_tests_1 = require("./parallel-tests/run-all-tests");
27
27
  Object.defineProperty(exports, "runAllTests", { enumerable: true, get: function () { return run_all_tests_1.runAllTests; } });
28
+ var logger_utils_1 = require("./utils/logger.utils");
29
+ Object.defineProperty(exports, "initLogger", { enumerable: true, get: function () { return logger_utils_1.initLogger; } });
30
+ Object.defineProperty(exports, "setLogLevel", { enumerable: true, get: function () { return logger_utils_1.setLogLevel; } });
@@ -1,5 +1,5 @@
1
1
  import log from "loglevel";
2
- import { TestCaseResult } from "../config/config.types";
2
+ import { DetailedTestCaseResult } from "../config/config.types";
3
3
  import { DeflakeReplayCommandHandlerOptions } from "../deflake-tests/deflake-tests.handler";
4
4
  export interface InitMessage {
5
5
  kind: "init";
@@ -12,6 +12,6 @@ export interface InitMessage {
12
12
  export interface ResultMessage {
13
13
  kind: "result";
14
14
  data: {
15
- result: TestCaseResult;
15
+ result: DetailedTestCaseResult;
16
16
  };
17
17
  }
@@ -1,28 +1,22 @@
1
1
  import { ReplayEventsDependencies, ReplayExecutionOptions } from "@alwaysmeticulous/common";
2
- import { AxiosInstance } from "axios";
3
2
  import { TestRun } from "../api/test-run.api";
4
3
  import { ScreenshotAssertionsEnabledOptions } from "../command-utils/common-types";
5
- import { MeticulousCliConfig, TestCaseResult } from "../config/config.types";
4
+ import { DetailedTestCaseResult, MeticulousCliConfig, TestCase } from "../config/config.types";
6
5
  import { TestRunProgress } from "./run-all-tests.types";
7
6
  export interface RunAllTestsInParallelOptions {
8
7
  config: MeticulousCliConfig;
9
- client: AxiosInstance;
10
8
  testRun: TestRun;
9
+ testsToRun: TestCase[];
11
10
  executionOptions: ReplayExecutionOptions;
12
11
  screenshottingOptions: ScreenshotAssertionsEnabledOptions;
13
12
  apiToken: string | null;
14
13
  commitSha: string;
15
- /**
16
- * The base commit to compare test results against for test cases that don't have a baseReplayId specified.
17
- */
18
- baseCommitSha: string | null;
19
14
  appUrl: string | null;
20
15
  useAssetsSnapshottedInBaseSimulation: boolean;
21
16
  parallelTasks: number | null;
22
17
  deflake: boolean;
23
- cachedTestRunResults: TestCaseResult[];
24
18
  replayEventsDependencies: ReplayEventsDependencies;
25
- onTestFinished?: (progress: TestRunProgress) => void;
19
+ onTestFinished?: (progress: TestRunProgress, resultsSoFar: DetailedTestCaseResult[]) => Promise<void>;
26
20
  }
27
21
  /** Handler for running Meticulous tests in parallel using child processes */
28
- export declare const runAllTestsInParallel: (options: RunAllTestsInParallelOptions) => Promise<TestCaseResult[]>;
22
+ export declare const runAllTestsInParallel: (options: RunAllTestsInParallelOptions) => Promise<DetailedTestCaseResult[]>;