@alwaysmeticulous/cli 2.39.0 → 2.40.4
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/test-run.api.d.ts +3 -31
- package/dist/api/test-run.api.js +3 -96
- package/dist/commands/download-replay/download-replay.command.js +4 -4
- package/dist/commands/download-session/download-session.command.js +4 -4
- package/dist/commands/record/record.command.js +8 -11
- package/dist/commands/replay/replay.command.d.ts +4 -20
- package/dist/commands/replay/replay.command.js +4 -239
- package/dist/commands/run-all-tests/run-all-tests.command.js +5 -6
- package/dist/commands/show-project/show-project.command.js +2 -3
- package/dist/index.d.ts +0 -7
- package/dist/index.js +1 -13
- package/dist/main.js +5 -11
- package/dist/utils/sentry.utils.d.ts +0 -4
- package/dist/utils/sentry.utils.js +3 -23
- package/package.json +14 -32
- package/dist/api/client.d.ts +0 -5
- package/dist/api/client.js +0 -32
- package/dist/api/download.d.ts +0 -1
- package/dist/api/download.js +0 -21
- package/dist/api/project.api.d.ts +0 -3
- package/dist/api/project.api.js +0 -22
- package/dist/api/replay-diff.api.d.ts +0 -13
- package/dist/api/replay-diff.api.js +0 -32
- package/dist/api/replay.api.d.ts +0 -36
- package/dist/api/replay.api.js +0 -89
- package/dist/api/session.api.d.ts +0 -6
- package/dist/api/session.api.js +0 -45
- package/dist/api/types.d.ts +0 -39
- package/dist/api/types.js +0 -2
- package/dist/api/upload.d.ts +0 -1
- package/dist/api/upload.js +0 -18
- package/dist/archive/archive.d.ts +0 -4
- package/dist/archive/archive.js +0 -64
- package/dist/command-utils/common-types.d.ts +0 -20
- package/dist/command-utils/common-types.js +0 -2
- package/dist/commands/bootstrap/bootstrap.command.d.ts +0 -2
- package/dist/commands/bootstrap/bootstrap.command.js +0 -33
- package/dist/commands/create-test/create-test.command.d.ts +0 -94
- package/dist/commands/create-test/create-test.command.js +0 -170
- package/dist/commands/replay/utils/compute-diff.d.ts +0 -13
- package/dist/commands/replay/utils/compute-diff.js +0 -95
- package/dist/commands/replay/utils/exit-early-if-skip-upload-env-var-set.d.ts +0 -1
- package/dist/commands/replay/utils/exit-early-if-skip-upload-env-var-set.js +0 -27
- package/dist/commands/screenshot-diff/screenshot-diff.command.d.ts +0 -53
- package/dist/commands/screenshot-diff/screenshot-diff.command.js +0 -182
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.d.ts +0 -2
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.js +0 -18
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.d.ts +0 -2
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.js +0 -24
- package/dist/commands/screenshot-diff/utils/has-notable-differences.d.ts +0 -2
- package/dist/commands/screenshot-diff/utils/has-notable-differences.js +0 -10
- package/dist/commands/serve/serve.command.d.ts +0 -10
- package/dist/commands/serve/serve.command.js +0 -31
- package/dist/commands/update-tests/update-tests.command.d.ts +0 -21
- package/dist/commands/update-tests/update-tests.command.js +0 -96
- package/dist/config/config.d.ts +0 -3
- package/dist/config/config.js +0 -70
- package/dist/config/config.types.d.ts +0 -16
- package/dist/config/config.types.js +0 -2
- package/dist/config/snippets.d.ts +0 -1
- package/dist/config/snippets.js +0 -18
- package/dist/errors/config.d.ts +0 -3
- package/dist/errors/config.js +0 -10
- package/dist/image/diff.utils.d.ts +0 -19
- package/dist/image/diff.utils.js +0 -25
- package/dist/image/io.utils.d.ts +0 -3
- package/dist/image/io.utils.js +0 -25
- package/dist/local-data/local-data.utils.d.ts +0 -20
- package/dist/local-data/local-data.utils.js +0 -92
- package/dist/local-data/replay-assets.d.ts +0 -3
- package/dist/local-data/replay-assets.js +0 -94
- package/dist/local-data/replays.d.ts +0 -18
- package/dist/local-data/replays.js +0 -87
- package/dist/local-data/screenshot-diffs.d.ts +0 -7
- package/dist/local-data/screenshot-diffs.js +0 -20
- package/dist/local-data/serve-assets-from-simulation.d.ts +0 -5
- package/dist/local-data/serve-assets-from-simulation.js +0 -52
- package/dist/local-data/sessions.d.ts +0 -10
- package/dist/local-data/sessions.js +0 -41
- package/dist/parallel-tests/__tests__/merge-test-results.spec.d.ts +0 -1
- package/dist/parallel-tests/__tests__/merge-test-results.spec.js +0 -104
- package/dist/parallel-tests/__tests__/mock-test-results.d.ts +0 -17
- package/dist/parallel-tests/__tests__/mock-test-results.js +0 -106
- package/dist/parallel-tests/__tests__/parrallel-tests.handler.spec.d.ts +0 -1
- package/dist/parallel-tests/__tests__/parrallel-tests.handler.spec.js +0 -205
- package/dist/parallel-tests/execute-test-in-child-process.d.ts +0 -3
- package/dist/parallel-tests/execute-test-in-child-process.js +0 -44
- package/dist/parallel-tests/merge-test-results.d.ts +0 -12
- package/dist/parallel-tests/merge-test-results.js +0 -122
- package/dist/parallel-tests/messages.types.d.ts +0 -17
- package/dist/parallel-tests/messages.types.js +0 -2
- package/dist/parallel-tests/parallel-replay.handler.d.ts +0 -3
- package/dist/parallel-tests/parallel-replay.handler.js +0 -57
- package/dist/parallel-tests/parallel-replay.types.d.ts +0 -16
- package/dist/parallel-tests/parallel-replay.types.js +0 -2
- package/dist/parallel-tests/parallel-tests.handler.d.ts +0 -14
- package/dist/parallel-tests/parallel-tests.handler.js +0 -194
- package/dist/parallel-tests/run-all-tests.d.ts +0 -73
- package/dist/parallel-tests/run-all-tests.js +0 -237
- package/dist/parallel-tests/run-all-tests.types.d.ts +0 -6
- package/dist/parallel-tests/run-all-tests.types.js +0 -2
- package/dist/parallel-tests/screenshot-diff-results.utils.d.ts +0 -7
- package/dist/parallel-tests/screenshot-diff-results.utils.js +0 -20
- package/dist/parallel-tests/task.handler.d.ts +0 -1
- package/dist/parallel-tests/task.handler.js +0 -86
- package/dist/utils/api-token.utils.d.ts +0 -1
- package/dist/utils/api-token.utils.js +0 -31
- package/dist/utils/commit-sha.utils.d.ts +0 -1
- package/dist/utils/commit-sha.utils.js +0 -42
- package/dist/utils/config.utils.d.ts +0 -7
- package/dist/utils/config.utils.js +0 -33
- package/dist/utils/github-summary.utils.d.ts +0 -5
- package/dist/utils/github-summary.utils.js +0 -38
- package/dist/utils/run-all-tests.utils.d.ts +0 -16
- package/dist/utils/run-all-tests.utils.js +0 -32
- package/dist/utils/test-run-environment.utils.d.ts +0 -2
- package/dist/utils/test-run-environment.utils.js +0 -25
- package/dist/utils/version.utils.d.ts +0 -1
- package/dist/utils/version.utils.js +0 -17
|
@@ -1,182 +0,0 @@
|
|
|
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.screenshotDiffCommand = exports.logDifferences = exports.diffScreenshots = void 0;
|
|
7
|
-
const path_1 = require("path");
|
|
8
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
9
|
-
const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify"));
|
|
10
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
11
|
-
const client_1 = require("../../api/client");
|
|
12
|
-
const command_builder_1 = require("../../command-utils/command-builder");
|
|
13
|
-
const common_options_1 = require("../../command-utils/common-options");
|
|
14
|
-
const diff_utils_1 = require("../../image/diff.utils");
|
|
15
|
-
const io_utils_1 = require("../../image/io.utils");
|
|
16
|
-
const replays_1 = require("../../local-data/replays");
|
|
17
|
-
const screenshot_diffs_1 = require("../../local-data/screenshot-diffs");
|
|
18
|
-
const has_notable_differences_1 = require("./utils/has-notable-differences");
|
|
19
|
-
const diffScreenshots = async ({ headReplayId, baseReplayId, baseScreenshotsDir, headScreenshotsDir, baseReplayScreenshots, headReplayScreenshots, diffOptions, }) => {
|
|
20
|
-
const { diffThreshold, diffPixelThreshold } = diffOptions;
|
|
21
|
-
const headReplayScreenshotsByIdentifier = new Map(headReplayScreenshots.map((screenshot) => [
|
|
22
|
-
(0, fast_json_stable_stringify_1.default)(screenshot.identifier),
|
|
23
|
-
screenshot,
|
|
24
|
-
]));
|
|
25
|
-
const baseReplayScreenshotsByIdentifier = new Map(baseReplayScreenshots.map((screenshot) => [
|
|
26
|
-
(0, fast_json_stable_stringify_1.default)(screenshot.identifier),
|
|
27
|
-
screenshot,
|
|
28
|
-
]));
|
|
29
|
-
const missingHeadImages = new Set(baseReplayScreenshots.filter((file) => !headReplayScreenshotsByIdentifier.has((0, fast_json_stable_stringify_1.default)(file.identifier))));
|
|
30
|
-
const missingHeadImagesResults = Array.from(missingHeadImages).flatMap(({ identifier, fileName }) => {
|
|
31
|
-
return [
|
|
32
|
-
{
|
|
33
|
-
identifier,
|
|
34
|
-
outcome: "missing-head",
|
|
35
|
-
baseScreenshotFile: `screenshots/${fileName}`,
|
|
36
|
-
},
|
|
37
|
-
];
|
|
38
|
-
});
|
|
39
|
-
const diffAgainstBase = async (headScreenshot) => {
|
|
40
|
-
const baseScreenshot = baseReplayScreenshotsByIdentifier.get((0, fast_json_stable_stringify_1.default)(headScreenshot.identifier));
|
|
41
|
-
if (baseScreenshot == null) {
|
|
42
|
-
return [
|
|
43
|
-
{
|
|
44
|
-
identifier: headScreenshot.identifier,
|
|
45
|
-
outcome: "missing-base",
|
|
46
|
-
headScreenshotFile: `screenshots/${headScreenshot.fileName}`,
|
|
47
|
-
},
|
|
48
|
-
];
|
|
49
|
-
}
|
|
50
|
-
const baseScreenshotFile = (0, path_1.join)(baseScreenshotsDir, baseScreenshot.fileName);
|
|
51
|
-
const headScreenshotFile = (0, path_1.join)(headScreenshotsDir, headScreenshot.fileName);
|
|
52
|
-
const baseScreenshotContents = await (0, io_utils_1.readPng)(baseScreenshotFile);
|
|
53
|
-
const headScreenshotContents = await (0, io_utils_1.readPng)(headScreenshotFile);
|
|
54
|
-
if (baseScreenshotContents.width !== headScreenshotContents.width ||
|
|
55
|
-
baseScreenshotContents.height !== headScreenshotContents.height) {
|
|
56
|
-
return [
|
|
57
|
-
{
|
|
58
|
-
identifier: headScreenshot.identifier,
|
|
59
|
-
outcome: "different-size",
|
|
60
|
-
baseScreenshotFile: `screenshots/${baseScreenshot.fileName}`,
|
|
61
|
-
headScreenshotFile: `screenshots/${headScreenshot.fileName}`,
|
|
62
|
-
baseWidth: baseScreenshotContents.width,
|
|
63
|
-
baseHeight: baseScreenshotContents.height,
|
|
64
|
-
headWidth: headScreenshotContents.width,
|
|
65
|
-
headHeight: headScreenshotContents.height,
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
}
|
|
69
|
-
const comparisonResult = (0, diff_utils_1.compareImages)({
|
|
70
|
-
base: baseScreenshotContents,
|
|
71
|
-
head: headScreenshotContents,
|
|
72
|
-
pixelThreshold: diffPixelThreshold,
|
|
73
|
-
});
|
|
74
|
-
await (0, screenshot_diffs_1.writeScreenshotDiff)({
|
|
75
|
-
baseReplayId,
|
|
76
|
-
headReplayId,
|
|
77
|
-
screenshotFileName: headScreenshot.fileName,
|
|
78
|
-
diff: comparisonResult.diff,
|
|
79
|
-
});
|
|
80
|
-
return [
|
|
81
|
-
{
|
|
82
|
-
identifier: headScreenshot.identifier,
|
|
83
|
-
outcome: comparisonResult.mismatchFraction > diffThreshold
|
|
84
|
-
? "diff"
|
|
85
|
-
: "no-diff",
|
|
86
|
-
baseScreenshotFile: `screenshots/${baseScreenshot.fileName}`,
|
|
87
|
-
headScreenshotFile: `screenshots/${headScreenshot.fileName}`,
|
|
88
|
-
width: baseScreenshotContents.width,
|
|
89
|
-
height: baseScreenshotContents.height,
|
|
90
|
-
mismatchPixels: comparisonResult.mismatchPixels,
|
|
91
|
-
mismatchFraction: comparisonResult.mismatchFraction,
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
};
|
|
95
|
-
const headDiffResults = (await Promise.all(headReplayScreenshots.map(diffAgainstBase))).flat();
|
|
96
|
-
return [...missingHeadImagesResults, ...headDiffResults];
|
|
97
|
-
};
|
|
98
|
-
exports.diffScreenshots = diffScreenshots;
|
|
99
|
-
const logDifferences = ({ results, diffOptions, logger, }) => {
|
|
100
|
-
const missingHeadImagesResults = results.flatMap((result) => result.outcome === "missing-head" ? [result] : []);
|
|
101
|
-
if (missingHeadImagesResults.length) {
|
|
102
|
-
const message = `Head replay is missing screenshots: ${missingHeadImagesResults
|
|
103
|
-
.map(({ baseScreenshotFile }) => (0, path_1.basename)(baseScreenshotFile))
|
|
104
|
-
.sort()} => FAIL!`;
|
|
105
|
-
logger.info(message);
|
|
106
|
-
}
|
|
107
|
-
const missingBaseImagesResults = results.flatMap((result) => result.outcome === "missing-base" ? [result] : []);
|
|
108
|
-
if (missingHeadImagesResults.length) {
|
|
109
|
-
const message = `Notice: Base replay is missing screenshots: ${missingBaseImagesResults
|
|
110
|
-
.map(({ headScreenshotFile }) => (0, path_1.basename)(headScreenshotFile))
|
|
111
|
-
.sort()}`;
|
|
112
|
-
logger.info(message);
|
|
113
|
-
}
|
|
114
|
-
results.forEach((result) => {
|
|
115
|
-
const { outcome } = result;
|
|
116
|
-
if (outcome === "different-size") {
|
|
117
|
-
const message = `Screenshots ${(0, path_1.basename)(result.headScreenshotFile)} have different sizes => FAIL!`;
|
|
118
|
-
logger.info(message);
|
|
119
|
-
}
|
|
120
|
-
if (outcome === "diff" || outcome === "no-diff") {
|
|
121
|
-
const mismatch = (result.mismatchFraction * 100).toFixed(3);
|
|
122
|
-
const threshold = (diffOptions.diffThreshold * 100).toFixed(3);
|
|
123
|
-
const message = `${mismatch}% pixel mismatch for screenshot ${(0, path_1.basename)(result.headScreenshotFile)} (threshold is ${threshold}%) => ${outcome === "no-diff" ? "PASS" : "FAIL!"}`;
|
|
124
|
-
logger.info(message);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
exports.logDifferences = logDifferences;
|
|
129
|
-
const handler = async ({ apiToken, baseSimulationId: baseReplayId, headSimulationId: headReplayId, threshold, pixelThreshold, }) => {
|
|
130
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
131
|
-
const client = (0, client_1.createClient)({ apiToken });
|
|
132
|
-
await (0, replays_1.getOrFetchReplay)(client, baseReplayId);
|
|
133
|
-
await (0, replays_1.getOrFetchReplayArchive)(client, baseReplayId);
|
|
134
|
-
await (0, replays_1.getOrFetchReplay)(client, headReplayId);
|
|
135
|
-
await (0, replays_1.getOrFetchReplayArchive)(client, headReplayId);
|
|
136
|
-
const baseScreenshotsDir = (0, replays_1.getScreenshotsDir)((0, replays_1.getReplayDir)(baseReplayId));
|
|
137
|
-
const headScreenshotsDir = (0, replays_1.getScreenshotsDir)((0, replays_1.getReplayDir)(headReplayId));
|
|
138
|
-
const diffOptions = {
|
|
139
|
-
diffThreshold: threshold,
|
|
140
|
-
diffPixelThreshold: pixelThreshold,
|
|
141
|
-
};
|
|
142
|
-
const baseReplayScreenshots = await (0, replays_1.getScreenshotFiles)(baseScreenshotsDir);
|
|
143
|
-
const headReplayScreenshots = await (0, replays_1.getScreenshotFiles)(headScreenshotsDir);
|
|
144
|
-
const results = await (0, exports.diffScreenshots)({
|
|
145
|
-
baseReplayId,
|
|
146
|
-
headReplayId,
|
|
147
|
-
baseScreenshotsDir,
|
|
148
|
-
headScreenshotsDir,
|
|
149
|
-
baseReplayScreenshots,
|
|
150
|
-
headReplayScreenshots,
|
|
151
|
-
diffOptions,
|
|
152
|
-
});
|
|
153
|
-
logger.debug(results);
|
|
154
|
-
(0, exports.logDifferences)({
|
|
155
|
-
results,
|
|
156
|
-
diffOptions,
|
|
157
|
-
logger,
|
|
158
|
-
});
|
|
159
|
-
if ((0, has_notable_differences_1.hasNotableDifferences)(results)) {
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
exports.screenshotDiffCommand = (0, command_builder_1.buildCommand)("screenshot-diff")
|
|
164
|
-
.details({ describe: "Diff two replay screenshots" })
|
|
165
|
-
.options({
|
|
166
|
-
apiToken: {
|
|
167
|
-
string: true,
|
|
168
|
-
},
|
|
169
|
-
baseSimulationId: {
|
|
170
|
-
string: true,
|
|
171
|
-
demandOption: true,
|
|
172
|
-
alias: "baseReplayId",
|
|
173
|
-
},
|
|
174
|
-
headSimulationId: {
|
|
175
|
-
string: true,
|
|
176
|
-
demandOption: true,
|
|
177
|
-
alias: "headReplayId",
|
|
178
|
-
},
|
|
179
|
-
threshold: common_options_1.SCREENSHOT_DIFF_OPTIONS.diffThreshold,
|
|
180
|
-
pixelThreshold: common_options_1.SCREENSHOT_DIFF_OPTIONS.diffPixelThreshold,
|
|
181
|
-
})
|
|
182
|
-
.handler(handler);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getScreenshotFilename = void 0;
|
|
4
|
-
// Note: ideally this should match the filenames produced by the screenshotting code
|
|
5
|
-
// in replay-node, and in https://github.com/alwaysmeticulous/meticulous-sdk/blob/395af4394dc51d9b51ba1136fc26b23fcbba5604/packages/replayer/src/screenshot.utils.ts#L42
|
|
6
|
-
const getScreenshotFilename = (identifier) => {
|
|
7
|
-
if (identifier.type === "end-state") {
|
|
8
|
-
return "final-state.png";
|
|
9
|
-
}
|
|
10
|
-
else if (identifier.type === "after-event") {
|
|
11
|
-
const eventIndexStr = identifier.eventNumber.toString().padStart(5, "0");
|
|
12
|
-
return `screenshot-after-event-${eventIndexStr}.png`;
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
throw new Error("Unexpected screenshot identifier: " + JSON.stringify(identifier));
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
exports.getScreenshotFilename = getScreenshotFilename;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getScreenshotIdentifier = void 0;
|
|
4
|
-
const path_1 = require("path");
|
|
5
|
-
const getScreenshotIdentifier = (filename) => {
|
|
6
|
-
const name = (0, path_1.basename)(filename);
|
|
7
|
-
if (name === "final-state.png") {
|
|
8
|
-
return {
|
|
9
|
-
type: "end-state",
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
if (name.startsWith("screenshot-after-event")) {
|
|
13
|
-
const match = name.match(/^(?:.*)-(\d+)[.]png$/);
|
|
14
|
-
const eventNumber = match ? parseInt(match[1], 10) : undefined;
|
|
15
|
-
if (match && eventNumber != null && !isNaN(eventNumber)) {
|
|
16
|
-
return {
|
|
17
|
-
type: "after-event",
|
|
18
|
-
eventNumber,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
};
|
|
24
|
-
exports.getScreenshotIdentifier = getScreenshotIdentifier;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasNotableDifferences = void 0;
|
|
4
|
-
const hasNotableDifferences = (screenshotDiffResults) => {
|
|
5
|
-
// Note: we ignore flakes & missing-bases here
|
|
6
|
-
return screenshotDiffResults.some((diff) => diff.outcome === "diff" ||
|
|
7
|
-
diff.outcome === "missing-head" ||
|
|
8
|
-
diff.outcome === "different-size");
|
|
9
|
-
};
|
|
10
|
-
exports.hasNotableDifferences = hasNotableDifferences;
|
|
@@ -1,31 +0,0 @@
|
|
|
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.serveCommand = void 0;
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
9
|
-
const client_1 = require("../../api/client");
|
|
10
|
-
const command_builder_1 = require("../../command-utils/command-builder");
|
|
11
|
-
const serve_assets_from_simulation_1 = require("../../local-data/serve-assets-from-simulation");
|
|
12
|
-
const handler = async ({ apiToken, replayId, }) => {
|
|
13
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
14
|
-
const client = (0, client_1.createClient)({ apiToken });
|
|
15
|
-
const { url } = await (0, serve_assets_from_simulation_1.serveAssetsFromSimulation)(client, replayId);
|
|
16
|
-
logger.info(`Serving assets at url ${url}`);
|
|
17
|
-
};
|
|
18
|
-
exports.serveCommand = (0, command_builder_1.buildCommand)("serve")
|
|
19
|
-
.details({
|
|
20
|
-
describe: "Spin up a localhost server to serve the assets that were snapshotted when running a particular replay",
|
|
21
|
-
})
|
|
22
|
-
.options({
|
|
23
|
-
apiToken: {
|
|
24
|
-
string: true,
|
|
25
|
-
},
|
|
26
|
-
replayId: {
|
|
27
|
-
string: true,
|
|
28
|
-
demandOption: true,
|
|
29
|
-
},
|
|
30
|
-
})
|
|
31
|
-
.handler(handler);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/// <reference types="yargs" />
|
|
2
|
-
export declare const updateTestsCommand: import("yargs").CommandModule<unknown, import("yargs").InferredOptionTypes<{
|
|
3
|
-
apiToken: {
|
|
4
|
-
string: true;
|
|
5
|
-
};
|
|
6
|
-
testRunId: {
|
|
7
|
-
string: true;
|
|
8
|
-
demandOption: true;
|
|
9
|
-
description: string;
|
|
10
|
-
};
|
|
11
|
-
accept: {
|
|
12
|
-
string: true;
|
|
13
|
-
array: true;
|
|
14
|
-
description: string;
|
|
15
|
-
};
|
|
16
|
-
acceptAll: {
|
|
17
|
-
boolean: true;
|
|
18
|
-
description: string;
|
|
19
|
-
conflicts: string;
|
|
20
|
-
};
|
|
21
|
-
}>>;
|
|
@@ -1,96 +0,0 @@
|
|
|
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.updateTestsCommand = void 0;
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
9
|
-
const client_1 = require("../../api/client");
|
|
10
|
-
const test_run_api_1 = require("../../api/test-run.api");
|
|
11
|
-
const command_builder_1 = require("../../command-utils/command-builder");
|
|
12
|
-
const config_1 = require("../../config/config");
|
|
13
|
-
const handler = async ({ apiToken, testRunId, accept: accept_, acceptAll: acceptAll_, }) => {
|
|
14
|
-
var _a;
|
|
15
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
16
|
-
const client = (0, client_1.createClient)({ apiToken });
|
|
17
|
-
const config = await (0, config_1.readConfig)();
|
|
18
|
-
const testCases = config.testCases || [];
|
|
19
|
-
if (!testCases.length) {
|
|
20
|
-
logger.error("Error! No test case defined");
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
const testRun = await (0, test_run_api_1.getTestRun)({ client, testRunId });
|
|
24
|
-
if (!testRun) {
|
|
25
|
-
logger.error("Error: Could not retrieve test run. Is the API token correct?");
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
if (testRun.status === "Success") {
|
|
29
|
-
logger.info("Test run successful, no tests to updates");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (testRun.status === "Running") {
|
|
33
|
-
logger.error("Error: Tests are still running");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
const testRunResults = ((_a = testRun.resultData) === null || _a === void 0 ? void 0 : _a.results) || [];
|
|
37
|
-
const accept = accept_ || [];
|
|
38
|
-
const acceptAll = acceptAll_ || false;
|
|
39
|
-
if (!acceptAll && !accept.length) {
|
|
40
|
-
throw new Error("One of --accept or --acceptAll needs to be specified.");
|
|
41
|
-
}
|
|
42
|
-
const testRunResultsBySessionId = new Map();
|
|
43
|
-
testRunResults.forEach((result) => {
|
|
44
|
-
testRunResultsBySessionId.set(result.sessionId, result);
|
|
45
|
-
});
|
|
46
|
-
const newTestCases = testCases.map((testCase) => {
|
|
47
|
-
const { sessionId } = testCase;
|
|
48
|
-
const matched = testRunResultsBySessionId.get(sessionId);
|
|
49
|
-
if (!matched) {
|
|
50
|
-
logger.warn(`WARNING: ${sessionId} not found in test run`);
|
|
51
|
-
return testCase;
|
|
52
|
-
}
|
|
53
|
-
if (matched.result === "pass" ||
|
|
54
|
-
(!acceptAll &&
|
|
55
|
-
!accept.find((replayId) => replayId === matched.headReplayId))) {
|
|
56
|
-
return testCase;
|
|
57
|
-
}
|
|
58
|
-
const headReplayId = matched.headReplayId || "";
|
|
59
|
-
if (!headReplayId) {
|
|
60
|
-
logger.warn(`WARNING: ${sessionId} has no new replay id`);
|
|
61
|
-
return testCase;
|
|
62
|
-
}
|
|
63
|
-
const newTestCase = { ...testCase, baseReplayId: headReplayId };
|
|
64
|
-
return newTestCase;
|
|
65
|
-
});
|
|
66
|
-
const newConfig = {
|
|
67
|
-
...config,
|
|
68
|
-
testCases: newTestCases,
|
|
69
|
-
};
|
|
70
|
-
await (0, config_1.saveConfig)(newConfig);
|
|
71
|
-
};
|
|
72
|
-
exports.updateTestsCommand = (0, command_builder_1.buildCommand)("update-tests")
|
|
73
|
-
.details({
|
|
74
|
-
describe: "Updates test cases",
|
|
75
|
-
})
|
|
76
|
-
.options({
|
|
77
|
-
apiToken: {
|
|
78
|
-
string: true,
|
|
79
|
-
},
|
|
80
|
-
testRunId: {
|
|
81
|
-
string: true,
|
|
82
|
-
demandOption: true,
|
|
83
|
-
description: "Test run id to fix",
|
|
84
|
-
},
|
|
85
|
-
accept: {
|
|
86
|
-
string: true,
|
|
87
|
-
array: true,
|
|
88
|
-
description: "Replay ids to accept as valid",
|
|
89
|
-
},
|
|
90
|
-
acceptAll: {
|
|
91
|
-
boolean: true,
|
|
92
|
-
description: "Accept all failing tests as valid",
|
|
93
|
-
conflicts: "accept",
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
.handler(handler);
|
package/dist/config/config.d.ts
DELETED
package/dist/config/config.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.saveConfig = exports.readConfig = void 0;
|
|
4
|
-
const promises_1 = require("fs/promises");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const process_1 = require("process");
|
|
7
|
-
const cosmiconfig_1 = require("cosmiconfig");
|
|
8
|
-
const METICULOUS_CONFIG_FILE = "meticulous.json";
|
|
9
|
-
const findConfig = async () => {
|
|
10
|
-
const explorer = (0, cosmiconfig_1.cosmiconfig)("meticulous", {
|
|
11
|
-
searchPlaces: [METICULOUS_CONFIG_FILE],
|
|
12
|
-
});
|
|
13
|
-
const loaded = await explorer.search((0, process_1.cwd)());
|
|
14
|
-
return (loaded === null || loaded === void 0 ? void 0 : loaded.filepath) || "";
|
|
15
|
-
};
|
|
16
|
-
let configFilePath = "";
|
|
17
|
-
const getConfigFilePath = async () => {
|
|
18
|
-
if (configFilePath) {
|
|
19
|
-
return configFilePath;
|
|
20
|
-
}
|
|
21
|
-
configFilePath = (await findConfig()) || (0, path_1.join)((0, process_1.cwd)(), METICULOUS_CONFIG_FILE);
|
|
22
|
-
return configFilePath;
|
|
23
|
-
};
|
|
24
|
-
const validateReplayOptions = (prevOptions) => {
|
|
25
|
-
const { appUrl, screenshotSelector, diffThreshold, diffPixelThreshold, moveBeforeClick, simulationIdForAssets, } = prevOptions;
|
|
26
|
-
return {
|
|
27
|
-
...(appUrl != null ? { appUrl } : {}),
|
|
28
|
-
...(screenshotSelector != null ? { screenshotSelector } : {}),
|
|
29
|
-
...(diffThreshold != null ? { diffThreshold } : {}),
|
|
30
|
-
...(diffPixelThreshold != null ? { diffPixelThreshold } : {}),
|
|
31
|
-
...(moveBeforeClick != null ? { moveBeforeClick } : {}),
|
|
32
|
-
...(simulationIdForAssets != null ? { simulationIdForAssets } : {}),
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
const validateConfig = (prevConfig) => {
|
|
36
|
-
const { testCases, ...rest } = prevConfig;
|
|
37
|
-
const nextTestCases = (testCases || [])
|
|
38
|
-
.map(({ title, sessionId, options }) => ({
|
|
39
|
-
title: typeof title === "string" ? title : "",
|
|
40
|
-
sessionId: typeof sessionId === "string" ? sessionId : "",
|
|
41
|
-
...(options ? { options: validateReplayOptions(options) } : {}),
|
|
42
|
-
}))
|
|
43
|
-
.map(({ title, ...rest }) => ({
|
|
44
|
-
title: title || `${rest.sessionId}`,
|
|
45
|
-
...rest,
|
|
46
|
-
}))
|
|
47
|
-
.filter(({ sessionId }) => sessionId);
|
|
48
|
-
return { ...rest, testCases: nextTestCases };
|
|
49
|
-
};
|
|
50
|
-
const readConfig = async (configFilePath) => {
|
|
51
|
-
const filePath = configFilePath !== null && configFilePath !== void 0 ? configFilePath : (await getConfigFilePath());
|
|
52
|
-
const configStr = await (0, promises_1.readFile)(filePath, "utf-8").catch((error) => {
|
|
53
|
-
// Use an empty config object if there is no config file
|
|
54
|
-
if (configFilePath == null &&
|
|
55
|
-
error instanceof Error &&
|
|
56
|
-
error.code === "ENOENT") {
|
|
57
|
-
return "{}";
|
|
58
|
-
}
|
|
59
|
-
throw error;
|
|
60
|
-
});
|
|
61
|
-
const config = JSON.parse(configStr);
|
|
62
|
-
return validateConfig(config);
|
|
63
|
-
};
|
|
64
|
-
exports.readConfig = readConfig;
|
|
65
|
-
const saveConfig = async (config) => {
|
|
66
|
-
const filePath = await getConfigFilePath();
|
|
67
|
-
const configStr = JSON.stringify(config, null, 2);
|
|
68
|
-
await (0, promises_1.writeFile)(filePath, configStr);
|
|
69
|
-
};
|
|
70
|
-
exports.saveConfig = saveConfig;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ScreenshotDiffResult, TestCase } from "@alwaysmeticulous/api";
|
|
2
|
-
export interface MeticulousCliConfig {
|
|
3
|
-
testCases?: TestCase[];
|
|
4
|
-
}
|
|
5
|
-
export interface TestCaseResult extends TestCase {
|
|
6
|
-
headReplayId: string;
|
|
7
|
-
/**
|
|
8
|
-
* A test case is marked as a flake if there were screenshot comparison failures,
|
|
9
|
-
* but for every one of those failures regenerating the screenshot on head sometimes gave
|
|
10
|
-
* a different screenshot to the original screenshot taken on head.
|
|
11
|
-
*/
|
|
12
|
-
result: "pass" | "fail" | "flake";
|
|
13
|
-
}
|
|
14
|
-
export interface DetailedTestCaseResult extends TestCaseResult {
|
|
15
|
-
screenshotDiffResultsByBaseReplayId: Record<string, ScreenshotDiffResult[]>;
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const getSnippetsBaseUrl: () => string;
|
package/dist/config/snippets.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSnippetsBaseUrl = void 0;
|
|
4
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
5
|
-
const config_1 = require("../errors/config");
|
|
6
|
-
const getSnippetsBaseUrl = () => {
|
|
7
|
-
const baseUrl = process.env["METICULOUS_SNIPPETS_BASE_URL"] || common_1.BASE_SNIPPETS_URL;
|
|
8
|
-
try {
|
|
9
|
-
return new URL(baseUrl).href;
|
|
10
|
-
}
|
|
11
|
-
catch (e) {
|
|
12
|
-
if (e instanceof TypeError) {
|
|
13
|
-
throw new config_1.ConfigurationError(`Invalid base snippets URL: ${baseUrl}`);
|
|
14
|
-
}
|
|
15
|
-
throw e;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
exports.getSnippetsBaseUrl = getSnippetsBaseUrl;
|
package/dist/errors/config.d.ts
DELETED
package/dist/errors/config.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ConfigurationError = void 0;
|
|
4
|
-
class ConfigurationError extends Error {
|
|
5
|
-
constructor(message) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.name = "ConfigurationError";
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
exports.ConfigurationError = ConfigurationError;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { PNG } from "pngjs";
|
|
2
|
-
export interface CompareImageOptions {
|
|
3
|
-
base: PNG;
|
|
4
|
-
head: PNG;
|
|
5
|
-
/**
|
|
6
|
-
* Maximum colour distance a given pixel is allowed to differ before counting two
|
|
7
|
-
* pixels as different.
|
|
8
|
-
*
|
|
9
|
-
* Measure is based on "Measuring perceived color difference using YIQ NTSC transmission color space
|
|
10
|
-
* in mobile applications" by Y. Kotsarenko and F. Ramos
|
|
11
|
-
*/
|
|
12
|
-
pixelThreshold: number;
|
|
13
|
-
}
|
|
14
|
-
export interface CompareImageResult {
|
|
15
|
-
mismatchPixels: number;
|
|
16
|
-
mismatchFraction: number;
|
|
17
|
-
diff: PNG;
|
|
18
|
-
}
|
|
19
|
-
export declare const compareImages: (options: CompareImageOptions) => CompareImageResult;
|
package/dist/image/diff.utils.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
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.compareImages = void 0;
|
|
7
|
-
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
8
|
-
const pngjs_1 = require("pngjs");
|
|
9
|
-
const compareImages = ({ base, head, pixelThreshold }) => {
|
|
10
|
-
if (base.width !== head.width || base.height !== head.height) {
|
|
11
|
-
throw new Error("Cannot handle different size yet");
|
|
12
|
-
}
|
|
13
|
-
const { width, height } = base;
|
|
14
|
-
const diff = new pngjs_1.PNG({ width, height });
|
|
15
|
-
const mismatchPixels = (0, pixelmatch_1.default)(base.data, head.data, diff.data, width, height, {
|
|
16
|
-
threshold: pixelThreshold,
|
|
17
|
-
});
|
|
18
|
-
const mismatchFraction = mismatchPixels / (width * height);
|
|
19
|
-
return {
|
|
20
|
-
mismatchPixels,
|
|
21
|
-
mismatchFraction,
|
|
22
|
-
diff,
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
exports.compareImages = compareImages;
|
package/dist/image/io.utils.d.ts
DELETED
package/dist/image/io.utils.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.writePng = exports.readPng = void 0;
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const pngjs_1 = require("pngjs");
|
|
6
|
-
const readPng = async (path) => {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const png = new pngjs_1.PNG();
|
|
9
|
-
(0, fs_1.createReadStream)(path)
|
|
10
|
-
.pipe(png)
|
|
11
|
-
.on("parsed", () => {
|
|
12
|
-
resolve(png);
|
|
13
|
-
})
|
|
14
|
-
.on("error", (error) => reject(error));
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
exports.readPng = readPng;
|
|
18
|
-
const writePng = async (png, path) => {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const str = (0, fs_1.createWriteStream)(path);
|
|
21
|
-
png.pack().pipe(str);
|
|
22
|
-
str.on("close", () => resolve()).on("error", (error) => reject(error));
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
exports.writePng = writePng;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const sanitizeFilename: (filename: string) => string;
|
|
2
|
-
type ReleaseLock = () => Promise<void>;
|
|
3
|
-
export interface LoadOrDownloadJsonFileOptions<T> {
|
|
4
|
-
filePath: string;
|
|
5
|
-
downloadJson: () => Promise<T | null>;
|
|
6
|
-
/**
|
|
7
|
-
* For debug messages e.g. 'session' or 'session data'
|
|
8
|
-
*/
|
|
9
|
-
dataDescription: string;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Returns the JSON.parse'd contents of the file at the given path. If the file
|
|
13
|
-
* doesn't exist yet then it downloads the object, writes it to the file, and returns it.
|
|
14
|
-
*
|
|
15
|
-
* Handles concurrent processes trying to download to the same file at the same time.
|
|
16
|
-
*/
|
|
17
|
-
export declare const getOrDownloadJsonFile: <T>({ filePath, downloadJson, dataDescription, }: LoadOrDownloadJsonFileOptions<T>) => Promise<T | null>;
|
|
18
|
-
export declare const fileExists: (filePath: string) => Promise<boolean>;
|
|
19
|
-
export declare const waitToAcquireLockOnDirectory: (directoryPath: string) => Promise<ReleaseLock>;
|
|
20
|
-
export {};
|