donobu 5.60.3 → 5.60.5

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.
@@ -1378,16 +1378,16 @@ async function attemptAutoHealRun(params) {
1378
1378
  envOverrides.DONOBU_PAGE_AI_CLEAR_CACHE_FILES =
1379
1379
  JSON.stringify(clearCacheFiles);
1380
1380
  }
1381
- // The rerun plan drives the runtime gate in the test fixture: within the
1382
- // gated projects, only the heal targets (and their declared
1383
- // `describe.serial` siblings) execute. Projects outside the gate
1384
- // declared dependencies of target projects, teardown projectsrun in
1385
- // full, exactly as Playwright schedules them.
1381
+ // The rerun plan drives the runtime gate in the test wrapper: within the
1382
+ // target projects, only the heal targets (and their declared
1383
+ // `describe.serial` siblings) execute; every other collected test skips.
1384
+ // Declared dependency (setup) projects still run in full the gate
1385
+ // exempts any test whose project is not a heal-target project, so the
1386
+ // storage-state auth and fixture seeding the targets rely on are produced
1387
+ // (see healRerunGate). Each target carries its `projectName` so the gate
1388
+ // can tell a target project apart from a dependency one.
1386
1389
  const healPlanPath = path.join(staging.rootDir, 'heal-rerun-plan.json');
1387
- await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
1388
- targets: healTargets,
1389
- gatedProjects: (0, healRerunGate_1.computeGatedProjects)(healTargets.map((target) => target.projectName), params.initialReport?.metadata?.projectDependencies),
1390
- }), 'utf8');
1390
+ await fs_1.promises.writeFile(healPlanPath, JSON.stringify({ targets: healTargets }), 'utf8');
1391
1391
  envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
1392
1392
  Logger_1.appLogger.info(`Auto-heal: re-running ${healTargets.length} targeted test(s) from ${evaluation.eligiblePlans.length} treatment plan(s)...`);
1393
1393
  const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
@@ -2182,10 +2182,8 @@ async function runHealCommand(cliArgs) {
2182
2182
  applyJsonReportEnv(envOverrides, playwrightOutputDir);
2183
2183
  // Downstream hooks check this flag to avoid recursive auto-heal loops.
2184
2184
  envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
2185
- // Same runtime gating as the automatic rerun: only the plan's test (plus
2186
- // declared `describe.serial` siblings) executes within its own project;
2187
- // dependency projects run in full. Without an initial report there is no
2188
- // dependency graph, so the gate scopes to just the target's project.
2185
+ // Same collection-time gating as the automatic rerun: only the plan's test
2186
+ // (plus declared `describe.serial` siblings) executes.
2189
2187
  const healPlanPath = path.join(os.tmpdir(), `donobu-heal-rerun-plan-${Date.now()}.json`);
2190
2188
  await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
2191
2189
  targets: [
@@ -2195,7 +2193,6 @@ async function runHealCommand(cliArgs) {
2195
2193
  projectName: persisted.failure.testCase.projectName,
2196
2194
  },
2197
2195
  ],
2198
- gatedProjects: (0, healRerunGate_1.computeGatedProjects)([persisted.failure.testCase.projectName], undefined),
2199
2196
  }), 'utf8');
2200
2197
  envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
2201
2198
  Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
@@ -1378,16 +1378,16 @@ async function attemptAutoHealRun(params) {
1378
1378
  envOverrides.DONOBU_PAGE_AI_CLEAR_CACHE_FILES =
1379
1379
  JSON.stringify(clearCacheFiles);
1380
1380
  }
