@alwaysmeticulous/cli 2.41.0 → 2.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-utils/common-options.d.ts +1 -1
- package/dist/command-utils/common-options.js +1 -1
- package/dist/commands/replay/replay.command.js +51 -17
- package/dist/commands/replay/utils/replay-debugger.ui.d.ts +23 -0
- package/dist/commands/replay/utils/replay-debugger.ui.js +113 -0
- package/dist/commands/run-all-tests/run-all-tests.command.js +29 -17
- package/dist/utils/out-of-date-client-error.d.ts +5 -0
- package/dist/utils/out-of-date-client-error.js +14 -0
- package/package.json +11 -9
|
@@ -97,7 +97,7 @@ export declare const SCREENSHOT_DIFF_OPTIONS: {
|
|
|
97
97
|
};
|
|
98
98
|
};
|
|
99
99
|
/**
|
|
100
|
-
* Options that are passed onto
|
|
100
|
+
* Options that are passed onto launchBrowserAndReplay, that are shared by the replay, run-all-tests, and create-test commands
|
|
101
101
|
*/
|
|
102
102
|
export declare const COMMON_REPLAY_OPTIONS: {
|
|
103
103
|
headless: {
|
|
@@ -98,7 +98,7 @@ exports.SCREENSHOT_DIFF_OPTIONS = {
|
|
|
98
98
|
storyboard: exports.OPTIONS.storyboard,
|
|
99
99
|
};
|
|
100
100
|
/**
|
|
101
|
-
* Options that are passed onto
|
|
101
|
+
* Options that are passed onto launchBrowserAndReplay, that are shared by the replay, run-all-tests, and create-test commands
|
|
102
102
|
*/
|
|
103
103
|
exports.COMMON_REPLAY_OPTIONS = {
|
|
104
104
|
headless: exports.OPTIONS.headless,
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.replayCommand = exports.getReplayTarget = exports.rawReplayCommandHandler = void 0;
|
|
4
|
+
const common_1 = require("@alwaysmeticulous/common");
|
|
4
5
|
const replay_orchestrator_1 = require("@alwaysmeticulous/replay-orchestrator");
|
|
5
6
|
const command_builder_1 = require("../../command-utils/command-builder");
|
|
6
7
|
const common_options_1 = require("../../command-utils/common-options");
|
|
8
|
+
const out_of_date_client_error_1 = require("../../utils/out-of-date-client-error");
|
|
9
|
+
const replay_debugger_ui_1 = require("./utils/replay-debugger.ui");
|
|
7
10
|
const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl, simulationIdForAssets, headless, devTools, bypassCSP, screenshot, baseReplayId, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, moveBeforeClick, cookiesFile, disableRemoteFonts, noSandbox, skipPauses, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, debugger: enableStepThroughDebugger, }) => {
|
|
8
11
|
if (!screenshot && storyboard) {
|
|
9
12
|
throw new Error("Cannot take storyboard screenshots without taking end state screenshots. Please set '--screenshot' to true, or '--storyboard' to false.");
|
|
10
13
|
}
|
|
14
|
+
if (headless && enableStepThroughDebugger) {
|
|
15
|
+
throw new Error("Cannot run with both --debugger flag and --headless flag at the same time.");
|
|
16
|
+
}
|
|
11
17
|
const executionOptions = {
|
|
12
18
|
headless,
|
|
13
19
|
devTools,
|
|
@@ -41,23 +47,51 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
|
|
|
41
47
|
},
|
|
42
48
|
}
|
|
43
49
|
: { enabled: false };
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
const getOnBeforeUserEventCallback = (0, common_1.defer)();
|
|
51
|
+
const getOnClosePageCallback = (0, common_1.defer)();
|
|
52
|
+
try {
|
|
53
|
+
const replayExecution = await (0, replay_orchestrator_1.replayAndStoreResults)({
|
|
54
|
+
replayTarget: (0, exports.getReplayTarget)({
|
|
55
|
+
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
56
|
+
simulationIdForAssets: simulationIdForAssets !== null && simulationIdForAssets !== void 0 ? simulationIdForAssets : null,
|
|
57
|
+
}),
|
|
58
|
+
executionOptions,
|
|
59
|
+
screenshottingOptions,
|
|
60
|
+
apiToken,
|
|
61
|
+
commitSha,
|
|
62
|
+
cookiesFile,
|
|
63
|
+
sessionId,
|
|
64
|
+
generatedBy: generatedByOption,
|
|
65
|
+
testRunId: null,
|
|
66
|
+
suppressScreenshotDiffLogging: false,
|
|
67
|
+
...(enableStepThroughDebugger
|
|
68
|
+
? {
|
|
69
|
+
onBeforeUserEvent: async (options) => (await getOnBeforeUserEventCallback.promise)(options),
|
|
70
|
+
onClosePage: async () => (await getOnClosePageCallback.promise)(),
|
|
71
|
+
}
|
|
72
|
+
: {}),
|
|
73
|
+
maxSemanticVersionSupported: 1,
|
|
74
|
+
});
|
|
75
|
+
if (enableStepThroughDebugger) {
|
|
76
|
+
const stepThroughDebuggerUI = await (0, replay_debugger_ui_1.openStepThroughDebuggerUI)({
|
|
77
|
+
onLogEventTarget: replayExecution.logEventTarget,
|
|
78
|
+
onCloseReplayedPage: replayExecution.closePage,
|
|
79
|
+
replayableEvents: replayExecution.eventsBeingReplayed,
|
|
80
|
+
});
|
|
81
|
+
getOnBeforeUserEventCallback.resolve(stepThroughDebuggerUI.onBeforeUserEvent);
|
|
82
|
+
getOnClosePageCallback.resolve(stepThroughDebuggerUI.close);
|
|
83
|
+
}
|
|
84
|
+
const { replay } = await replayExecution.finalResult;
|
|
85
|
+
return replay;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if ((0, out_of_date_client_error_1.isOutOfDateClientError)(error)) {
|
|
89
|
+
throw new out_of_date_client_error_1.OutOfDateCLIError();
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
61
95
|
};
|
|
62
96
|
exports.rawReplayCommandHandler = rawReplayCommandHandler;
|
|
63
97
|
const getReplayTarget = ({ appUrl, simulationIdForAssets, }) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReplayableEvent } from "@alwaysmeticulous/api";
|
|
2
|
+
import { BeforeUserEventOptions, BeforeUserEventResult } from "@alwaysmeticulous/sdk-bundles-api";
|
|
3
|
+
import { Page } from "puppeteer";
|
|
4
|
+
export interface ReplayDebuggerState {
|
|
5
|
+
events: ReplayableEvent[];
|
|
6
|
+
index: number;
|
|
7
|
+
loading: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ReplayDebuggerUIOptions {
|
|
10
|
+
onCloseReplayedPage: () => void;
|
|
11
|
+
onLogEventTarget: (event: ReplayableEvent) => Promise<void>;
|
|
12
|
+
replayableEvents: ReplayableEvent[];
|
|
13
|
+
}
|
|
14
|
+
export interface ReplayDebuggerUI {
|
|
15
|
+
page: Page;
|
|
16
|
+
}
|
|
17
|
+
type OnBeforeUserEventCallback = (options: BeforeUserEventOptions) => Promise<BeforeUserEventResult>;
|
|
18
|
+
export interface StepThroughDebuggerUI {
|
|
19
|
+
onBeforeUserEvent: OnBeforeUserEventCallback;
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare const openStepThroughDebuggerUI: ({ onCloseReplayedPage, onLogEventTarget, replayableEvents, }: ReplayDebuggerUIOptions) => Promise<StepThroughDebuggerUI>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
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.openStepThroughDebuggerUI = void 0;
|
|
7
|
+
const common_1 = require("@alwaysmeticulous/common");
|
|
8
|
+
const replay_debugger_ui_1 = require("@alwaysmeticulous/replay-debugger-ui");
|
|
9
|
+
const loglevel_1 = __importDefault(require("loglevel"));
|
|
10
|
+
const puppeteer_1 = require("puppeteer");
|
|
11
|
+
// openStepThroughDebuggerUI returns a OnBeforeUserEventCallback, which the replay code
|
|
12
|
+
// calls before each next event, and blocks on the returned promise. We use this to pause
|
|
13
|
+
// the execution until the user OKs it to continue to a certain event.
|
|
14
|
+
const openStepThroughDebuggerUI = async ({ onCloseReplayedPage, onLogEventTarget, replayableEvents, }) => {
|
|
15
|
+
const logger = loglevel_1.default.getLogger(common_1.METICULOUS_LOGGER_NAME);
|
|
16
|
+
// Start the UI server
|
|
17
|
+
const uiServer = await (0, replay_debugger_ui_1.startUIServer)();
|
|
18
|
+
// Launch the browser
|
|
19
|
+
const browser = await (0, puppeteer_1.launch)({
|
|
20
|
+
args: [`--window-size=600,1000`],
|
|
21
|
+
headless: false,
|
|
22
|
+
});
|
|
23
|
+
// Create page for the debugger UI
|
|
24
|
+
const debuggerPage = (await browser.pages())[0];
|
|
25
|
+
debuggerPage.setViewport({
|
|
26
|
+
width: 0,
|
|
27
|
+
height: 0,
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* The index the page is in the process of advancing to. Equal to the current index
|
|
31
|
+
* if the page has already replayed all the so-far-requested user events.
|
|
32
|
+
*/
|
|
33
|
+
let targetIndex = 0;
|
|
34
|
+
let state = {
|
|
35
|
+
events: replayableEvents,
|
|
36
|
+
index: 0,
|
|
37
|
+
loading: false,
|
|
38
|
+
};
|
|
39
|
+
const setState = async (newState) => {
|
|
40
|
+
state = { ...state, ...newState };
|
|
41
|
+
await debuggerPage.evaluate((state) => {
|
|
42
|
+
window.__meticulous__replayDebuggerSetState(state);
|
|
43
|
+
}, state);
|
|
44
|
+
};
|
|
45
|
+
const onReady = async () => {
|
|
46
|
+
await setState(state);
|
|
47
|
+
};
|
|
48
|
+
let advanceToEvent = null;
|
|
49
|
+
const onBeforeUserEvent = async ({ userEventIndex, }) => {
|
|
50
|
+
await setState({
|
|
51
|
+
loading: userEventIndex < targetIndex,
|
|
52
|
+
index: Math.max(0, Math.min(state.events.length - 1, userEventIndex)),
|
|
53
|
+
});
|
|
54
|
+
if (state.index < targetIndex) {
|
|
55
|
+
advanceToEvent = null;
|
|
56
|
+
return { nextEventIndexToPauseBefore: targetIndex }; // keep going
|
|
57
|
+
}
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
advanceToEvent = resolve;
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const onPlayNextEvent = async () => {
|
|
63
|
+
targetIndex = state.index + 1;
|
|
64
|
+
await setState({ loading: true });
|
|
65
|
+
advanceToEvent === null || advanceToEvent === void 0 ? void 0 : advanceToEvent({ nextEventIndexToPauseBefore: targetIndex });
|
|
66
|
+
};
|
|
67
|
+
const onAdvanceToIndex = async (newTargetIndex) => {
|
|
68
|
+
if (newTargetIndex <= state.index) {
|
|
69
|
+
return; // Do nothing
|
|
70
|
+
}
|
|
71
|
+
targetIndex = newTargetIndex;
|
|
72
|
+
await setState({ loading: true });
|
|
73
|
+
advanceToEvent === null || advanceToEvent === void 0 ? void 0 : advanceToEvent({ nextEventIndexToPauseBefore: targetIndex });
|
|
74
|
+
};
|
|
75
|
+
// This function is called by the UI itself
|
|
76
|
+
await debuggerPage.exposeFunction("__meticulous__replayDebuggerDispatchEvent", (eventType, data) => {
|
|
77
|
+
if (eventType === "ready") {
|
|
78
|
+
return onReady();
|
|
79
|
+
}
|
|
80
|
+
if (eventType === "check-next-target") {
|
|
81
|
+
if (state.index >= replayableEvents.length) {
|
|
82
|
+
logger.info("End of replay!");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const nextEvent = state.events[state.index];
|
|
86
|
+
return onLogEventTarget(nextEvent);
|
|
87
|
+
}
|
|
88
|
+
if (eventType === "play-next-event") {
|
|
89
|
+
return onPlayNextEvent();
|
|
90
|
+
}
|
|
91
|
+
if (eventType === "set-index") {
|
|
92
|
+
return onAdvanceToIndex(data.index || 0);
|
|
93
|
+
}
|
|
94
|
+
logger.info(`[debugger-ui] Warning: received unknown event "${eventType}"`);
|
|
95
|
+
});
|
|
96
|
+
logger.info(`[debugger-ui] Navigating to ${uiServer.url}...`);
|
|
97
|
+
const res = await debuggerPage.goto(uiServer.url, {
|
|
98
|
+
waitUntil: "domcontentloaded",
|
|
99
|
+
});
|
|
100
|
+
const status = res && res.status();
|
|
101
|
+
if (status !== 200) {
|
|
102
|
+
throw new Error(`Expected a 200 status when going to the initial URL of the site. Got a ${status} instead.`);
|
|
103
|
+
}
|
|
104
|
+
logger.info(`[debugger-ui] Navigated to ${uiServer.url}`);
|
|
105
|
+
debuggerPage.on("close", () => {
|
|
106
|
+
uiServer.close();
|
|
107
|
+
});
|
|
108
|
+
debuggerPage.on("close", () => {
|
|
109
|
+
onCloseReplayedPage();
|
|
110
|
+
});
|
|
111
|
+
return { onBeforeUserEvent, close: () => debuggerPage.close() };
|
|
112
|
+
};
|
|
113
|
+
exports.openStepThroughDebuggerUI = openStepThroughDebuggerUI;
|
|
@@ -7,6 +7,7 @@ const replay_orchestrator_1 = require("@alwaysmeticulous/replay-orchestrator");
|
|
|
7
7
|
const test_run_api_1 = require("../../api/test-run.api");
|
|
8
8
|
const command_builder_1 = require("../../command-utils/command-builder");
|
|
9
9
|
const common_options_1 = require("../../command-utils/common-options");
|
|
10
|
+
const out_of_date_client_error_1 = require("../../utils/out-of-date-client-error");
|
|
10
11
|
const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl, headless, devTools, bypassCSP, diffThreshold, diffPixelThreshold, shiftTime, networkStubbing, githubSummary, parallelize, parallelTasks: parrelelTasks_, maxRetriesOnFailure, rerunTestsNTimes, useCache, testsFile, disableRemoteFonts, noSandbox, skipPauses, moveBeforeClick, maxDurationMs, maxEventCount, storyboard, essentialFeaturesOnly, baseTestRunId, }) => {
|
|
11
12
|
const executionOptions = {
|
|
12
13
|
headless,
|
|
@@ -36,23 +37,34 @@ const handler = async ({ apiToken, commitSha: commitSha_, baseCommitSha, appUrl,
|
|
|
36
37
|
const cachedTestRunResults = useCache
|
|
37
38
|
? await (0, test_run_api_1.getCachedTestRunResults)({ client, commitSha })
|
|
38
39
|
: [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
try {
|
|
41
|
+
const { testRun } = await (0, replay_orchestrator_1.executeTestRun)({
|
|
42
|
+
testsFile: testsFile !== null && testsFile !== void 0 ? testsFile : null,
|
|
43
|
+
executionOptions,
|
|
44
|
+
screenshottingOptions,
|
|
45
|
+
apiToken: apiToken !== null && apiToken !== void 0 ? apiToken : null,
|
|
46
|
+
commitSha,
|
|
47
|
+
baseCommitSha: baseCommitSha !== null && baseCommitSha !== void 0 ? baseCommitSha : null,
|
|
48
|
+
baseTestRunId: baseTestRunId !== null && baseTestRunId !== void 0 ? baseTestRunId : null,
|
|
49
|
+
appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
|
|
50
|
+
parallelTasks: parrelelTasks !== null && parrelelTasks !== void 0 ? parrelelTasks : null,
|
|
51
|
+
maxRetriesOnFailure,
|
|
52
|
+
rerunTestsNTimes,
|
|
53
|
+
cachedTestRunResults,
|
|
54
|
+
githubSummary,
|
|
55
|
+
maxSemanticVersionSupported: 1,
|
|
56
|
+
});
|
|
57
|
+
if (testRun.status === "Failure") {
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if ((0, out_of_date_client_error_1.isOutOfDateClientError)(error)) {
|
|
63
|
+
throw new out_of_date_client_error_1.OutOfDateCLIError();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
};
|
|
58
70
|
exports.runAllTestsCommand = (0, command_builder_1.buildCommand)("run-all-tests")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OutOfDateCLIError = exports.isOutOfDateClientError = void 0;
|
|
4
|
+
const isOutOfDateClientError = (error) => {
|
|
5
|
+
return error.name === "OutOfDateClient";
|
|
6
|
+
};
|
|
7
|
+
exports.isOutOfDateClientError = isOutOfDateClientError;
|
|
8
|
+
class OutOfDateCLIError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("The version of @alwaysmeticulous/cli you are using is out of date. Please update to the latest version, using npm or yarn, and try again.");
|
|
11
|
+
this.name = "OutOfDateCLI";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.OutOfDateCLIError = OutOfDateCLIError;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwaysmeticulous/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.42.1",
|
|
4
4
|
"description": "The Meticulous CLI",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,17 +27,19 @@
|
|
|
27
27
|
"depcheck": "depcheck --ignore-patterns=dist"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@alwaysmeticulous/api": "^2.
|
|
31
|
-
"@alwaysmeticulous/client": "^2.
|
|
32
|
-
"@alwaysmeticulous/common": "^2.
|
|
33
|
-
"@alwaysmeticulous/downloading-helpers": "^2.
|
|
34
|
-
"@alwaysmeticulous/record": "^2.
|
|
35
|
-
"@alwaysmeticulous/replay-
|
|
36
|
-
"@alwaysmeticulous/
|
|
30
|
+
"@alwaysmeticulous/api": "^2.42.0",
|
|
31
|
+
"@alwaysmeticulous/client": "^2.42.0",
|
|
32
|
+
"@alwaysmeticulous/common": "^2.42.0",
|
|
33
|
+
"@alwaysmeticulous/downloading-helpers": "^2.42.0",
|
|
34
|
+
"@alwaysmeticulous/record": "^2.42.0",
|
|
35
|
+
"@alwaysmeticulous/replay-debugger-ui": "^2.42.0",
|
|
36
|
+
"@alwaysmeticulous/replay-orchestrator": "^2.42.1",
|
|
37
|
+
"@alwaysmeticulous/sdk-bundles-api": "^2.42.1",
|
|
37
38
|
"@alwaysmeticulous/sentry": "^2.40.0",
|
|
38
39
|
"@sentry/node": "^7.36.0",
|
|
39
40
|
"axios": "^1.2.6",
|
|
40
41
|
"loglevel": "^1.8.0",
|
|
42
|
+
"puppeteer": "^19.7.5",
|
|
41
43
|
"yargs": "^17.5.1"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
@@ -77,5 +79,5 @@
|
|
|
77
79
|
"coverageDirectory": "../coverage",
|
|
78
80
|
"testEnvironment": "node"
|
|
79
81
|
},
|
|
80
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "6c08f6e79f34f7efdc2ebd0e8e4e09baffeb8f04"
|
|
81
83
|
}
|