@riddledc/riddle-proof 0.7.57 → 0.7.59

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/README.md CHANGED
@@ -240,7 +240,7 @@ where the second request must carry newer state.
240
240
  `target.setup_actions` is optional. Use it when the meaningful proof surface
241
241
  appears only after a picker, tab, login stub, storage seed, form fill,
242
242
  transport control, or other bounded interaction. Supported setup actions are
243
- `click`, `fill`, `set_input_value`, `assert_text_visible`,
243
+ `click`, `press`, `fill`, `set_input_value`, `assert_text_visible`,
244
244
  `assert_text_absent`, `assert_selector_count`, `assert_window_value`,
245
245
  `assert_window_number`, `local_storage`, `session_storage`, `clear_storage`, `wait`,
246
246
  `wait_for_selector`, `wait_for_text`, and `window_call`; a failed setup action
@@ -249,8 +249,11 @@ pass without reaching the intended state. Text-matched `click` actions prefer
249
249
  visible matching elements, which keeps responsive layouts from selecting hidden
250
250
  desktop or mobile-only links. Add `force: true` to a click action only when the
251
251
  matched visible element is intentionally animated or otherwise never becomes
252
- stable enough for Playwright's default click actionability checks. Use setup
253
- assertions when the pre-click or pre-navigation state is part of the contract,
252
+ stable enough for Playwright's default click actionability checks. Use `press`
253
+ with a Playwright key name, such as `Enter`, `Space`, or `ArrowLeft`,
254
+ when a route's intended browser control is keyboard-driven; omit `selector` for
255
+ a page-level key press, or provide `selector` to press against a focused element.
256
+ Use setup assertions when the pre-click or pre-navigation state is part of the contract,
254
257
  for example a fresh row must be present, stale copy must be absent, exactly one
255
258
  source link must exist before clicking into the final route, or a canvas app's
256
259
  proof state must expose a terminal flag. `assert_selector_count` accepts
@@ -267,6 +270,13 @@ can include `repeat` / `repeat_count` / `times` from 1 to 100; each repetition
267
270
  is recorded with `repeat_index` and `repeat_count`, and `after_ms` runs after
268
271
  each repetition. Use it for bounded game proof helpers, retry controls, or other
269
272
  workflows where one declarative action needs to advance the app several times.
273
+ Add `frame_selector` / `frameSelector` to a setup action when the interaction
274
+ target lives inside an embedded iframe, such as a community game player or
275
+ hosted preview surface. Selector-based actions, storage actions, window calls,
276
+ and setup assertions then execute in that frame context and record
277
+ `frame_selector`, `frame_index`, and `frame_count` in setup-action evidence.
278
+ Use `frame_index` / `frameIndex` when more than one matching iframe is present;
279
+ it defaults to the first frame.
270
280
 
271
281
  `target.timeout_sec` is optional. Use it for known-heavy profile targets so the
272
282
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
@@ -32,6 +32,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
32
32
  ];
33
33
  var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
34
34
  "click",
35
+ "press",
35
36
  "fill",
36
37
  "set_input_value",
37
38
  "assert_text_visible",
@@ -201,7 +202,7 @@ function isSupportedCheckType(value) {
201
202
  }
202
203
  function normalizeSetupActionType(value, index) {
203
204
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
204
- const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput;
205
+ const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput;
205
206
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
206
207
  return normalized;
207
208
  }
@@ -235,6 +236,11 @@ function normalizeSetupAction(input, index) {
235
236
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
236
237
  const type = normalizeSetupActionType(stringValue(input.type), index);
237
238
  const selector = stringValue(input.selector);
239
+ const frameSelector = stringFromOwn(input, "frame_selector", "frameSelector", "iframe_selector", "iframeSelector");
240
+ const frameIndex = numberValue(valueFromOwn(input, "frame_index", "frameIndex", "iframe_index", "iframeIndex"));
241
+ if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
242
+ throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
243
+ }
238
244
  if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
239
245
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
240
246
  }
@@ -254,6 +260,9 @@ function normalizeSetupAction(input, index) {
254
260
  throw new Error(`target.setup_actions[${index}] ${type} requires value.`);
255
261
  }
