@alwaysmeticulous/cli 2.40.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 +3 -14
- package/dist/commands/replay/replay.command.js +3 -233
- package/dist/commands/run-all-tests/run-all-tests.command.js +5 -5
- 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 -90
- package/dist/commands/create-test/create-test.command.js +0 -165
- 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/__tests__/get-screenshot-identifier.spec.d.ts +0 -1
- package/dist/commands/screenshot-diff/utils/__tests__/get-screenshot-identifier.spec.js +0 -25
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.d.ts +0 -2
- package/dist/commands/screenshot-diff/utils/get-screenshot-filename.js +0 -22
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.d.ts +0 -2
- package/dist/commands/screenshot-diff/utils/get-screenshot-identifier.js +0 -39
- 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 -69
- 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 -106
- 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 -56
- 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,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const get_screenshot_identifier_1 = require("../get-screenshot-identifier");
|
|
4
|
-
describe("get-screenshot-identifier", () => {
|
|
5
|
-
it("can parse identifiers without version numbers", () => {
|
|
6
|
-
expect((0, get_screenshot_identifier_1.getScreenshotIdentifier)("final-state.png")).toEqual({
|
|
7
|
-
type: "end-state",
|
|
8
|
-
});
|
|
9
|
-
expect((0, get_screenshot_identifier_1.getScreenshotIdentifier)("screenshot-after-event-7.png")).toEqual({
|
|
10
|
-
type: "after-event",
|
|
11
|
-
eventNumber: 7,
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
it("can parse identifiers with version numbers", () => {
|
|
15
|
-
expect((0, get_screenshot_identifier_1.getScreenshotIdentifier)("final-state-v2.png")).toEqual({
|
|
16
|
-
type: "end-state",
|
|
17
|
-
logicVersion: 2,
|
|
18
|
-
});
|
|
19
|
-
expect((0, get_screenshot_identifier_1.getScreenshotIdentifier)("screenshot-after-event-7-v2.png")).toEqual({
|
|
20
|
-
type: "after-event",
|
|
21
|
-
eventNumber: 7,
|
|
22
|
-
logicVersion: 2,
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1,22 +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 identifier.logicVersion == null
|
|
9
|
-
? "final-state.png"
|
|
10
|
-
: `final-state-v${identifier.logicVersion}.png`;
|
|
11
|
-
}
|
|
12
|
-
else if (identifier.type === "after-event") {
|
|
13
|
-
const eventIndexStr = identifier.eventNumber.toString().padStart(5, "0");
|
|
14
|
-
return identifier.logicVersion == null
|
|
15
|
-
? `screenshot-after-event-${eventIndexStr}.png`
|
|
16
|
-
: `screenshot-after-event-${eventIndexStr}-v${identifier.logicVersion}.png`;
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
throw new Error("Unexpected screenshot identifier: " + JSON.stringify(identifier));
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
exports.getScreenshotFilename = getScreenshotFilename;
|
|
@@ -1,39 +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
|
-
var _a;
|
|
7
|
-
const name = (0, path_1.basename)(filename);
|
|
8
|
-
if (name === "final-state.png") {
|
|
9
|
-
return {
|
|
10
|
-
type: "end-state",
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
if (name.startsWith("final-state-v")) {
|
|
14
|
-
const match = name.match(/^final-state-v(\d+)[.]png$/);
|
|
15
|
-
const logicVersionNumber = match ? parseInt(match[1], 10) : undefined;
|
|
16
|
-
if (match && logicVersionNumber != null && !isNaN(logicVersionNumber)) {
|
|
17
|
-
return {
|
|
18
|
-
type: "end-state",
|
|
19
|
-
logicVersion: logicVersionNumber,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (name.startsWith("screenshot-after-event")) {
|
|
24
|
-
const match = (_a = name.match(/^(?:.*)-(\d+)-v(\d+)?[.]png$/)) !== null && _a !== void 0 ? _a : name.match(/^(?:.*)-(\d+)[.]png$/);
|
|
25
|
-
const eventNumber = match ? parseInt(match[1], 10) : undefined;
|
|
26
|
-
const logicVersionNumber = match ? parseInt(match[2], 10) : undefined;
|
|
27
|
-
if (match && eventNumber != null && !isNaN(eventNumber)) {
|
|
28
|
-
return {
|
|
29
|
-
type: "after-event",
|
|
30
|
-
eventNumber,
|
|
31
|
-
...(logicVersionNumber != null && !isNaN(logicVersionNumber)
|
|
32
|
-
? { logicVersion: logicVersionNumber }
|
|
33
|
-
: {}),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
};
|
|
39
|
-
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,69 +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, diffThreshold, diffPixelThreshold, moveBeforeClick, simulationIdForAssets, } = prevOptions;
|
|
26
|
-
return {
|
|
27
|
-
...(appUrl != null ? { appUrl } : {}),
|
|
28
|
-
...(diffThreshold != null ? { diffThreshold } : {}),
|
|
29
|
-
...(diffPixelThreshold != null ? { diffPixelThreshold } : {}),
|
|
30
|
-
...(moveBeforeClick != null ? { moveBeforeClick } : {}),
|
|
31
|
-
...(simulationIdForAssets != null ? { simulationIdForAssets } : {}),
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
const validateConfig = (prevConfig) => {
|
|
35
|
-
const { testCases, ...rest } = prevConfig;
|
|
36
|
-
const nextTestCases = (testCases || [])
|
|
37
|
-
.map(({ title, sessionId, options }) => ({
|
|
38
|
-
title: typeof title === "string" ? title : "",
|
|
39
|
-
sessionId: typeof sessionId === "string" ? sessionId : "",
|
|
40
|
-
...(options ? { options: validateReplayOptions(options) } : {}),
|
|
41
|
-
}))
|
|
42
|
-
.map(({ title, ...rest }) => ({
|
|
43
|
-
title: title || `${rest.sessionId}`,
|
|
44
|
-
...rest,
|
|
45
|
-
}))
|
|
46
|
-
.filter(({ sessionId }) => sessionId);
|
|
47
|
-
return { ...rest, testCases: nextTestCases };
|
|
48
|
-
};
|
|
49
|
-
const readConfig = async (configFilePath) => {
|
|
50
|
-
const filePath = configFilePath !== null && configFilePath !== void 0 ? configFilePath : (await getConfigFilePath());
|
|
51
|
-
const configStr = await (0, promises_1.readFile)(filePath, "utf-8").catch((error) => {
|
|
52
|
-
// Use an empty config object if there is no config file
|
|
53
|
-
if (configFilePath == null &&
|
|
54
|
-
error instanceof Error &&
|
|
55
|
-
error.code === "ENOENT") {
|
|
56
|
-
return "{}";
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
|
-
});
|
|
60
|
-
const config = JSON.parse(configStr);
|
|
61
|
-
return validateConfig(config);
|
|
62
|
-
};
|
|
63
|
-
exports.readConfig = readConfig;
|
|
64
|
-
const saveConfig = async (config) => {
|
|
65
|
-
const filePath = await getConfigFilePath();
|
|
66
|
-
const configStr = JSON.stringify(config, null, 2);
|
|
67
|
-
await (0, promises_1.writeFile)(filePath, configStr);
|
|
68
|
-
};
|
|
69
|
-
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 {};
|
|
@@ -1,92 +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.waitToAcquireLockOnDirectory = exports.fileExists = exports.getOrDownloadJsonFile = exports.sanitizeFilename = void 0;
|
|
7
|
-
const promises_1 = require("fs/promises");
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
10
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
11
|
-
const luxon_1 = require("luxon");
|
|
12
|
-
const proper_lockfile_1 = require("proper-lockfile");
|
|
13
|
-
const sanitizeFilename = (filename) => {
|
|
14
|
-
return filename.replace(/[^a-zA-Z0-9]/g, "_");
|
|
15
|
-
};
|
|
16
|
-
exports.sanitizeFilename = sanitizeFilename;
|
|
17
|
-
/**
|
|
18
|
-
* Returns the JSON.parse'd contents of the file at the given path. If the file
|
|
19
|
-
* doesn't exist yet then it downloads the object, writes it to the file, and returns it.
|
|
20
|
-
*
|
|
21
|
-
* Handles concurrent processes trying to download to the same file at the same time.
|
|
22
|
-
*/
|
|
23
|
-
const getOrDownloadJsonFile = async ({ filePath, downloadJson, dataDescription, }) => {
|
|
24
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
25
|
-
await (0, promises_1.mkdir)((0, path_1.dirname)(filePath), { recursive: true });
|
|
26
|
-
// We create a lock file so that if multiple processes try downloading at the same
|
|
27
|
-
// time they don't interfere with each other. The second process to run will
|
|
28
|
-
// wait for the first process to complete, and then return straight away because
|
|
29
|
-
// it'll notice the file already exists.
|
|
30
|
-
const releaseLock = await waitToAcquireLockOnFile(filePath);
|
|
31
|
-
try {
|
|
32
|
-
const existingData = await (0, promises_1.readFile)(filePath)
|
|
33
|
-
.then((data) => JSON.parse(data.toString("utf-8")))
|
|
34
|
-
.catch(() => null);
|
|
35
|
-
if (existingData) {
|
|
36
|
-
logger.debug(`Reading ${dataDescription} from local copy in ${filePath}`);
|
|
37
|
-
return existingData;
|
|
38
|
-
}
|
|
39
|
-
const downloadedData = await downloadJson();
|
|
40
|
-
if (downloadedData) {
|
|
41
|
-
await (0, promises_1.writeFile)(filePath, JSON.stringify(downloadedData, null, 2));
|
|
42
|
-
logger.debug(`Wrote ${dataDescription} to ${filePath}`);
|
|
43
|
-
}
|
|
44
|
-
return downloadedData;
|
|
45
|
-
}
|
|
46
|
-
finally {
|
|
47
|
-
await releaseLock();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
exports.getOrDownloadJsonFile = getOrDownloadJsonFile;
|
|
51
|
-
const waitToAcquireLockOnFile = async (filePath) => {
|
|
52
|
-
// In many cases the file doesn't exist yet, and can't exist yet (need to download the data, and creating an
|
|
53
|
-
// empty file beforehand is risky if the process crashes, and a second process tries reading the empty file).
|
|
54
|
-
// However proper-lockfile requires us to pass a file or directory as the first arg. This path is just used
|
|
55
|
-
// to detect if the same process tries taking out multiple locks on the same file. It just needs to be calculated
|
|
56
|
-
// as something that's unique to the file, and gives the same path for a given file everytime. So we create our
|
|
57
|
-
// own lock-target directory for this purpose (directory not file since mkdir is guaranteed to be synchronous).
|
|
58
|
-
// The path needs to actually exist, since proper-lockfile resolves symlinks on it.
|
|
59
|
-
//
|
|
60
|
-
// Note: we don't delete the lock directory afterwards because doing so without creating race-conditions is tricky
|
|
61
|
-
const lockDirectory = `${filePath}.lock-target`;
|
|
62
|
-
await (0, promises_1.mkdir)(lockDirectory, { recursive: true });
|
|
63
|
-
const releaseLock = await (0, proper_lockfile_1.lock)(lockDirectory, {
|
|
64
|
-
retries: LOCK_RETRY_OPTIONS,
|
|
65
|
-
lockfilePath: `${filePath}.lock`,
|
|
66
|
-
});
|
|
67
|
-
return async () => {
|
|
68
|
-
await releaseLock();
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
const fileExists = (filePath) => (0, promises_1.access)(filePath)
|
|
72
|
-
.then(() => true)
|
|
73
|
-
.catch(() => false);
|
|
74
|
-
exports.fileExists = fileExists;
|
|
75
|
-
const waitToAcquireLockOnDirectory = (directoryPath) => {
|
|
76
|
-
return (0, proper_lockfile_1.lock)(directoryPath, {
|
|
77
|
-
retries: LOCK_RETRY_OPTIONS,
|
|
78
|
-
lockfilePath: (0, path_1.join)(directoryPath, "dir.lock"),
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
exports.waitToAcquireLockOnDirectory = waitToAcquireLockOnDirectory;
|
|
82
|
-
const LOCK_RETRY_OPTIONS = {
|
|
83
|
-
// We want to keep on retrying till we get the maxRetryTime, so we set retries, which is a maximum, to a high value
|
|
84
|
-
retries: 1000,
|
|
85
|
-
factor: 1.05,
|
|
86
|
-
minTimeout: 500,
|
|
87
|
-
maxTimeout: 2000,
|
|
88
|
-
// Wait a maximum of 120s for the other process to finish downloading and/or extracting
|
|
89
|
-
maxRetryTime: luxon_1.Duration.fromObject({ minutes: 2 }).as("milliseconds"),
|
|
90
|
-
// Randomize so processes are less likely to clash on their retries
|
|
91
|
-
randomize: true,
|
|
92
|
-
};
|