1381
- // The rerun plan drives the runtime gate in the test fixture: within the
1382
- // gated projects, only the heal targets (and their declared
1383
- // `describe.serial` siblings) execute. Projects outside the gate
1384
- // declared dependencies of target projects, teardown projectsrun in
1385
- // full, exactly as Playwright schedules them.
1381
+ // The rerun plan drives the runtime gate in the test wrapper: within the
1382
+ // target projects, only the heal targets (and their declared
1383
+ // `describe.serial` siblings) execute; every other collected test skips.
1384
+ // Declared dependency (setup) projects still run in full the gate
1385
+ // exempts any test whose project is not a heal-target project, so the
1386
+ // storage-state auth and fixture seeding the targets rely on are produced
1387
+ // (see healRerunGate). Each target carries its `projectName` so the gate
1388
+ // can tell a target project apart from a dependency one.
1386
1389
  const healPlanPath = path.join(staging.rootDir, 'heal-rerun-plan.json');
1387
- await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
1388
- targets: healTargets,
1389
- gatedProjects: (0, healRerunGate_1.computeGatedProjects)(healTargets.map((target) => target.projectName), params.initialReport?.metadata?.projectDependencies),
1390
- }), 'utf8');
1390
+ await fs_1.promises.writeFile(healPlanPath, JSON.stringify({ targets: healTargets }), 'utf8');
1391
1391
  envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
1392
1392
  Logger_1.appLogger.info(`Auto-heal: re-running ${healTargets.length} targeted test(s) from ${evaluation.eligiblePlans.length} treatment plan(s)...`);
1393
1393
  const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