256
262
  const key = stringValue(input.key);
263
+ if (type === "press" && !key) {
264
+ throw new Error(`target.setup_actions[${index}] ${type} requires key.`);
265
+ }
257
266
  if ((type === "local_storage" || type === "session_storage") && !key) {
258
267
  throw new Error(`target.setup_actions[${index}] ${type} requires key.`);
259
268
  }
@@ -284,6 +293,8 @@ function normalizeSetupAction(input, index) {
284
293
  return {
285
294
  type,
286
295
  selector,
296
+ frame_selector: frameSelector,
297
+ frame_index: frameIndex,
287
298
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
288
299
  key,
289
300
  value,
@@ -1170,6 +1181,8 @@ function assessSetupActionsFromEvidence(profile, evidence) {
1170
1181
  viewport: viewport.name,
1171
1182
  action: result.action ?? result.type ?? null,
1172
1183
  selector: result.selector ?? null,
1184
+ frame_selector: result.frame_selector ?? null,
1185
+ frame_index: result.frame_index ?? null,
1173
1186
  reason: result.reason ?? result.error ?? null
1174
1187
  });
1175
1188
  }
@@ -1682,10 +1695,12 @@ function assessProfile(profile, evidence) {
1682
1695
  if (result && result.ok === false) {
1683
1696
  failed.push({
1684
1697
  viewport: viewport.name,
1685
- action: result.action || result.type || null,
1686
- selector: result.selector || null,
1687
- reason: result.reason || result.error || null,
1688
- });
1698
+ action: result.action || result.type || null,
1699
+ selector: result.selector || null,
1700
+ frame_selector: result.frame_selector || null,
1701
+ frame_index: result.frame_index ?? null,
1702
+ reason: result.reason || result.error || null,
1703
+ });
1689
1704
  }
1690
1705
  }
