@alwaysmeticulous/cli 2.41.0 → 2.42.0

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.
@@ -97,7 +97,7 @@ export declare const SCREENSHOT_DIFF_OPTIONS: {
97
97
  };
98
98
  };
99
99
  /**
100
- * Options that are passed onto replayEvents, that are shared by the replay, run-all-tests, and create-test commands
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 replayEvents, that are shared by the replay, run-all-tests, and create-test commands
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,18 @@
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 replay_debugger_ui_1 = require("./utils/replay-debugger.ui");
7
9
  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
10
  if (!screenshot && storyboard) {
9
11
  throw new Error("Cannot take storyboard screenshots without taking end state screenshots. Please set '--screenshot' to true, or '--storyboard' to false.");
10
12
  }
13
+ if (headless && enableStepThroughDebugger) {
14
+ throw new Error("Cannot run with both --debugger flag and --headless flag at the same time.");
15
+ }
11
16
  const executionOptions = {
12
17
  headless,
13
18
  devTools,
@@ -41,7 +46,9 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
41
46
  },
42
47
  }
43
48
  : { enabled: false };
44
- const { replay } = await (0, replay_orchestrator_1.replayAndStoreResults)({
49
+ const getOnBeforeUserEventCallback = (0, common_1.defer)();
50
+ const getOnClosePageCallback = (0, common_1.defer)();
51
+ const replayExecution = await (0, replay_orchestrator_1.replayAndStoreResults)({
45
52
  replayTarget: (0, exports.getReplayTarget)({
46
53
  appUrl: appUrl !== null && appUrl !== void 0 ? appUrl : null,
47
54
  simulationIdForAssets: simulationIdForAssets !== null && simulationIdForAssets !== void 0 ? simulationIdForAssets : null,
@@ -54,9 +61,24 @@ const rawReplayCommandHandler = async ({ apiToken, commitSha, sessionId, appUrl,
54
61
  sessionId,
55
62
  generatedBy: generatedByOption,
56
63
  testRunId: null,
57
- debugger: enableStepThroughDebugger,
58
64
  suppressScreenshotDiffLogging: false,
65
+ ...(enableStepThroughDebugger
66
+ ? {
67
+ onBeforeUserEvent: async (options) => (await getOnBeforeUserEventCallback.promise)(options),
68
+ onClosePage: async () => (await getOnClosePageCallback.promise)(),
69
+ }
70
+ : {}),
59
71
  });
72
+ if (enableStepThroughDebugger) {
73
+ const stepThroughDebuggerUI = await (0, replay_debugger_ui_1.openStepThroughDebuggerUI)({
74
+ onLogEventTarget: replayExecution.logEventTarget,
75
+ onCloseReplayedPage: replayExecution.closePage,
76
+ replayableEvents: replayExecution.eventsBeingReplayed,
77
+ });
78
+ getOnBeforeUserEventCallback.resolve(stepThroughDebuggerUI.onBeforeUserEvent);
79
+ getOnClosePageCallback.resolve(stepThroughDebuggerUI.close);
80
+ }
81
+ const { replay } = await replayExecution.finalResult;
60
82
  return replay;
61
83
  };
62
84
  exports.rawReplayCommandHandler = rawReplayCommandHandler;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwaysmeticulous/cli",
3
- "version": "2.41.0",
3
+ "version": "2.42.0",
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.41.0",
31
- "@alwaysmeticulous/client": "^2.41.0",
32
- "@alwaysmeticulous/common": "^2.41.0",
33
- "@alwaysmeticulous/downloading-helpers": "^2.41.0",
34
- "@alwaysmeticulous/record": "^2.41.0",
35
- "@alwaysmeticulous/replay-orchestrator": "^2.41.0",
36
- "@alwaysmeticulous/sdk-bundles-api": "^2.41.0",
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.0",
37
+ "@alwaysmeticulous/sdk-bundles-api": "^2.42.0",
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": "778895a7cd6a010538e338f9b2da225e71c53f48"
82
+ "gitHead": "65e2410a784642674ea6c8203d5b9f8a303c33ea"
81
83
  }