@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
package/dist/archive/archive.js
DELETED
|
@@ -1,64 +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.deleteArchive = exports.createReplayArchive = exports.createArchive = exports.checkDistFolder = void 0;
|
|
7
|
-
const fs_1 = require("fs");
|
|
8
|
-
const promises_1 = require("fs/promises");
|
|
9
|
-
const os_1 = require("os");
|
|
10
|
-
const path_1 = require("path");
|
|
11
|
-
const archiver_1 = __importDefault(require("archiver"));
|
|
12
|
-
const checkDistFolder = async (dist_) => {
|
|
13
|
-
const dist = (0, path_1.resolve)(process.cwd(), dist_);
|
|
14
|
-
const files = await (0, promises_1.readdir)(dist, { encoding: "utf-8", withFileTypes: true });
|
|
15
|
-
const indexHtml = files.find((entry) => entry.name.toLocaleLowerCase() === "index.html" && entry.isFile());
|
|
16
|
-
if (!indexHtml) {
|
|
17
|
-
throw new Error(`Cannot find "index.html" in ${dist}`);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
exports.checkDistFolder = checkDistFolder;
|
|
21
|
-
const createArchive = async (dist_) => {
|
|
22
|
-
const tempDir = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), "meticulous-"));
|
|
23
|
-
const archivePath = (0, path_1.join)(tempDir, "build.zip");
|
|
24
|
-
const dist = (0, path_1.resolve)(process.cwd(), dist_);
|
|
25
|
-
const fileStream = (0, fs_1.createWriteStream)(archivePath);
|
|
26
|
-
const archive = (0, archiver_1.default)("zip");
|
|
27
|
-
await new Promise((resolve, reject) => {
|
|
28
|
-
archive.on("error", (err) => reject(err));
|
|
29
|
-
fileStream.on("close", () => {
|
|
30
|
-
resolve(null);
|
|
31
|
-
});
|
|
32
|
-
archive.pipe(fileStream);
|
|
33
|
-
archive.directory(dist, false);
|
|
34
|
-
archive.finalize().catch((error) => {
|
|
35
|
-
throw error;
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
return archivePath;
|
|
39
|
-
};
|
|
40
|
-
exports.createArchive = createArchive;
|
|
41
|
-
const createReplayArchive = async (dir) => {
|
|
42
|
-
const tempDir = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), "meticulous-"));
|
|
43
|
-
const archivePath = (0, path_1.join)(tempDir, "replay.zip");
|
|
44
|
-
const fileStream = (0, fs_1.createWriteStream)(archivePath);
|
|
45
|
-
const archive = (0, archiver_1.default)("zip");
|
|
46
|
-
await new Promise((resolve, reject) => {
|
|
47
|
-
archive.on("error", (err) => reject(err));
|
|
48
|
-
fileStream.on("close", () => {
|
|
49
|
-
resolve(null);
|
|
50
|
-
});
|
|
51
|
-
archive.pipe(fileStream);
|
|
52
|
-
archive.directory(dir, false);
|
|
53
|
-
archive.finalize().catch((error) => {
|
|
54
|
-
throw error;
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
return archivePath;
|
|
58
|
-
};
|
|
59
|
-
exports.createReplayArchive = createReplayArchive;
|
|
60
|
-
const deleteArchive = async (archivePath) => {
|
|
61
|
-
const dir = (0, path_1.dirname)(archivePath);
|
|
62
|
-
await (0, promises_1.rm)(dir, { force: true, recursive: true });
|
|
63
|
-
};
|
|
64
|
-
exports.deleteArchive = deleteArchive;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ScreenshottingEnabledOptions } from "@alwaysmeticulous/common";
|
|
2
|
-
/**
|
|
3
|
-
* Options for taking a screenshot and comparing it against a previous screenshot
|
|
4
|
-
*/
|
|
5
|
-
export type ScreenshotAssertionsOptions = {
|
|
6
|
-
enabled: false;
|
|
7
|
-
} | ScreenshotAssertionsEnabledOptions;
|
|
8
|
-
export interface ScreenshotAssertionsEnabledOptions extends ScreenshottingEnabledOptions {
|
|
9
|
-
diffOptions: ScreenshotDiffOptions;
|
|
10
|
-
}
|
|
11
|
-
export interface ScreenshotDiffOptions {
|
|
12
|
-
/**
|
|
13
|
-
* Acceptable maximum proportion of changed pixels, between 0 and 1.
|
|
14
|
-
*/
|
|
15
|
-
diffThreshold: number;
|
|
16
|
-
/**
|
|
17
|
-
* A number between 0 and 1. Color/brightness differences in individual pixels will be ignored if the difference is less than this threshold. A value of 1.0 would accept any difference in color, while a value of 0.0 would accept no difference in color.
|
|
18
|
-
*/
|
|
19
|
-
diffPixelThreshold: number;
|
|
20
|
-
}
|
|
@@ -1,33 +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.bootstrapCommand = void 0;
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
10
|
-
const command_builder_1 = require("../../command-utils/command-builder");
|
|
11
|
-
const config_1 = require("../../config/config");
|
|
12
|
-
const npm_set_script_utils_1 = require("../../utils/npm-set-script.utils");
|
|
13
|
-
const handler = async () => {
|
|
14
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
15
|
-
logger.info(`Setting up ${chalk_1.default.green("meticulous.json")}...`);
|
|
16
|
-
const meticulousConfig = await (0, config_1.readConfig)();
|
|
17
|
-
const newConfig = {
|
|
18
|
-
...meticulousConfig,
|
|
19
|
-
testCases: [...(meticulousConfig.testCases || [])],
|
|
20
|
-
};
|
|
21
|
-
await (0, config_1.saveConfig)(newConfig);
|
|
22
|
-
logger.info(`Setting up ${chalk_1.default.green("test:meticulous")} script...`);
|
|
23
|
-
await (0, npm_set_script_utils_1.npmSetScript)({
|
|
24
|
-
script: "test:meticulous",
|
|
25
|
-
command: "meticulous run-all-tests --headless --parallelize",
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
exports.bootstrapCommand = (0, command_builder_1.buildCommand)("bootstrap")
|
|
29
|
-
.details({
|
|
30
|
-
describe: "Bootstrap your project to use Meticulous",
|
|
31
|
-
})
|
|
32
|
-
.options({})
|
|
33
|
-
.handler(handler);
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/// <reference types="yargs" />
|
|
2
|
-
export declare const createTestCommand: import("yargs").CommandModule<unknown, import("yargs").InferredOptionTypes<{
|
|
3
|
-
headless: {
|
|
4
|
-
default: boolean;
|
|
5
|
-
boolean: true;
|
|
6
|
-
description: "Start browser in headless mode";
|
|
7
|
-
};
|
|
8
|
-
skipPauses: {
|
|
9
|
-
description: string;
|
|
10
|
-
boolean: true;
|
|
11
|
-
default: true;
|
|
12
|
-
};
|
|
13
|
-
devTools: {
|
|
14
|
-
readonly boolean: true;
|
|
15
|
-
readonly description: "Open Chrome Dev Tools";
|
|
16
|
-
readonly default: false;
|
|
17
|
-
};
|
|
18
|
-
bypassCSP: {
|
|
19
|
-
readonly boolean: true;
|
|
20
|
-
readonly description: "Enables bypass CSP in the browser (danger: this could mean you tests hit your production backend)";
|
|
21
|
-
readonly default: false;
|
|
22
|
-
};
|
|
23
|
-
shiftTime: {
|
|
24
|
-
readonly boolean: true;
|
|
25
|
-
readonly description: "Shift time during simulation to be set as the recording time";
|
|
26
|
-
readonly default: true;
|
|
27
|
-
};
|
|
28
|
-
networkStubbing: {
|
|
29
|
-
readonly boolean: true;
|
|
30
|
-
readonly description: "Stub network requests during replay";
|
|
31
|
-
readonly default: true;
|
|
32
|
-
};
|
|
33
|
-
disableRemoteFonts: {
|
|
34
|
-
readonly boolean: true;
|
|
35
|
-
readonly description: "Pass the disable remote fonts flag into chromium";
|
|
36
|
-
readonly default: false;
|
|
37
|
-
};
|
|
38
|
-
noSandbox: {
|
|
39
|
-
readonly boolean: true;
|
|
40
|
-
readonly description: "Pass the no sandbox flag into chromium";
|
|
41
|
-
readonly default: false;
|
|
42
|
-
};
|
|
43
|
-
maxDurationMs: {
|
|
44
|
-
readonly number: true;
|
|
45
|
-
readonly description: "Maximum duration (in milliseconds) the simulation will run";
|
|
46
|
-
};
|
|
47
|
-
maxEventCount: {
|
|
48
|
-
readonly number: true;
|
|
49
|
-
readonly description: "Maximum number of events the simulation will run";
|
|
50
|
-
};
|
|
51
|
-
essentialFeaturesOnly: {
|
|
52
|
-
readonly boolean: true;
|
|
53
|
-
readonly description: "Disable any features that are non-essential for running tests/executing replays. This includes disabling recording a video of the replay, for playback in the web app. This flag is useful to reduce noise when debugging.";
|
|
54
|
-
readonly default: false;
|
|
55
|
-
};
|
|
56
|
-
apiToken: {
|
|
57
|
-
readonly string: true;
|
|
58
|
-
};
|
|
59
|
-
commitSha: {
|
|
60
|
-
readonly string: true;
|
|
61
|
-
};
|
|
62
|
-
width: {
|
|
63
|
-
number: true;
|
|
64
|
-
};
|
|
65
|
-
height: {
|
|
66
|
-
number: true;
|
|
67
|
-
};
|
|
68
|
-
uploadIntervalMs: {
|
|
69
|
-
number: true;
|
|
70
|
-
description: string;
|
|
71
|
-
};
|
|
72
|
-
incognito: {
|
|
73
|
-
boolean: true;
|
|
74
|
-
description: string;
|
|
75
|
-
default: boolean;
|
|
76
|
-
};
|
|
77
|
-
trace: {
|
|
78
|
-
boolean: true;
|
|
79
|
-
description: string;
|
|
80
|
-
};
|
|
81
|
-
screenshotSelector: {
|
|
82
|
-
string: true;
|
|
83
|
-
description: string;
|
|
84
|
-
};
|
|
85
|
-
moveBeforeClick: {
|
|
86
|
-
readonly boolean: true;
|
|
87
|
-
readonly description: "Simulate mouse movement before clicking";
|
|
88
|
-
readonly default: true;
|
|
89
|
-
};
|
|
90
|
-
cookiesFile: {
|
|
91
|
-
string: true;
|
|
92
|
-
description: string;
|
|
93
|
-
};
|
|
94
|
-
}>>;
|
|
@@ -1,170 +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.createTestCommand = void 0;
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const inquirer_1 = require("inquirer");
|
|
10
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
11
|
-
const command_builder_1 = require("../../command-utils/command-builder");
|
|
12
|
-
const common_options_1 = require("../../command-utils/common-options");
|
|
13
|
-
const config_utils_1 = require("../../utils/config.utils");
|
|
14
|
-
const record_command_1 = require("../record/record.command");
|
|
15
|
-
const replay_command_1 = require("../replay/replay.command");
|
|
16
|
-
const handleTestCreation = async (replay, sessionId) => {
|
|
17
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
18
|
-
const validationResponse = await (0, inquirer_1.prompt)([
|
|
19
|
-
{
|
|
20
|
-
type: "confirm",
|
|
21
|
-
name: "value",
|
|
22
|
-
message: "Does the end state screenshot match your expectation?",
|
|
23
|
-
default: true,
|
|
24
|
-
},
|
|
25
|
-
]);
|
|
26
|
-
if (!validationResponse.value) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const createTestResponse = await (0, inquirer_1.prompt)([
|
|
30
|
-
{
|
|
31
|
-
type: "confirm",
|
|
32
|
-
name: "value",
|
|
33
|
-
message: `Would you like to save this as a test to ${chalk_1.default.green("meticulous.json")}?`,
|
|
34
|
-
default: true,
|
|
35
|
-
},
|
|
36
|
-
]);
|
|
37
|
-
if (!createTestResponse.value) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const testNameResponse = await (0, inquirer_1.prompt)([
|
|
41
|
-
{
|
|
42
|
-
type: "input",
|
|
43
|
-
name: "name",
|
|
44
|
-
message: "Test name:",
|
|
45
|
-
default: `${sessionId} | ${replay.id}`,
|
|
46
|
-
},
|
|
47
|
-
]);
|
|
48
|
-
await (0, config_utils_1.addTestCase)({
|
|
49
|
-
title: testNameResponse.name,
|
|
50
|
-
sessionId,
|
|
51
|
-
});
|
|
52
|
-
logger.info(chalk_1.default.bold.white(`Test saved to ${chalk_1.default.green("meticulous.json")}.`));
|
|
53
|
-
};
|
|
54
|
-
// The create-test handler combines recording a session and simulating it for
|
|
55
|
-
// validation.
|
|
56
|
-
const handler = async ({
|
|
57
|
-
// Common options
|
|
58
|
-
apiToken, commitSha, devTools, bypassCSP,
|
|
59
|
-
// Record options
|
|
60
|
-
width, height, uploadIntervalMs, incognito, trace,
|
|
61
|
-
// Replay options
|
|
62
|
-
headless, screenshotSelector, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, }) => {
|
|
63
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
64
|
-
logger.info("Creating a new Meticulous test");
|
|
65
|
-
logger.info("Step 1: record a new test");
|
|
66
|
-
let lastSessionId = "";
|
|
67
|
-
const onDetectedSession = (sessionId) => {
|
|
68
|
-
lastSessionId = sessionId;
|
|
69
|
-
};
|
|
70
|
-
// 1. Record
|
|
71
|
-
const recordOptions = {
|
|
72
|
-
apiToken,
|
|
73
|
-
commitSha,
|
|
74
|
-
devTools,
|
|
75
|
-
bypassCSP,
|
|
76
|
-
width,
|
|
77
|
-
height,
|
|
78
|
-
uploadIntervalMs,
|
|
79
|
-
incognito,
|
|
80
|
-
trace,
|
|
81
|
-
onDetectedSession,
|
|
82
|
-
};
|
|
83
|
-
await (0, record_command_1.recordCommandHandler)(recordOptions);
|
|
84
|
-
logger.debug(`lastSessionId = ${lastSessionId}`);
|
|
85
|
-
if (!lastSessionId) {
|
|
86
|
-
logger.error("No test was recorded!");
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
logger.info("Step 2: validating the new test...");
|
|
90
|
-
const replayOptions = {
|
|
91
|
-
apiToken,
|
|
92
|
-
commitSha,
|
|
93
|
-
sessionId: lastSessionId,
|
|
94
|
-
headless,
|
|
95
|
-
devTools,
|
|
96
|
-
bypassCSP,
|
|
97
|
-
screenshot: true,
|
|
98
|
-
screenshotSelector,
|
|
99
|
-
shiftTime,
|
|
100
|
-
disableRemoteFonts,
|
|
101
|
-
noSandbox,
|
|
102
|
-
networkStubbing,
|
|
103
|
-
moveBeforeClick,
|
|
104
|
-
cookiesFile,
|
|
105
|
-
skipPauses,
|
|
106
|
-
// We replay against the original recorded URL
|
|
107
|
-
appUrl: null,
|
|
108
|
-
simulationIdForAssets: null,
|
|
109
|
-
// We don't try comparing to the original screenshot, so just set these to their defaults
|
|
110
|
-
diffThreshold: common_options_1.OPTIONS.diffThreshold.default,
|
|
111
|
-
diffPixelThreshold: common_options_1.OPTIONS.diffPixelThreshold.default,
|
|
112
|
-
// We don't expose these options
|
|
113
|
-
maxDurationMs: null,
|
|
114
|
-
maxEventCount: null,
|
|
115
|
-
storyboard: false,
|
|
116
|
-
essentialFeaturesOnly: false,
|
|
117
|
-
debugger: false,
|
|
118
|
-
};
|
|
119
|
-
const replay = await (0, replay_command_1.rawReplayCommandHandler)(replayOptions);
|
|
120
|
-
await handleTestCreation(replay, lastSessionId);
|
|
121
|
-
};
|
|
122
|
-
exports.createTestCommand = (0, command_builder_1.buildCommand)("create-test")
|
|
123
|
-
.details({
|
|
124
|
-
describe: "Create a new test",
|
|
125
|
-
})
|
|
126
|
-
.options({
|
|
127
|
-
// Common options
|
|
128
|
-
apiToken: common_options_1.OPTIONS.apiToken,
|
|
129
|
-
commitSha: common_options_1.OPTIONS.commitSha,
|
|
130
|
-
// Record options
|
|
131
|
-
width: {
|
|
132
|
-
number: true,
|
|
133
|
-
},
|
|
134
|
-
height: {
|
|
135
|
-
number: true,
|
|
136
|
-
},
|
|
137
|
-
uploadIntervalMs: {
|
|
138
|
-
number: true,
|
|
139
|
-
description: "Meticulous recording upload interval (in milliseconds)",
|
|
140
|
-
},
|
|
141
|
-
incognito: {
|
|
142
|
-
boolean: true,
|
|
143
|
-
description: "Use an incognito browsing context",
|
|
144
|
-
default: true,
|
|
145
|
-
},
|
|
146
|
-
trace: {
|
|
147
|
-
boolean: true,
|
|
148
|
-
description: "Enable verbose logging",
|
|
149
|
-
},
|
|
150
|
-
// Replay options
|
|
151
|
-
screenshotSelector: {
|
|
152
|
-
string: true,
|
|
153
|
-
description: "Query selector to screenshot a specific DOM element instead of the whole page",
|
|
154
|
-
},
|
|
155
|
-
moveBeforeClick: common_options_1.OPTIONS.moveBeforeClick,
|
|
156
|
-
cookiesFile: {
|
|
157
|
-
string: true,
|
|
158
|
-
description: "Path to cookies to inject before simulation",
|
|
159
|
-
},
|
|
160
|
-
...common_options_1.COMMON_REPLAY_OPTIONS,
|
|
161
|
-
headless: {
|
|
162
|
-
...common_options_1.COMMON_REPLAY_OPTIONS.headless,
|
|
163
|
-
default: true,
|
|
164
|
-
},
|
|
165
|
-
skipPauses: {
|
|
166
|
-
...common_options_1.COMMON_REPLAY_OPTIONS.skipPauses,
|
|
167
|
-
description: "Fast forward through any pauses to replay as fast as possible when replaying for the first time to create the test.",
|
|
168
|
-
},
|
|
169
|
-
})
|
|
170
|
-
.handler(handler);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ScreenshotAssertionsEnabledOptions, ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
2
|
-
import { AxiosInstance } from "axios";
|
|
3
|
-
import log from "loglevel";
|
|
4
|
-
export interface ComputeAndSaveDiffOptions {
|
|
5
|
-
client: AxiosInstance;
|
|
6
|
-
baseTestRunId: string;
|
|
7
|
-
sessionId: string;
|
|
8
|
-
headReplayId: string;
|
|
9
|
-
tempDir: string;
|
|
10
|
-
screenshottingOptions: ScreenshotAssertionsEnabledOptions;
|
|
11
|
-
logger: log.Logger;
|
|
12
|
-
}
|
|
13
|
-
export declare const computeDiff: ({ client, baseTestRunId, sessionId, tempDir, headReplayId, screenshottingOptions, logger, }: ComputeAndSaveDiffOptions) => Promise<Record<string, ScreenshotDiffResult[]>>;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.computeDiff = void 0;
|
|
4
|
-
const promises_1 = require("fs/promises");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const download_1 = require("../../../api/download");
|
|
7
|
-
const test_run_api_1 = require("../../../api/test-run.api");
|
|
8
|
-
const replays_1 = require("../../../local-data/replays");
|
|
9
|
-
const screenshot_diff_command_1 = require("../../screenshot-diff/screenshot-diff.command");
|
|
10
|
-
const get_screenshot_filename_1 = require("../../screenshot-diff/utils/get-screenshot-filename");
|
|
11
|
-
const computeDiff = async ({ client, baseTestRunId, sessionId, tempDir, headReplayId, screenshottingOptions, logger, }) => {
|
|
12
|
-
var _a;
|
|
13
|
-
logger.info(`Diffing screenshots against replays of session ${sessionId} in test run ${baseTestRunId}`);
|
|
14
|
-
const baseScreenshotsDir = (0, path_1.join)((0, replays_1.getScreenshotsDir)(tempDir), "base-screenshots");
|
|
15
|
-
await (0, promises_1.mkdir)(baseScreenshotsDir, { recursive: true });
|
|
16
|
-
const baseScreenshots = await (0, test_run_api_1.getBaseScreenshots)({
|
|
17
|
-
client,
|
|
18
|
-
testRunId: baseTestRunId,
|
|
19
|
-
sessionId,
|
|
20
|
-
});
|
|
21
|
-
const screenshotIdentifiersByBaseReplayId = groupByBaseReplayId(baseScreenshots);
|
|
22
|
-
await downloadScreenshots(baseScreenshots, baseScreenshotsDir);
|
|
23
|
-
logger.debug(`Downloaded ${baseScreenshots.length} base screenshots to ${baseScreenshotsDir}}`);
|
|
24
|
-
const headReplayScreenshotsDir = (0, replays_1.getScreenshotsDir)(tempDir);
|
|
25
|
-
const headReplayScreenshots = await (0, replays_1.getScreenshotFiles)(headReplayScreenshotsDir);
|
|
26
|
-
const screenshotDiffResultsByBaseReplayId = {};
|
|
27
|
-
// Diff the screenshots for each base replay
|
|
28
|
-
for (const baseReplayId of screenshotIdentifiersByBaseReplayId.keys()) {
|
|
29
|
-
const baseScreenshotIdentifiers = (_a = screenshotIdentifiersByBaseReplayId.get(baseReplayId)) !== null && _a !== void 0 ? _a : [];
|
|
30
|
-
const baseReplayScreenshots = baseScreenshotIdentifiers.map((identifier) => ({
|
|
31
|
-
identifier,
|
|
32
|
-
fileName: (0, get_screenshot_filename_1.getScreenshotFilename)(identifier),
|
|
33
|
-
}));
|
|
34
|
-
const resultsForBaseReplay = await (0, screenshot_diff_command_1.diffScreenshots)({
|
|
35
|
-
baseReplayId,
|
|
36
|
-
headReplayId,
|
|
37
|
-
baseScreenshotsDir: baseScreenshotsDir,
|
|
38
|
-
headScreenshotsDir: headReplayScreenshotsDir,
|
|
39
|
-
headReplayScreenshots,
|
|
40
|
-
baseReplayScreenshots,
|
|
41
|
-
diffOptions: screenshottingOptions.diffOptions,
|
|
42
|
-
});
|
|
43
|
-
screenshotDiffResultsByBaseReplayId[baseReplayId] = resultsForBaseReplay;
|
|
44
|
-
}
|
|
45
|
-
(0, screenshot_diff_command_1.logDifferences)({
|
|
46
|
-
results: Object.values(screenshotDiffResultsByBaseReplayId).flat(),
|
|
47
|
-
diffOptions: screenshottingOptions.diffOptions,
|
|
48
|
-
logger,
|
|
49
|
-
});
|
|
50
|
-
// Delete base screenshots directory and all files inside it
|
|
51
|
-
// (we don't want to upload it to the backend)
|
|
52
|
-
await deleteScreenshotsDirectory(baseScreenshotsDir);
|
|
53
|
-
return screenshotDiffResultsByBaseReplayId;
|
|
54
|
-
};
|
|
55
|
-
exports.computeDiff = computeDiff;
|
|
56
|
-
const deleteScreenshotsDirectory = async (directory) => {
|
|
57
|
-
const files = await (0, promises_1.opendir)(directory);
|
|
58
|
-
for await (const file of files) {
|
|
59
|
-
if (file.isFile()) {
|
|
60
|
-
await (0, promises_1.rm)((0, path_1.join)(directory, file.name));
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
throw new Error(`Expected screenshots directory to only contain screenshot files, but it contained: ${file.name}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
await (0, promises_1.rmdir)(directory);
|
|
67
|
-
};
|
|
68
|
-
const groupByBaseReplayId = (screenshots) => {
|
|
69
|
-
const screenshotIdentifiersByBaseReplayId = new Map();
|
|
70
|
-
screenshots.forEach(({ replayId, screenshotIdentifier }) => {
|
|
71
|
-
var _a;
|
|
72
|
-
const screenshotIdentifiers = (_a = screenshotIdentifiersByBaseReplayId.get(replayId)) !== null && _a !== void 0 ? _a : [];
|
|
73
|
-
screenshotIdentifiers.push(screenshotIdentifier);
|
|
74
|
-
screenshotIdentifiersByBaseReplayId.set(replayId, screenshotIdentifiers);
|
|
75
|
-
});
|
|
76
|
-
return screenshotIdentifiersByBaseReplayId;
|
|
77
|
-
};
|
|
78
|
-
const downloadScreenshots = async (screenshots, directory) => {
|
|
79
|
-
// Download in batches of 20
|
|
80
|
-
const screenshotBatches = chunk(screenshots, 20);
|
|
81
|
-
for (const screenshotBatch of screenshotBatches) {
|
|
82
|
-
const screenshotDownloads = screenshotBatch.map((screenshot) => {
|
|
83
|
-
const filePath = (0, path_1.join)(directory, (0, get_screenshot_filename_1.getScreenshotFilename)(screenshot.screenshotIdentifier));
|
|
84
|
-
return (0, download_1.downloadFile)(screenshot.screenshotUrl, filePath);
|
|
85
|
-
});
|
|
86
|
-
await Promise.all(screenshotDownloads);
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
const chunk = (array, size) => {
|
|
90
|
-
const chunkedArray = [];
|
|
91
|
-
for (let i = 0; i < array.length; i += size) {
|
|
92
|
-
chunkedArray.push(array.slice(i, i + size));
|
|
93
|
-
}
|
|
94
|
-
return chunkedArray;
|
|
95
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const exitEarlyIfSkipUploadEnvVarSet: (baseTestRunId: string | null | undefined) => void;
|
|
@@ -1,27 +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.exitEarlyIfSkipUploadEnvVarSet = void 0;
|
|
7
|
-
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
|
9
|
-
const METICULOUS_SKIP_UPLOAD_ENV_VAR = "METICULOUS_SKIP_UPLOAD";
|
|
10
|
-
// Uploading the zip can take a long time, so we expose a env variable to support skipping it
|
|
11
|
-
// We don't expose this as a first-class CLI option because we don't want to pollute the list of CLI options
|
|
12
|
-
const exitEarlyIfSkipUploadEnvVarSet = (baseTestRunId) => {
|
|
13
|
-
if (!shouldSkipUpload()) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (baseTestRunId != null) {
|
|
17
|
-
throw new Error(`Cannot specify baseTestRunId and compare to base results when ${METICULOUS_SKIP_UPLOAD_ENV_VAR} is set to true`);
|
|
18
|
-
}
|
|
19
|
-
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
20
|
-
logger.info(`Skipping upload / exiting early since ${METICULOUS_SKIP_UPLOAD_ENV_VAR} is set to true`);
|
|
21
|
-
process.exit(0);
|
|
22
|
-
};
|
|
23
|
-
exports.exitEarlyIfSkipUploadEnvVarSet = exitEarlyIfSkipUploadEnvVarSet;
|
|
24
|
-
const shouldSkipUpload = () => {
|
|
25
|
-
var _a;
|
|
26
|
-
return (((_a = process.env[METICULOUS_SKIP_UPLOAD_ENV_VAR]) !== null && _a !== void 0 ? _a : "").toLowerCase() === "true");
|
|
27
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/// <reference types="yargs" />
|
|
2
|
-
import { ScreenshotDiffOptions, ScreenshotDiffResult } from "@alwaysmeticulous/api";
|
|
3
|
-
import log from "loglevel";
|
|
4
|
-
import { IdentifiedScreenshotFile } from "../../local-data/replays";
|
|
5
|
-
export declare const diffScreenshots: ({ headReplayId, baseReplayId, baseScreenshotsDir, headScreenshotsDir, baseReplayScreenshots, headReplayScreenshots, diffOptions, }: {
|
|
6
|
-
baseReplayId: string;
|
|
7
|
-
headReplayId: string;
|
|
8
|
-
baseScreenshotsDir: string;
|
|
9
|
-
headScreenshotsDir: string;
|
|
10
|
-
baseReplayScreenshots: IdentifiedScreenshotFile[];
|
|
11
|
-
headReplayScreenshots: IdentifiedScreenshotFile[];
|
|
12
|
-
diffOptions: ScreenshotDiffOptions;
|
|
13
|
-
}) => Promise<ScreenshotDiffResult[]>;
|
|
14
|
-
export declare const logDifferences: ({ results, diffOptions, logger, }: {
|
|
15
|
-
results: ScreenshotDiffResult[];
|
|
16
|
-
diffOptions: ScreenshotDiffOptions;
|
|
17
|
-
logger: log.Logger;
|
|
18
|
-
}) => void;
|
|
19
|
-
export type ScreenshotDiffsSummary = HasDiffsScreenshotDiffsResult | NoDiffsScreenshotDiffsResult;
|
|
20
|
-
export interface HasDiffsScreenshotDiffsResult {
|
|
21
|
-
hasDiffs: true;
|
|
22
|
-
summaryMessage: string;
|
|
23
|
-
baseReplayId: string;
|
|
24
|
-
headReplayId: string;
|
|
25
|
-
}
|
|
26
|
-
export interface NoDiffsScreenshotDiffsResult {
|
|
27
|
-
hasDiffs: false;
|
|
28
|
-
}
|
|
29
|
-
export declare const screenshotDiffCommand: import("yargs").CommandModule<unknown, import("yargs").InferredOptionTypes<{
|
|
30
|
-
apiToken: {
|
|
31
|
-
string: true;
|
|
32
|
-
};
|
|
33
|
-
baseSimulationId: {
|
|
34
|
-
string: true;
|
|
35
|
-
demandOption: true;
|
|
36
|
-
alias: string;
|
|
37
|
-
};
|
|
38
|
-
headSimulationId: {
|
|
39
|
-
string: true;
|
|
40
|
-
demandOption: true;
|
|
41
|
-
alias: string;
|
|
42
|
-
};
|
|
43
|
-
threshold: {
|
|
44
|
-
readonly number: true;
|
|
45
|
-
readonly description: "Acceptable maximum proportion of changed pixels, between 0 and 1. If this proportion is exceeded then the test will fail.";
|
|
46
|
-
readonly default: 0.01;
|
|
47
|
-
};
|
|
48
|
-
pixelThreshold: {
|
|
49
|
-
readonly number: true;
|
|
50
|
-
readonly description: "A number between 0 and 1. Color/brightness differences in individual pixels will be ignored if the difference is less than this threshold. A value of 1.0 would accept any difference in color, while a value of 0.0 would accept no difference in color.";
|
|
51
|
-
readonly default: 0.01;
|
|
52
|
-
};
|
|
53
|
-
}>>;
|