1691
1706
  if (results.length < actionCount && results.every((result) => !result || result.ok !== false)) {
@@ -2148,8 +2163,8 @@ function setupJsonValue(value) {
2148
2163
  function setupValuesEqual(left, right) {
2149
2164
  return JSON.stringify(setupJsonValue(left)) === JSON.stringify(setupJsonValue(right));
2150
2165
  }
2151
- async function setupReadWindowValue(path) {
2152
- return await page.evaluate(({ path }) => {
2166
+ async function setupReadWindowValue(context, path) {
2167
+ return await context.evaluate(({ path }) => {
2153
2168
  const toJsonValue = (value) => {
2154
2169
  if (value === null || value === undefined) return null;
2155
2170
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2171,6 +2186,80 @@ async function setupReadWindowValue(path) {
2171
2186
  return { ok: true, value: toJsonValue(current) };
2172
2187
  }, { path });
2173
2188
  }
2189
+ function setupFrameSelector(action) {
2190
+ return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
2191
+ }
2192
+ function setupFrameIndex(action) {
2193
+ const raw = action?.frame_index ?? action?.frameIndex ?? action?.iframe_index ?? action?.iframeIndex ?? 0;
2194
+ const number = Number(raw);
2195
+ return Number.isInteger(number) && number >= 0 ? number : 0;
2196
+ }
2197
+ function setupScopeEvidence(scope) {
2198
+ if (!scope || !scope.frame_selector) return {};
2199
+ return {
2200
+ frame_selector: scope.frame_selector,
2201
+ frame_index: scope.frame_index,
2202
+ frame_count: scope.frame_count,
2203
+ };
2204
+ }
2205
+ function setupScopeFailure(base, scope) {
2206
+ return {
2207
+ ...base,
2208
+ ...setupScopeEvidence(scope),
2209
+ reason: scope?.reason || "frame_scope_unavailable",
2210
+ error: scope?.error || undefined,
2211
+ };
2212
+ }
2213
+ async function setupActionScope(action, timeout) {
2214
+ const frameSelector = setupFrameSelector(action);
2215
+ if (!frameSelector) return { ok: true, context: page };
2216
+ const frameIndex = setupFrameIndex(action);
2217
+ let frameCount = 0;
2218
+ let locator = null;
2219
+ try {
2220
+ await page.waitForSelector(frameSelector, { state: "attached", timeout });
2221
+ locator = page.locator(frameSelector);
2222
+ frameCount = await locator.count();
2223
+ } catch (error) {
2224
+ return {
2225
+ ok: false,
2226
+ reason: "frame_selector_not_found",
2227
+ frame_selector: frameSelector,
2228
+ frame_index: frameIndex,
2229
+ frame_count: frameCount,
2230
+ error: String(error && error.message ? error.message : error).slice(0, 1000),
2231
+ };
2232
+ }
2233
+ if (!frameCount) {
2234
+ return { ok: false, reason: "frame_selector_not_found", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2235
+ }
2236
+ if (frameIndex >= frameCount) {
2237
+ return { ok: false, reason: "frame_index_out_of_range", frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2238
+ }
2239
+ const handle = await locator.nth(frameIndex).elementHandle({ timeout }).catch((error) => ({ __riddle_error: error }));
2240
+ if (!handle || handle.__riddle_error) {
2241
+ return {
2242
+ ok: false,
2243
+ reason: "frame_element_unavailable",
2244
+ frame_selector: frameSelector,
2245
+ frame_index: frameIndex,
2246
+ frame_count: frameCount,
2247
+ error: handle?.__riddle_error ? String(handle.__riddle_error && handle.__riddle_error.message ? handle.__riddle_error.message : handle.__riddle_error).slice(0, 1000) : undefined,
2248
+ };
2249
+ }
2250
+ const frame = typeof handle.contentFrame === "function" ? await handle.contentFrame().catch((error) => ({ __riddle_error: error })) : null;
2251
+ if (!frame || frame.__riddle_error) {
2252
+ return {
2253
+ ok: false,
2254
+ reason: "content_frame_unavailable",
2255
+ frame_selector: frameSelector,
2256
+ frame_index: frameIndex,
2257
+ frame_count: frameCount,
2258
+ error: frame?.__riddle_error ? String(frame.__riddle_error && frame.__riddle_error.message ? frame.__riddle_error.message : frame.__riddle_error).slice(0, 1000) : undefined,
2259
+ };
2260
+ }
2261
+ return { ok: true, context: frame, frame_selector: frameSelector, frame_index: frameIndex, frame_count: frameCount };
2262
+ }
2174
2263
  async function setupLocatorText(locator, index) {
2175
2264
  return await locator.nth(index).textContent({ timeout: 1000 }).catch(() => "");
2176
2265
  }
@@ -2332,7 +2421,8 @@ async function registerNetworkMocks(mocks) {
2332
2421
  }
2333
2422
  async function executeSetupAction(action, ordinal) {
2334
2423
  const type = setupActionType(action);
2335
- const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null };
2424
+ const frameSelector = setupFrameSelector(action);
2425
+ const base = { ok: false, action: type || "unknown", ordinal, selector: action.selector || null, frame_selector: frameSelector || null };
2336
2426
  const timeout = setupNumber(action.timeout_ms, 5000);
2337
2427
  try {
2338
2428
  if (type === "wait") {
@@ -2341,36 +2431,65 @@ async function executeSetupAction(action, ordinal) {
2341
2431
  return { ...base, ok: true, ms };
2342
2432
  }
2343
2433
  if (type === "wait_for_selector") {
2344
- await page.waitForSelector(action.selector, { state: "visible", timeout });
2345
- return { ...base, ok: true, timeout_ms: timeout };
2434
+ const scope = await setupActionScope(action, timeout);
2435
+ if (!scope.ok) return setupScopeFailure(base, scope);
2436
+ await scope.context.waitForSelector(action.selector, { state: "visible", timeout });
2437
+ return { ...base, ...setupScopeEvidence(scope), ok: true, timeout_ms: timeout };
2438
+ }
2439
+ if (type === "press") {
2440
+ const key = String(action.key || "").trim();
2441
+ if (!key) return { ...base, reason: "missing_key" };
2442
+ const scope = await setupActionScope(action, timeout);
2443
+ if (!scope.ok) return setupScopeFailure(base, scope);
2444
+ if (!action.selector) {
2445
+ if (scope.frame_selector) {
2446
+ await scope.context.locator("body").press(key, { timeout });
2447
+ } else {
2448
+ await page.keyboard.press(key);
2449
+ }
2450
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key };
2451
+ }
2452
+ const locator = scope.context.locator(action.selector);
2453
+ const count = await locator.count();
2454
+ if (!count) return { ...base, reason: "selector_not_found", count, key };
2455
+ const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2456
+ if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex, key };
2457
+ await locator.nth(targetIndex).press(key, { timeout });
2458
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, key };
2346
2459
  }
2347
2460
  if (type === "local_storage" || type === "session_storage") {
2348
2461
  const value = setupActionValue(action);
2349
- await page.evaluate(({ type, key, value }) => {
2462
+ const scope = await setupActionScope(action, timeout);
2463
+ if (!scope.ok) return setupScopeFailure(base, scope);
2464
+ await scope.context.evaluate(({ type, key, value }) => {
2350
2465
  const storage = type === "session_storage" ? window.sessionStorage : window.localStorage;
2351
2466
  storage.setItem(key, value);
2352
2467
  }, { type, key: action.key, value });
2353
2468
  if (action.reload === true) {
2354
2469
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2355
2470
  }
2356
- return { ...base, ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2471
+ return { ...base, ...setupScopeEvidence(scope), ok: true, key: action.key, value_length: value.length, reload: action.reload === true };
2357
2472
  }
2358
2473
  if (type === "clear_storage") {
2359
2474
  const storage = action.storage || "both";
2360
- await page.evaluate(({ storage }) => {
2475
+ const scope = await setupActionScope(action, timeout);
2476
+ if (!scope.ok) return setupScopeFailure(base, scope);
2477
+ await scope.context.evaluate(({ storage }) => {
2361
2478
  if (storage === "local" || storage === "both") window.localStorage.clear();
2362
2479
  if (storage === "session" || storage === "both") window.sessionStorage.clear();
2363
2480
  }, { storage });
2364
2481
  if (action.reload === true) {
2365
2482
  await page.reload({ waitUntil: "domcontentloaded", timeout: 45000 });
2366
2483
  }
2367
- return { ...base, ok: true, storage, reload: action.reload === true };
2484
+ return { ...base, ...setupScopeEvidence(scope), ok: true, storage, reload: action.reload === true };
2368
2485
  }
2369
2486
  if (type === "window_call") {
2370
2487
  const path = String(action.path || action.function_path || action.functionPath || "");
2371
2488
  const args = Array.isArray(action.args) ? action.args : [];
2372
2489
  if (!path) return { ...base, path, reason: "missing_path" };
2373
- const result = await page.evaluate(async ({ path, args }) => {
2490
+ const scope = await setupActionScope(action, timeout);
2491
+ if (!scope.ok) return setupScopeFailure(base, scope);
2492
+ const result = await scope.context.evaluate(async ({ path, args }) => {
2374
2493
  const toJsonValue = (value) => {
2375
2494
  if (value === null || value === undefined) return null;
2376
2495
  if (typeof value === "string" || typeof value === "boolean") return value;
@@ -2410,6 +2529,7 @@ async function executeSetupAction(action, ordinal) {
2410
2529
  const expectationMet = !hasExpectation || setupValuesEqual(result.returned, expected);
2411
2530
  return {
2412
2531
  ...base,
2532
+ ...setupScopeEvidence(scope),
2413
2533
  ok: Boolean(result.ok && expectationMet),
2414
2534
  path,
2415
2535
  arg_count: args.length,
@@ -2440,13 +2560,16 @@ async function executeSetupAction(action, ordinal) {
2440
2560
  : action.expect;
2441
2561
  if (!path) return { ...base, path, reason: "missing_path" };
2442
2562
  if (!hasExpected) return { ...base, path, reason: "missing_expected_value" };
2563
+ const scope = await setupActionScope(action, timeout);
2564
+ if (!scope.ok) return setupScopeFailure(base, scope);
2443
2565
  const startedAt = Date.now();
2444
2566
  let result = null;
2445
2567
  while (Date.now() - startedAt <= timeout) {
2446
- result = await setupReadWindowValue(path);
2568
+ result = await setupReadWindowValue(scope.context, path);
2447
2569
  if (result.ok && setupValuesEqual(result.value, expected)) {
2448
2570
  return {
2449
2571
  ...base,
2572
+ ...setupScopeEvidence(scope),
2450
2573
  ok: true,
2451
2574
  path,
2452
2575
  value: setupJsonValue(result.value),
@@ -2458,6 +2581,7 @@ async function executeSetupAction(action, ordinal) {
2458
2581
  }
2459
2582
  return {
2460
2583
  ...base,
2584
+ ...setupScopeEvidence(scope),
2461
2585
  path,
2462
2586
  reason: result?.ok ? "unexpected_value" : result?.reason || "path_not_found",
2463
2587
  missing_part: result?.missing_part || undefined,
@@ -2474,11 +2598,13 @@ async function executeSetupAction(action, ordinal) {
2474
2598
  const hasExpected = expected !== undefined;
2475
2599
  if (!path) return { ...base, path, reason: "missing_path" };
2476
2600
  if (!hasExpected && minValue === undefined && maxValue === undefined) return { ...base, path, reason: "missing_number_expectation" };
2601
+ const scope = await setupActionScope(action, timeout);
2602
+ if (!scope.ok) return setupScopeFailure(base, scope);
2477
2603
  const startedAt = Date.now();
2478
2604
  let result = null;
2479
2605
  let lastReason = "path_not_found";
2480
2606
  while (Date.now() - startedAt <= timeout) {
2481
- result = await setupReadWindowValue(path);
2607
+ result = await setupReadWindowValue(scope.context, path);
2482
2608
  if (result.ok) {
2483
2609
  const actual = setupFiniteNumber(result.value);
2484
2610
  if (actual === undefined) {
@@ -2492,6 +2618,7 @@ async function executeSetupAction(action, ordinal) {
2492
2618
  } else {
2493
2619
  return {
2494
2620
  ...base,
2621
+ ...setupScopeEvidence(scope),
2495
2622
  ok: true,
2496
2623
  path,
2497
2624
  value: actual,
@@ -2508,6 +2635,7 @@ async function executeSetupAction(action, ordinal) {
2508
2635
  }
2509
2636
  return {
2510
2637
  ...base,
2638
+ ...setupScopeEvidence(scope),
2511
2639
  path,
2512
2640
  reason: lastReason,
2513
2641
  missing_part: result?.missing_part || undefined,
@@ -2519,7 +2647,9 @@ async function executeSetupAction(action, ordinal) {
2519
2647
  };
2520
2648
  }
2521
2649
  if (type === "click") {
2522
- const locator = page.locator(action.selector);
2650
+ const scope = await setupActionScope(action, timeout);
2651
+ if (!scope.ok) return setupScopeFailure(base, scope);
2652
+ const locator = scope.context.locator(action.selector);
2523
2653
  const count = await locator.count();
2524
2654
  if (!count) return { ...base, reason: "selector_not_found", count };
2525
2655
  let targetIndex = Number.isInteger(action.index) ? action.index : 0;
@@ -2551,33 +2681,39 @@ async function executeSetupAction(action, ordinal) {
2551
2681
  ? { timeout, noWaitAfter: true, force: true }
2552
2682
  : { timeout, noWaitAfter: true };
2553
2683
  await locator.nth(targetIndex).click(clickOptions);
2554
- return { ...base, ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2684
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, text: matchedText, force: action.force === true || undefined };
2555
2685
  }
2556
2686
  if (type === "fill" || type === "set_input_value") {
2557
- const locator = page.locator(action.selector);
2687
+ const scope = await setupActionScope(action, timeout);
2688
+ if (!scope.ok) return setupScopeFailure(base, scope);
2689
+ const locator = scope.context.locator(action.selector);
2558
2690
  const count = await locator.count();
2559
2691
  if (!count) return { ...base, reason: "selector_not_found", count };
2560
2692
  const targetIndex = Number.isInteger(action.index) ? action.index : 0;
2561
2693
  if (targetIndex < 0 || targetIndex >= count) return { ...base, reason: "index_out_of_range", count, target_index: targetIndex };
2562
2694
  const value = setupActionValue(action);
2563
2695
  await locator.nth(targetIndex).fill(value, { timeout });
2564
- return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
2696
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, target_index: targetIndex, value_length: value.length };
2565
2697
  }
2566
2698
  if (type === "assert_selector_count") {
2567
- const locator = page.locator(action.selector);
2699
+ const scope = await setupActionScope(action, timeout);
2700
+ if (!scope.ok) return setupScopeFailure(base, scope);
2701
+ const locator = scope.context.locator(action.selector);
2568
2702
  const expectedCount = setupNumber(action.expected_count, -1);
2569
2703
  if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
2570
2704
  const startedAt = Date.now();
2571
2705
  let count = 0;
2572
2706
  while (Date.now() - startedAt <= timeout) {
2573
2707
  count = await locator.count().catch(() => 0);
2574
- if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2708
+ if (count === expectedCount) return { ...base, ...setupScopeEvidence(scope), ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2575
2709
  await page.waitForTimeout(100);
2576
2710
  }
2577
- return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2711
+ return { ...base, ...setupScopeEvidence(scope), reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2578
2712
  }
2579
2713
  if (type === "wait_for_text") {
2580
- const locator = page.locator(action.selector);
2714
+ const scope = await setupActionScope(action, timeout);
2715
+ if (!scope.ok) return setupScopeFailure(base, scope);
2716
+ const locator = scope.context.locator(action.selector);
2581
2717
  const startedAt = Date.now();
2582
2718
  let lastText = "";
2583
2719
  while (Date.now() - startedAt <= timeout) {
@@ -2586,15 +2722,17 @@ async function executeSetupAction(action, ordinal) {
2586
2722
  const text = await setupLocatorText(locator, index);
2587
2723
  lastText = text || lastText;
2588
2724
  if (setupTextMatches(text, action)) {
2589
- return { ...base, ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2725
+ return { ...base, ...setupScopeEvidence(scope), ok: true, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2590
2726
  }
2591
2727
  }
2592
2728
  await page.waitForTimeout(100);
2593
2729
  }
2594
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2730
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2595
2731
  }
2596
2732
  if (type === "assert_text_visible" || type === "assert_text_absent") {
2597
- const locator = page.locator(action.selector);
2733
+ const scope = await setupActionScope(action, timeout);
2734
+ if (!scope.ok) return setupScopeFailure(base, scope);
2735
+ const locator = scope.context.locator(action.selector);
2598
2736
  const startedAt = Date.now();
2599
2737
  let lastText = "";
2600
2738
  let matchedText = "";
@@ -2613,7 +2751,7 @@ async function executeSetupAction(action, ordinal) {
2613
2751
  if (type === "assert_text_visible") {
2614
2752
  const visible = await setupLocatorVisible(locator, index);
2615
2753
  if (visible) {
2616
- return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2754
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2617
2755
  }
2618
2756
  hiddenMatch = true;
2619
2757
  break;
@@ -2622,17 +2760,17 @@ async function executeSetupAction(action, ordinal) {
2622
2760
  }
2623
2761
  }
2624
2762
  if (type === "assert_text_absent" && !matched) {
2625
- return { ...base, ok: true, count, timeout_ms: timeout };
2763
+ return { ...base, ...setupScopeEvidence(scope), ok: true, count, timeout_ms: timeout };
2626
2764
  }
2627
2765
  await page.waitForTimeout(100);
2628
2766
  }
2629
2767
  if (type === "assert_text_visible") {
2630
2768
  if (hiddenMatch) {
2631
- return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2769
+ return { ...base, ...setupScopeEvidence(scope), reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2632
2770
  }
2633
- return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2771
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2634
2772
  }
2635
- return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2773
+ return { ...base, ...setupScopeEvidence(scope), reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2636
2774
  }
2637
2775
  return { ...base, reason: "unsupported_action" };
2638
2776
  } catch (error) {