@@ -2182,10 +2182,8 @@ async function runHealCommand(cliArgs) {
2182
2182
  applyJsonReportEnv(envOverrides, playwrightOutputDir);
2183
2183
  // Downstream hooks check this flag to avoid recursive auto-heal loops.
2184
2184
  envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
2185
- // Same runtime gating as the automatic rerun: only the plan's test (plus
2186
- // declared `describe.serial` siblings) executes within its own project;
2187
- // dependency projects run in full. Without an initial report there is no
2188
- // dependency graph, so the gate scopes to just the target's project.
2185
+ // Same collection-time gating as the automatic rerun: only the plan's test
2186
+ // (plus declared `describe.serial` siblings) executes.
2189
2187
  const healPlanPath = path.join(os.tmpdir(), `donobu-heal-rerun-plan-${Date.now()}.json`);
2190
2188
  await fs_1.promises.writeFile(healPlanPath, JSON.stringify({
2191
2189
  targets: [
@@ -2195,7 +2193,6 @@ async function runHealCommand(cliArgs) {
2195
2193
  projectName: persisted.failure.testCase.projectName,
2196
2194
  },
2197
2195
  ],
2198
- gatedProjects: (0, healRerunGate_1.computeGatedProjects)([persisted.failure.testCase.projectName], undefined),
2199
2196
  }), 'utf8');
2200
2197
  envOverrides.DONOBU_AUTO_HEAL_PLAN_PATH = healPlanPath;
2201
2198
  Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
@@ -22,7 +22,15 @@
22
22
  * `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
23
23
  * Donobu reporter recorded during the initial run — the runner process
24
24
  * sees the suite tree; the worker (where this gate runs) does not.
25
- * - Declared dependency projects, which Playwright always runs in full.
25
+ * - Declared dependency (setup) projects, in full. Playwright schedules them
26
+ * because a target project lists them in `dependencies`, and the rerun
27
+ * command only ever passes `--project=<target>`, so any project that runs
28
+ * and is NOT a heal-target project is, by construction, such a dependency
29
+ * project. The gate exempts every test whose project is not a target
30
+ * project, so the auth/storage-state and fixture seeding those projects
31
+ * perform actually runs. (A prior version skipped them too — every test
32
+ * not literally in the plan — which broke targets that depend on the
33
+ * `.auth` storage state a setup project produces.)
26
34
  *
27
35
  * Implicit ordering (checkpoint files between plain tests, cross-file state
28
36
  * with `workers: 1`) is deliberately NOT honored: tests relying on it will
@@ -44,34 +52,43 @@ export interface HealRerunPlan {
44
52
  title: string;
45
53
  projectName?: string;
46
54
  }>;
47
- /**
48
- * Projects the gate applies to. Tests in any other project — declared
49
- * dependency projects (auth/setup), teardown projects — run untouched,
50
- * exactly as Playwright schedules them. Computed by the orchestrator via
51
- * `computeGatedProjects`; when absent, the gate falls back to the targets'
52
- * own project names.
53
- */
54
- gatedProjects?: string[];
55
55
  }
56
- /** Targets indexed by absolute spec path for O(1) per-test decisions. */
57
- export type HealRerunPlanIndex = Map<string, Set<string>>;
58
- export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
59
56
  /**
60
- * The projects the rerun gate should apply to: the heal targets' own
61
- * projects, minus any project that is a declared (transitive) dependency of
62
- * another target project the dependency declaration wins, and that project
63
- * runs in full.
57
+ * Heal-rerun targets indexed for O(1) per-test decisions.
58
+ *
59
+ * `byFile` maps an absolute spec path to the heal-target titles in that file.
60
+ * `targetProjects` is the set of projects that own those targets; the rerun is
61
+ * launched with `--project=<target>` only, so any project Playwright also runs
62
+ * is there purely as a declared dependency (setup) project — see
63
+ * `shouldRunDuringHealRerun`. Empty when no target carries a project name, in
64
+ * which case the gate degrades to pure file+title matching.
64
65
  */
65
- export declare function computeGatedProjects(targetProjects: Array<string | undefined>, projectDependencies: Record<string, string[]> | undefined): string[];
66
+ export interface HealRerunPlanIndex {
67
+ byFile: Map<string, Set<string>>;
68
+ targetProjects: Set<string>;
69
+ }
70
+ export declare function buildPlanIndex(plan: HealRerunPlan): HealRerunPlanIndex;
66
71
  /**
67
- * Pure decision: should the test in `file` with `title` actually execute
68
- * during the heal rerun? The plan is fully explicit — serial companions were
69
- * already expanded into it by the orchestrator.
72
+ * Pure decision: should the test in `file` with `title` (owned by project
73
+ * `projectName`) actually execute during the heal rerun? The plan is fully
74
+ * explicit — serial companions were already expanded into it by the
75
+ * orchestrator.
76
+ *
77
+ * Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
78
+ * Playwright because a target project declares them in `dependencies`. They
79
+ * are never themselves heal targets, but their tests MUST run so the state the
80
+ * targets depend on (storage-state auth files, seeded documents) is in place
81
+ * before the rerun. The rerun command only ever passes `--project=<target>`,
82
+ * so any project Playwright runs that is not a target project is, by
83
+ * construction, such a dependency project — run it in full. Guarded on a
84
+ * non-empty target-project set so the gate degrades to pure file+title
85
+ * matching when project names are unavailable.
70
86
  */
71
87
  export declare function shouldRunDuringHealRerun(params: {
72
88
  index: HealRerunPlanIndex;
73
89
  file: string;
74
90
  title: string;
91
+ projectName?: string;
75
92
  }): boolean;
76
93
  /**
77
94
  * Expand heal targets with their `describe.serial` siblings, using the
@@ -91,10 +108,8 @@ export declare function expandTargetsWithSerialCompanions(targets: HealRerunPlan
91
108
  export declare function resetHealRerunPlanCacheForTesting(): void;
92
109
  /**
93
110
  * Called from the Donobu auto fixture before any browser fixture initializes.
94
- * Outside heal reruns this is a no-op. During a rerun, tests in gated
95
- * projects that are outside the plan are annotated and skipped on the spot —
96
- * no context, no page, no cost. Tests in ungated projects (declared
97
- * dependencies of the targets, teardown projects) always run.
111
+ * Outside heal reruns this is a no-op. During a rerun, tests outside the plan
112
+ * are annotated and skipped on the spot — no context, no page, no cost.
98
113
  */
99
114
  export declare function maybeSkipForHealRerun(testInfo: TestInfo, options?: {
100
115
  planPath?: string;
@@ -23,7 +23,15 @@
23
23
  * `expandTargetsWithSerialCompanions`) using the `serialScoped` flags the
24
24
  * Donobu reporter recorded during the initial run — the runner process
25
25
  * sees the suite tree; the worker (where this gate runs) does not.
26
- * - Declared dependency projects, which Playwright always runs in full.
26
+ * - Declared dependency (setup) projects, in full. Playwright schedules them
27
+ * because a target project lists them in `dependencies`, and the rerun
28
+ * command only ever passes `--project=<target>`, so any project that runs
29
+ * and is NOT a heal-target project is, by construction, such a dependency
30
+ * project. The gate exempts every test whose project is not a target
31
+ * project, so the auth/storage-state and fixture seeding those projects
32
+ * perform actually runs. (A prior version skipped them too — every test
33
+ * not literally in the plan — which broke targets that depend on the
34
+ * `.auth` storage state a setup project produces.)
27
35
  *
28
36
  * Implicit ordering (checkpoint files between plain tests, cross-file state
29
37
  * with `workers: 1`) is deliberately NOT honored: tests relying on it will
@@ -41,7 +49,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
49
  };
42
50
  Object.defineProperty(exports, "__esModule", { value: true });
43
51
  exports.buildPlanIndex = buildPlanIndex;
44
- exports.computeGatedProjects = computeGatedProjects;
45
52
  exports.shouldRunDuringHealRerun = shouldRunDuringHealRerun;
46
53
  exports.expandTargetsWithSerialCompanions = expandTargetsWithSerialCompanions;
47
54
  exports.resetHealRerunPlanCacheForTesting = resetHealRerunPlanCacheForTesting;
@@ -52,52 +59,47 @@ const envVars_1 = require("../../envVars");
52
59
  const model_1 = require("../../reporter/model");
53
60
  const Logger_1 = require("../../utils/Logger");
54
61
  function buildPlanIndex(plan) {
55
- const index = new Map();
62
+ const byFile = new Map();
63
+ const targetProjects = new Set();
56
64
  for (const target of plan.targets ?? []) {
57
65
  if (!target?.file || !target?.title) {
58
66
  continue;
59
67
  }
60
68
  const file = path_1.default.resolve(target.file);
61
- if (!index.has(file)) {
62
- index.set(file, new Set());
69
+ if (!byFile.has(file)) {
70
+ byFile.set(file, new Set());
63
71
  }
64
- index.get(file).add(target.title);
65
- }
66
- return index;
67
- }
68
- /**
69
- * The projects the rerun gate should apply to: the heal targets' own
70
- * projects, minus any project that is a declared (transitive) dependency of
71
- * another target project — the dependency declaration wins, and that project
72
- * runs in full.
73
- */
74
- function computeGatedProjects(targetProjects, projectDependencies) {
75
- const targets = [
76
- ...new Set(targetProjects.filter((name) => Boolean(name))),
77
- ];
78
- if (!projectDependencies) {
79
- return targets;
80
- }
81
- // Every project reachable through any target's declared dependency chain.
82
- const reachable = new Set();
83
- const visit = (name) => {
84
- for (const dependency of projectDependencies[name] ?? []) {
85
- if (!reachable.has(dependency)) {
86
- reachable.add(dependency);
87
- visit(dependency);
88
- }
72
+ byFile.get(file).add(target.title);
73
+ if (target.projectName) {
74
+ targetProjects.add(target.projectName);
89
75
  }
90
- };
91
- targets.forEach(visit);
92
- return targets.filter((name) => !reachable.has(name));
76
+ }
77
+ return { byFile, targetProjects };
93
78
  }
94
79
  /**
95
- * Pure decision: should the test in `file` with `title` actually execute
96
- * during the heal rerun? The plan is fully explicit — serial companions were
97
- * already expanded into it by the orchestrator.
80
+ * Pure decision: should the test in `file` with `title` (owned by project
81
+ * `projectName`) actually execute during the heal rerun? The plan is fully
82
+ * explicit — serial companions were already expanded into it by the
83
+ * orchestrator.
84
+ *
85
+ * Dependency/setup projects (auth login, fixture seeding, …) are pulled in by
86
+ * Playwright because a target project declares them in `dependencies`. They
87
+ * are never themselves heal targets, but their tests MUST run so the state the
88
+ * targets depend on (storage-state auth files, seeded documents) is in place
89
+ * before the rerun. The rerun command only ever passes `--project=<target>`,
90
+ * so any project Playwright runs that is not a target project is, by
91
+ * construction, such a dependency project — run it in full. Guarded on a
92
+ * non-empty target-project set so the gate degrades to pure file+title
93
+ * matching when project names are unavailable.
98
94
  */
99
95
  function shouldRunDuringHealRerun(params) {
100
- const titles = params.index.get(path_1.default.resolve(params.file));
96
+ const { index, projectName } = params;
97
+ if (projectName !== undefined &&
98
+ index.targetProjects.size > 0 &&
99
+ !index.targetProjects.has(projectName)) {
100
+ return true;
101
+ }
102
+ const titles = index.byFile.get(path_1.default.resolve(params.file));
101
103
  return titles?.has(params.title) ?? false;
102
104
  }
103
105
  /**
@@ -160,61 +162,47 @@ function expandTargetsWithSerialCompanions(targets, initialReport) {
160
162
  }
161
163
  return expanded;
162
164
  }
163
- let cachedPlan;
164
- function loadPlan(planPathOverride) {
165
- if (planPathOverride === undefined && cachedPlan !== undefined) {
166
- return cachedPlan;
165
+ let cachedPlanIndex;
166
+ function getPlanIndex(planPathOverride) {
167
+ if (planPathOverride === undefined && cachedPlanIndex !== undefined) {
168
+ return cachedPlanIndex;
167
169
  }
168
170
  const planPath = planPathOverride ?? envVars_1.env.data.DONOBU_AUTO_HEAL_PLAN_PATH;
169
- let loaded = null;
171
+ let index = null;
170
172
  if (planPath) {
171
173
  try {
172
174
  const raw = fs_1.default.readFileSync(planPath, 'utf8');
173
- const plan = JSON.parse(raw);
174
- loaded = {
175
- index: buildPlanIndex(plan),
176
- gatedProjects: new Set(plan.gatedProjects ??
177
- // Older plans carry no project list — gate the targets' own
178
- // projects, leaving everything else (dependencies) untouched.
179
- plan.targets
180
- ?.map((target) => target.projectName)
181
- .filter((name) => Boolean(name)) ??
182
- []),
183
- };
175
+ index = buildPlanIndex(JSON.parse(raw));
184
176
  }
185
177
  catch (error) {
186
178
  Logger_1.appLogger.warn(`Auto-heal rerun plan at ${planPath} could not be read; running all collected tests.`, error);
187
- loaded = null;
179
+ index = null;
188
180
  }
189
181
  }
190
182
  if (planPathOverride === undefined) {
191
- cachedPlan = loaded;
183
+ cachedPlanIndex = index;
192
184
  }
193
- return loaded;
185
+ return index;
194
186
  }
195
187
  /** Test-only: reset the memoized plan so each test can load its own. */
196
188
  function resetHealRerunPlanCacheForTesting() {
197
- cachedPlan = undefined;
189
+ cachedPlanIndex = undefined;
198
190
  }
199
191
  /**
200
192
  * Called from the Donobu auto fixture before any browser fixture initializes.
201
- * Outside heal reruns this is a no-op. During a rerun, tests in gated
202
- * projects that are outside the plan are annotated and skipped on the spot —
203
- * no context, no page, no cost. Tests in ungated projects (declared
204
- * dependencies of the targets, teardown projects) always run.
193
+ * Outside heal reruns this is a no-op. During a rerun, tests outside the plan
194
+ * are annotated and skipped on the spot — no context, no page, no cost.
205
195
  */
206
196
  function maybeSkipForHealRerun(testInfo, options) {
207
- const plan = loadPlan(options?.planPath);
208
- if (!plan) {
209
- return;
210
- }
211
- if (!plan.gatedProjects.has(testInfo.project.name)) {
197
+ const index = getPlanIndex(options?.planPath);
198
+ if (!index) {
212
199
  return;
213
200
  }
214
201
  const shouldRun = shouldRunDuringHealRerun({
215
- index: plan.index,
202
+ index,
216
203
  file: testInfo.file,
217
204
  title: testInfo.title,
205
+ projectName: testInfo.project?.name,
218
206
  });
219
207
  if (shouldRun) {
220
208
  return;
@@ -122,28 +122,44 @@ async function waitForPendingVideoPersists(timeoutMs) {
122
122
  * - `'on'` → always retain → always persist.
123
123
  * - `'retain-on-failure'` → retain only on non-passing → persist
124
124
  * only when status !== 'passed'.
125
+ * - `'retain-on-first-failure'` → recorded only on the first run; a video
126
+ * only exists when that run failed → same
127
+ * persist condition as 'retain-on-failure'.
128
+ * - `'retain-on-failure-and-retries'` → retain on non-passing OR on any
129
+ * retry → persist when status !== 'passed'
130
+ * or retry > 0.
125
131
  * - `'on-first-retry'` → recorded only on retries; if a video
126
132
  * exists, we're on a retry → persist.
133
+ * - `'on-all-retries'` → recorded on every retry; same as
134
+ * 'on-first-retry' once a video exists.
127
135
  * - any unknown future mode → conservatively SKIP, with a warn log.
128
136
  * Better to under-persist than violate
129
137
  * user intent for a mode we don't yet
130
138
  * understand.
131
139
  */
132
- function shouldPersistVideo(videoOption, status) {
140
+ function shouldPersistVideo(videoOption, status, retry) {
133
141
  const mode = typeof videoOption === 'string' ? videoOption : videoOption?.mode;
134
142
  switch (mode) {
135
143
  case 'on':
136
144
  return true;
137
145
  case 'on-first-retry':
138
- // When set, Playwright only records on retries; if a video exists
139
- // it implies we're on a retry always retain.
146
+ case 'on-all-retries':
147
+ // Playwright only records on retries under these modes; if a video
148
+ // exists it implies we're on a retry — always retain.
140
149
  return true;
141
150
  case 'retry-with-video':
142
151
  // Deprecated alias for 'on-first-retry' that Playwright still
143
152
  // accepts on the type. Same retain semantics.
144
153
  return true;
145
154
  case 'retain-on-failure':
155
+ case 'retain-on-first-failure':
156
+ // Both record up front and discard on a passing run. ('first-failure'
157
+ // only records the first run, but a video existing already implies
158
+ // that's the run we're persisting.)
146
159
  return status !== 'passed';
160
+ case 'retain-on-failure-and-retries':
161
+ // Records every run; kept on failure OR on any retry attempt.
162
+ return status !== 'passed' || retry > 0;
147
163
  case 'off':
148
164
  case undefined:
149
165
  return false;
@@ -182,7 +198,7 @@ function persistVideoIfApplicable(page, testInfo, videoOption) {
182
198
  // and this isn't a retry, etc. Nothing to do.
183
199
  return;
184
200
  }
185
- if (!shouldPersistVideo(videoOption, testInfo.status)) {
201
+ if (!shouldPersistVideo(videoOption, testInfo.status, testInfo.retry)) {
186
202
  Logger_1.appLogger.info(`Skipping video persist for flow ${flowId}: video mode ` +
187
203
  `"${describeVideoMode(videoOption)}" + status "${testInfo.status}" ` +
188
204
  `means Playwright will discard the file and we'd be violating user ` +
@@ -5,11 +5,16 @@ export declare class InteractionVisualizer {
5
5
  private static readonly TIP_X;
6
6
  private static readonly TIP_Y;
7
7
  private static readonly SVG_MOUSE;
8
- private static readonly SVG_MOUSE_JSON;
9
- private static readonly CSS;
10
- private static readonly CONTAINER_ID;
11
- private cursorPos;
8
+ /** Message tooltip box styling — mirrors the legacy `.donobu-message` rule. */
9
+ private static readonly MESSAGE_MAX_WIDTH;
10
+ private static readonly MESSAGE_MARGIN;
11
+ /**
12
+ * Per-page cursor state. Keyed weakly so closed/GC'd pages drop out without
13
+ * us tracking page lifecycles.
14
+ */
15
+ private readonly states;
12
16
  constructor(defaultMessageDurationMillis: number);
17
+ private static escapeHtml;
13
18
  /**
14
19
  * Moves the virtual cursor to the center of the specified element and optionally displays a message.
15
20
  *
@@ -22,39 +27,45 @@ export declare class InteractionVisualizer {
22
27
  * @returns Promise that resolves when the cursor movement and message display are complete
23
28
  *
24
29
  * @remarks
25
- * - The target element will be scrolled into view if necessary
26
30
  * - The cursor animates smoothly to the element's center point
27
31
  * - Messages are positioned automatically to avoid viewport edges
28
- * - The virtual cursor does not interfere with actual page interactions
32
+ * - Overlays are `pointer-events: none`, so they never intercept real interactions
29
33
  */
30
34
  pointAt(page: Page, locator?: Pick<Locator, 'boundingBox'>, message?: string, duration?: number): Promise<void>;
31
35
  /**
32
- * Shows the virtual mouse cursor on the page.
36
+ * Shows the virtual mouse cursor on the page at its current position.
33
37
  *
34
38
  * @param page - The Playwright page instance where the cursor will be displayed.
35
- *
36
39
  * @returns Promise that resolves when the cursor is shown.
37
- *
38
- * @remarks
39
- * - If the cursor doesn't exist yet, it will be created.
40
- * - The cursor will be made visible with a smooth opacity transition.
41
40
  */
42
41
  showMouse(page: Page): Promise<void>;
43
42
  /**
44
43
  * Hides the virtual mouse cursor on the page.
45
44
  *
46
45
  * @param page - The Playwright page instance where the cursor is displayed.
47
- *
48
- * @returns Promise that resolves when the cursor is hidden
49
- *
50
- * @remarks
51
- * - The cursor will be hidden with a smooth opacity transition
52
- * - The cursor element remains in the DOM but becomes invisible
46
+ * @returns Promise that resolves when the cursor is hidden.
53
47
  */
54
48
  hideMouse(page: Page): Promise<void>;
55
- private ensureContainer;
56
- private ensureCursor;
57
- private moveCursor;
49
+ private getState;
50
+ /**
51
+ * Replaces the sticky cursor overlay with one positioned at `to`, gliding
52
+ * from `from` when `animMs > 0`. We show the new overlay before disposing
53
+ * the old one to avoid a visible flicker, and tolerate a stale disposable
54
+ * (e.g. cleared by a navigation) by swallowing its dispose error.
55
+ */
56
+ private renderCursor;
58
57
  private showMessage;
58
+ /**
59
+ * Builds the cursor overlay markup: the arrow SVG plus a `@keyframes` glide
60
+ * from the previous position and a one-shot ripple at the arrow tip.
61
+ */
62
+ private buildCursorHtml;
63
+ /**
64
+ * Builds the message tooltip markup, positioned near `target` and clamped
65
+ * inside the viewport. Without being able to measure the rendered box, we
66
+ * keep it on-screen with a conservative max-width clamp and anchor it above
67
+ * the cursor (via `translateY(-100%)`) when the cursor sits low in the page.
68
+ */
69
+ private buildMessageHtml;
59
70
  }
60
71
  //# sourceMappingURL=InteractionVisualizer.d.ts.map