@riddledc/riddle-proof 0.7.197 → 0.7.198

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
@@ -406,7 +406,7 @@ when body matching overrides sequence order.
406
406
  `target.setup_actions` is optional. Use it when the meaningful proof surface
407
407
  appears only after a picker, tab, login stub, storage seed, form fill,
408
408
  transport control, or other bounded interaction. Supported setup actions are
409
- `click`, `tap`, `drag`, `press`, `key_down`, `key_up`, `fill`, `set_input_value`, `set_range_value`,
409
+ `click`, `tap`, `tap_until`, `drag`, `press`, `key_down`, `key_up`, `fill`, `set_input_value`, `set_range_value`,
410
410
  `deterministic_runtime`, `canvas_signature`, `assert_text_visible`, `assert_text_absent`,
411
411
  `assert_selector_count`, `assert_window_value`, `assert_window_number`,
412
412
  `local_storage`, `session_storage`, `clear_storage`, `clear_console`,
@@ -436,6 +436,14 @@ It requires `selector`, defaults to a touch tap at the target center, and
436
436
  accepts `x` / `y` or `from_x` / `from_y` plus `coordinate_mode: "ratio"` for
437
437
  element-relative coordinates. Set `pointer_type` to `mouse`, `touch`, or `pen`
438
438
  when the proof must distinguish input modality.
439
+ Use `tap_until` for gameplay loops where the proof should tap a visible target
440
+ until a browser-state predicate is satisfied. It accepts the same selector,
441
+ coordinate, and pointer options as `tap`, plus `until_path`,
442
+ `until_expected_value`, `max_taps` / `max_calls` from 1 to 100, and optional
443
+ `interval_ms`. The action stops early when the predicate matches and records one
444
+ compact receipt with `tap_count`, final `until_value`, and input dispatch
445
+ details, so long canvas interaction loops do not need dozens of repeated setup
446
+ actions.
439
447
  Use `set_range_value` for HTML range inputs and React-controlled sliders. It
440
448
  accepts aliases such as `set-slider-value`, requires `selector` plus `value`,
441
449
  uses the native input value setter, dispatches bubbling `input` and `change`
@@ -531,7 +539,9 @@ actions stay visible. Click actions with `click_count` greater than `1` are
531
539
  included in clicked-target evidence and rolled up as `click_count_action_total`
532
540
  and `click_count_value_total`. Repeated selector runs such as long gameplay
533
541
  button loops are also grouped as compact `same-selector` click-sequence
534
- receipts with click totals and ordinals. Setup receipt sampling favors both
542
+ receipts with click totals and ordinals. `tap_until` actions are summarized as
543
+ one compact receipt with total taps and the final predicate value, which is the
544
+ preferred shape for long canvas gameplay loops. Setup receipt sampling favors both
535
545
  first and last per-viewport receipts before filling remaining space, so late
536
546
  lifecycle phases such as terminal or restart remain visible in compact
537
547
  summaries.
@@ -45,6 +45,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
45
45
  var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
46
46
  "click",
47
47
  "tap",
48
+ "tap_until",
48
49
  "drag",
49
50
  "press",
50
51
  "key_down",
@@ -615,6 +616,28 @@ function profileSetupTapReceipts(results) {
615
616
  reason: result.reason ?? result.error ?? null
616
617
  }));
617
618
  }
619
+ function profileSetupTapUntilReceipts(results) {
620
+ return results.filter((result) => profileSetupResultAction(result) === "tap_until").map((result) => ({
621
+ ordinal: result.ordinal ?? null,
622
+ ok: result.ok !== false,
623
+ selector: result.selector ?? null,
624
+ frame_selector: result.frame_selector ?? null,
625
+ pointer_type: result.pointer_type ?? null,
626
+ input_dispatch: result.input_dispatch ?? null,
627
+ coordinate_mode: result.coordinate_mode ?? null,
628
+ x: result.x ?? null,
629
+ y: result.y ?? null,
630
+ duration_ms: result.duration_ms ?? null,
631
+ until_path: result.until_path ?? null,
632
+ until_value: result.until_value ?? null,
633
+ until_expected_value: result.until_expected_value ?? null,
634
+ tap_count: result.tap_count ?? null,
635
+ max_taps: result.max_taps ?? result.max_calls ?? null,
636
+ interval_ms: result.interval_ms ?? null,
637
+ timeout_ms: result.timeout_ms ?? null,
638
+ reason: result.reason ?? result.error ?? null
639
+ }));
640
+ }
618
641
  function profileSetupKeyboardReceipts(results) {
619
642
  return results.filter((result) => ["press", "key_down", "key_up"].includes(profileSetupResultAction(result))).map((result) => ({
620
643
  action: profileSetupResultAction(result),
@@ -790,6 +813,9 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
790
813
  const sampledDragReceipts = sampleProfileSetupSummaryItems(dragReceipts, 8);
791
814
  const tapReceipts = profileSetupTapReceipts(results);
792
815
  const sampledTapReceipts = sampleProfileSetupSummaryItems(tapReceipts, 8);
816
+ const tapUntilReceipts = profileSetupTapUntilReceipts(results);
817
+ const tapUntilTapCounts = tapUntilReceipts.map((result) => typeof result.tap_count === "number" && Number.isFinite(result.tap_count) ? result.tap_count : void 0).filter((value) => value !== void 0);
818
+ const sampledTapUntilReceipts = sampleProfileSetupSummaryItems(tapUntilReceipts, 8);
793
819
  const keyboardReceipts = profileSetupKeyboardReceipts(results);
794
820
  const sampledKeyboardReceipts = sampleProfileSetupSummaryItems(keyboardReceipts, 8);
795
821
  const canvasSignatureReceipts = profileSetupCanvasSignatureReceipts(results);
@@ -862,6 +888,10 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
862
888
  tap_total: tapReceipts.length,
863
889
  tap_truncated: tapReceipts.length > sampledTapReceipts.length,
864
890
  tap: sampledTapReceipts,
891
+ tap_until_total: tapUntilReceipts.length,
892
+ tap_until_tap_total: tapUntilTapCounts.reduce((sum, value) => sum + value, 0),
893
+ tap_until_truncated: tapUntilReceipts.length > sampledTapUntilReceipts.length,
894
+ tap_until: sampledTapUntilReceipts,
865
895
  keyboard_total: keyboardReceipts.length,
866
896
  keyboard_truncated: keyboardReceipts.length > sampledKeyboardReceipts.length,
867
897
  keyboard: sampledKeyboardReceipts,
@@ -923,7 +953,7 @@ function isSupportedCheckType(value) {
923
953
  }
924
954
  function normalizeSetupActionType(value, index) {
925
955
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
926
- const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "reset_console" || normalizedInput === "clear_browser_console" || normalizedInput === "reset_browser_console" ? "clear_console" : normalizedInput === "pointer_drag" || normalizedInput === "mouse_drag" || normalizedInput === "drag_to" ? "drag" : normalizedInput === "pointer_tap" || normalizedInput === "touch_tap" || normalizedInput === "canvas_tap" ? "tap" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput === "keyboard_down" || normalizedInput === "key_down" || normalizedInput === "keydown" || normalizedInput === "press_down" ? "key_down" : normalizedInput === "keyboard_up" || normalizedInput === "key_up" || normalizedInput === "keyup" || normalizedInput === "press_up" || normalizedInput === "release_key" || normalizedInput === "key_release" ? "key_up" : normalizedInput === "set_slider_value" || normalizedInput === "slider_value" || normalizedInput === "set_slider" || normalizedInput === "set_range" || normalizedInput === "range_value" || normalizedInput === "range_input" || normalizedInput === "set_range_input" ? "set_range_value" : normalizedInput === "deterministic_runtime" || normalizedInput === "mock_runtime" || normalizedInput === "mock_random" || normalizedInput === "mock_random_queue" || normalizedInput === "seed_random_queue" || normalizedInput === "set_random_queue" || normalizedInput === "mock_clock" || normalizedInput === "set_mock_clock" || normalizedInput === "set_runtime_determinism" || normalizedInput === "runtime_determinism" ? "deterministic_runtime" : normalizedInput === "canvas_hash" || normalizedInput === "capture_canvas_hash" || normalizedInput === "capture_canvas_signature" || normalizedInput === "canvas_state_signature" ? "canvas_signature" : normalizedInput === "capture_screenshot" || normalizedInput === "save_screenshot" || normalizedInput === "setup_screenshot" ? "screenshot" : normalizedInput === "accept_dialog" || normalizedInput === "accept_dialogs" || normalizedInput === "confirm_dialog" || normalizedInput === "set_dialog_response" ? "dialog_response" : normalizedInput === "dismiss_dialog" || normalizedInput === "dismiss_dialogs" || normalizedInput === "cancel_dialog" ? "dialog_response" : normalizedInput === "window_call_until" || normalizedInput === "call_until" || normalizedInput === "window_call_repeat_until" || normalizedInput === "repeat_window_call_until" ? "window_call_until" : normalizedInput === "window_evaluate" || normalizedInput === "browser_eval" || normalizedInput === "browser_evaluate" || normalizedInput === "evaluate_script" || normalizedInput === "profile_script" ? "window_eval" : normalizedInput;
956
+ const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "reset_console" || normalizedInput === "clear_browser_console" || normalizedInput === "reset_browser_console" ? "clear_console" : normalizedInput === "pointer_drag" || normalizedInput === "mouse_drag" || normalizedInput === "drag_to" ? "drag" : normalizedInput === "pointer_tap" || normalizedInput === "touch_tap" || normalizedInput === "canvas_tap" ? "tap" : normalizedInput === "tap_until" || normalizedInput === "pointer_tap_until" || normalizedInput === "touch_tap_until" || normalizedInput === "canvas_tap_until" || normalizedInput === "tap_repeat_until" || normalizedInput === "repeat_tap_until" ? "tap_until" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput === "keyboard_down" || normalizedInput === "key_down" || normalizedInput === "keydown" || normalizedInput === "press_down" ? "key_down" : normalizedInput === "keyboard_up" || normalizedInput === "key_up" || normalizedInput === "keyup" || normalizedInput === "press_up" || normalizedInput === "release_key" || normalizedInput === "key_release" ? "key_up" : normalizedInput === "set_slider_value" || normalizedInput === "slider_value" || normalizedInput === "set_slider" || normalizedInput === "set_range" || normalizedInput === "range_value" || normalizedInput === "range_input" || normalizedInput === "set_range_input" ? "set_range_value" : normalizedInput === "deterministic_runtime" || normalizedInput === "mock_runtime" || normalizedInput === "mock_random" || normalizedInput === "mock_random_queue" || normalizedInput === "seed_random_queue" || normalizedInput === "set_random_queue" || normalizedInput === "mock_clock" || normalizedInput === "set_mock_clock" || normalizedInput === "set_runtime_determinism" || normalizedInput === "runtime_determinism" ? "deterministic_runtime" : normalizedInput === "canvas_hash" || normalizedInput === "capture_canvas_hash" || normalizedInput === "capture_canvas_signature" || normalizedInput === "canvas_state_signature" ? "canvas_signature" : normalizedInput === "capture_screenshot" || normalizedInput === "save_screenshot" || normalizedInput === "setup_screenshot" ? "screenshot" : normalizedInput === "accept_dialog" || normalizedInput === "accept_dialogs" || normalizedInput === "confirm_dialog" || normalizedInput === "set_dialog_response" ? "dialog_response" : normalizedInput === "dismiss_dialog" || normalizedInput === "dismiss_dialogs" || normalizedInput === "cancel_dialog" ? "dialog_response" : normalizedInput === "window_call_until" || normalizedInput === "call_until" || normalizedInput === "window_call_repeat_until" || normalizedInput === "repeat_window_call_until" ? "window_call_until" : normalizedInput === "window_evaluate" || normalizedInput === "browser_eval" || normalizedInput === "browser_evaluate" || normalizedInput === "evaluate_script" || normalizedInput === "profile_script" ? "window_eval" : normalizedInput;
927
957
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
928
958
  return normalized;
929
959
  }
@@ -1005,8 +1035,8 @@ function normalizeSetupActionCoordinateMode(value, index) {
1005
1035
  }
1006
1036
  function normalizeSetupActionPointerType(value, type, index) {
1007
1037
  if (value === void 0 || value === null || value === "") return void 0;
1008
- if (type !== "drag" && type !== "tap") {
1009
- throw new Error(`target.setup_actions[${index}].pointer_type is only supported for drag/tap actions.`);
1038
+ if (type !== "drag" && type !== "tap" && type !== "tap_until") {
1039
+ throw new Error(`target.setup_actions[${index}].pointer_type is only supported for drag/tap/tap_until actions.`);
1010
1040
  }
1011
1041
  const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
1012
1042
  if (normalized === "mouse") return "mouse";
@@ -1085,11 +1115,11 @@ function normalizeSetupAction(input, index) {
1085
1115
  if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
1086
1116
  throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
1087
1117
  }
1088
- if ((type === "click" || type === "tap" || type === "drag" || type === "fill" || type === "set_input_value" || type === "set_range_value" || type === "canvas_signature" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
1118
+ if ((type === "click" || type === "tap" || type === "tap_until" || type === "drag" || type === "fill" || type === "set_input_value" || type === "set_range_value" || type === "canvas_signature" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
1089
1119
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
1090
1120
  }
1091
- const fromX = type === "click" || type === "tap" ? numberValue(valueFromOwn(input, "from_x", "fromX", "x", "click_x", "clickX", "start_x", "startX", "x1")) : numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
1092
- const fromY = type === "click" || type === "tap" ? numberValue(valueFromOwn(input, "from_y", "fromY", "y", "click_y", "clickY", "start_y", "startY", "y1")) : numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
1121
+ const fromX = type === "click" || type === "tap" || type === "tap_until" ? numberValue(valueFromOwn(input, "from_x", "fromX", "x", "click_x", "clickX", "start_x", "startX", "x1")) : numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
1122
+ const fromY = type === "click" || type === "tap" || type === "tap_until" ? numberValue(valueFromOwn(input, "from_y", "fromY", "y", "click_y", "clickY", "start_y", "startY", "y1")) : numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
1093
1123
  const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
1094
1124
  const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
1095
1125
  const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
@@ -1114,18 +1144,18 @@ function normalizeSetupAction(input, index) {
1114
1144
  }
1115
1145
  }
1116
1146
  }
1117
- if (type === "tap") {
1147
+ if (type === "tap" || type === "tap_until") {
1118
1148
  const hasTapCoordinate = fromX !== void 0 || fromY !== void 0;
1119
1149
  if (hasTapCoordinate && (fromX === void 0 || fromY === void 0)) {
1120
- throw new Error(`target.setup_actions[${index}] tap coordinates require both x and y.`);
1150
+ throw new Error(`target.setup_actions[${index}] ${type} coordinates require both x and y.`);
1121
1151
  }
1122
1152
  if (hasTapCoordinate && fromX !== void 0 && fromY !== void 0) {
1123
1153
  const tapCoordinates = [fromX, fromY];
1124
1154
  if (coordinateMode === "ratio" && tapCoordinates.some((value2) => value2 < 0 || value2 > 1)) {
1125
- throw new Error(`target.setup_actions[${index}] tap ratio coordinates must be between 0 and 1.`);
1155
+ throw new Error(`target.setup_actions[${index}] ${type} ratio coordinates must be between 0 and 1.`);
1126
1156
  }
1127
1157
  if ((coordinateMode === void 0 || coordinateMode === "pixels") && tapCoordinates.some((value2) => value2 < 0)) {
1128
- throw new Error(`target.setup_actions[${index}] tap pixel coordinates must be non-negative.`);
1158
+ throw new Error(`target.setup_actions[${index}] ${type} pixel coordinates must be non-negative.`);
1129
1159
  }
1130
1160
  }
1131
1161
  }
@@ -1231,7 +1261,7 @@ function normalizeSetupAction(input, index) {
1231
1261
  const captureReturn = input.capture_return === false || input.captureReturn === false || input.include_return === false || input.includeReturn === false || input.omit_return === true || input.omitReturn === true ? false : void 0;
1232
1262
  const untilPath = stringFromOwn(input, "until_path", "untilPath", "until_state_path", "untilStatePath", "until_window_path", "untilWindowPath", "until");
1233
1263
  const hasUntilExpectedValue = hasOwn(input, "until_expected_value") || hasOwn(input, "untilExpectedValue") || hasOwn(input, "until_expected") || hasOwn(input, "untilExpected") || hasOwn(input, "until_value") || hasOwn(input, "untilValue") || hasOwn(input, "expected_value") || hasOwn(input, "expectedValue") || hasOwn(input, "expected");
1234
- if (type === "window_call_until") {
1264
+ if (type === "window_call_until" || type === "tap_until") {
1235
1265
  if (!untilPath) {
1236
1266
  throw new Error(`target.setup_actions[${index}] ${type} requires until_path.`);
1237
1267
  }
@@ -1239,12 +1269,12 @@ function normalizeSetupAction(input, index) {
1239
1269
  throw new Error(`target.setup_actions[${index}] ${type} requires until_expected_value.`);
1240
1270
  }
1241
1271
  }
1242
- const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts"));
1243
- if (type === "window_call_until" && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
1272
+ const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts", "max_taps", "maxTaps", "tap_limit", "tapLimit"));
1273
+ if ((type === "window_call_until" || type === "tap_until") && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
1244
1274
  throw new Error(`target.setup_actions[${index}].max_calls must be an integer from 1 to 100.`);
1245
1275
  }
1246
1276
  const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
1247
- if (type === "window_call_until" && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
1277
+ if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
1248
1278
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
1249
1279
  }
1250
1280
  const steps = numberValue(input.steps);
@@ -4510,6 +4540,30 @@ function profileSetupTapReceipts(results) {
4510
4540
  reason: result.reason || result.error || null,
4511
4541
  }));
4512
4542
  }
4543
+ function profileSetupTapUntilReceipts(results) {
4544
+ return (results || [])
4545
+ .filter((result) => result && profileSetupResultAction(result) === "tap_until")
4546
+ .map((result) => ({
4547
+ ordinal: result.ordinal ?? null,
4548
+ ok: result.ok !== false,
4549
+ selector: result.selector ?? null,
4550
+ frame_selector: result.frame_selector ?? null,
4551
+ pointer_type: result.pointer_type ?? null,
4552
+ input_dispatch: result.input_dispatch ?? null,
4553
+ coordinate_mode: result.coordinate_mode ?? null,
4554
+ x: result.x ?? null,
4555
+ y: result.y ?? null,
4556
+ duration_ms: result.duration_ms ?? null,
4557
+ until_path: result.until_path ?? null,
4558
+ until_value: result.until_value ?? null,
4559
+ until_expected_value: result.until_expected_value ?? null,
4560
+ tap_count: result.tap_count ?? null,
4561
+ max_taps: result.max_taps ?? result.max_calls ?? null,
4562
+ interval_ms: result.interval_ms ?? null,
4563
+ timeout_ms: result.timeout_ms ?? null,
4564
+ reason: result.reason || result.error || null,
4565
+ }));
4566
+ }
4513
4567
  function profileSetupKeyboardReceipts(results) {
4514
4568
  return (results || [])
4515
4569
  .filter((result) => result && ["press", "key_down", "key_up"].includes(profileSetupResultAction(result)))
@@ -4712,6 +4766,11 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
4712
4766
  const sampledDragReceipts = sampleProfileSetupSummaryItems(dragReceipts, 8);
4713
4767
  const tapReceipts = profileSetupTapReceipts(results);
4714
4768
  const sampledTapReceipts = sampleProfileSetupSummaryItems(tapReceipts, 8);
4769
+ const tapUntilReceipts = profileSetupTapUntilReceipts(results);
4770
+ const tapUntilTapCounts = tapUntilReceipts
4771
+ .map((result) => typeof result.tap_count === "number" && Number.isFinite(result.tap_count) ? result.tap_count : undefined)
4772
+ .filter((value) => value !== undefined);
4773
+ const sampledTapUntilReceipts = sampleProfileSetupSummaryItems(tapUntilReceipts, 8);
4715
4774
  const keyboardReceipts = profileSetupKeyboardReceipts(results);
4716
4775
  const sampledKeyboardReceipts = sampleProfileSetupSummaryItems(keyboardReceipts, 8);
4717
4776
  const canvasSignatureReceipts = profileSetupCanvasSignatureReceipts(results);
@@ -4794,6 +4853,10 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
4794
4853
  tap_total: tapReceipts.length,
4795
4854
  tap_truncated: tapReceipts.length > sampledTapReceipts.length,
4796
4855
  tap: sampledTapReceipts,
4856
+ tap_until_total: tapUntilReceipts.length,
4857
+ tap_until_tap_total: tapUntilTapCounts.reduce((sum, value) => sum + value, 0),
4858
+ tap_until_truncated: tapUntilReceipts.length > sampledTapUntilReceipts.length,
4859
+ tap_until: sampledTapUntilReceipts,
4797
4860
  keyboard_total: keyboardReceipts.length,
4798
4861
  keyboard_truncated: keyboardReceipts.length > sampledKeyboardReceipts.length,
4799
4862
  keyboard: sampledKeyboardReceipts,
@@ -5740,6 +5803,108 @@ async function waitForAnyVisibleSelector(context, selector, timeout) {
5740
5803
  }
5741
5804
  throw new Error("No visible match for selector " + selector + ": " + lastReason);
5742
5805
  }
5806
+ async function dispatchSetupTapPoint(point, pointerType, durationMs) {
5807
+ if (pointerType === "touch" || pointerType === "pen") {
5808
+ const client = await page.context().newCDPSession(page);
5809
+ try {
5810
+ if (pointerType === "touch") {
5811
+ const touchPoint = {
5812
+ x: point.x,
5813
+ y: point.y,
5814
+ radiusX: 1,
5815
+ radiusY: 1,
5816
+ force: 1,
5817
+ id: 11,
5818
+ };
5819
+ await client.send("Input.dispatchTouchEvent", {
5820
+ type: "touchStart",
5821
+ touchPoints: [touchPoint],
5822
+ });
5823
+ if (durationMs) await page.waitForTimeout(durationMs);
5824
+ await client.send("Input.dispatchTouchEvent", {
5825
+ type: "touchEnd",
5826
+ touchPoints: [],
5827
+ });
5828
+ } else {
5829
+ await client.send("Input.dispatchMouseEvent", {
5830
+ type: "mousePressed",
5831
+ x: point.x,
5832
+ y: point.y,
5833
+ button: "left",
5834
+ buttons: 1,
5835
+ clickCount: 1,
5836
+ pointerType: "pen",
5837
+ });
5838
+ if (durationMs) await page.waitForTimeout(durationMs);
5839
+ await client.send("Input.dispatchMouseEvent", {
5840
+ type: "mouseReleased",
5841
+ x: point.x,
5842
+ y: point.y,
5843
+ button: "left",
5844
+ buttons: 0,
5845
+ clickCount: 1,
5846
+ pointerType: "pen",
5847
+ });
5848
+ }
5849
+ } finally {
5850
+ await client.detach().catch(() => {});
5851
+ }
5852
+ } else {
5853
+ await page.mouse.click(point.x, point.y);
5854
+ }
5855
+ }
5856
+ async function resolveSetupTapTarget(action, base, scope, timeout) {
5857
+ const locator = scope.context.locator(action.selector);
5858
+ const count = await locator.count();
5859
+ if (!count) return { result: { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count } };
5860
+ const targetIndex = Number.isInteger(action.index) ? action.index : 0;
5861
+ if (targetIndex < 0 || targetIndex >= count) return { result: { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex } };
5862
+ const target = locator.nth(targetIndex);
5863
+ await target.waitFor({ state: "visible", timeout });
5864
+ const box = await target.boundingBox();
5865
+ if (!box) return { result: { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex } };
5866
+ const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.x ?? action.click_x ?? action.clickX);
5867
+ const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.y ?? action.click_y ?? action.clickY);
5868
+ const hasTapPosition = fromX !== undefined || fromY !== undefined;
5869
+ if (hasTapPosition && (fromX === undefined || fromY === undefined)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "missing_tap_coordinates", count, target_index: targetIndex } };
5870
+ const mode = String(action.coordinate_mode || action.coordinateMode || (hasTapPosition ? "pixels" : "ratio")).trim();
5871
+ if (hasTapPosition && mode === "ratio" && [fromX, fromY].some((value) => value < 0 || value > 1)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "invalid_ratio_coordinates", count, target_index: targetIndex } };
5872
+ if (hasTapPosition && mode !== "ratio" && [fromX, fromY].some((value) => value < 0)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex } };
5873
+ const coordinate = (value, size) => mode === "ratio" ? value * size : value;
5874
+ const localX = hasTapPosition && fromX !== undefined ? fromX : 0.5;
5875
+ const localY = hasTapPosition && fromY !== undefined ? fromY : 0.5;
5876
+ const point = {
5877
+ x: box.x + coordinate(localX, box.width),
5878
+ y: box.y + coordinate(localY, box.height),
5879
+ };
5880
+ const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
5881
+ const pointerType = String(action.pointer_type || action.pointerType || "touch").trim().toLowerCase();
5882
+ return {
5883
+ target: {
5884
+ count,
5885
+ targetIndex,
5886
+ point,
5887
+ mode,
5888
+ fromX,
5889
+ fromY,
5890
+ hasTapPosition,
5891
+ pointerType,
5892
+ durationMs,
5893
+ },
5894
+ };
5895
+ }
5896
+ function setupTapTargetEvidence(tapTarget) {
5897
+ return {
5898
+ count: tapTarget.count,
5899
+ target_index: tapTarget.targetIndex,
5900
+ coordinate_mode: tapTarget.hasTapPosition ? tapTarget.mode : undefined,
5901
+ x: tapTarget.hasTapPosition ? tapTarget.fromX : undefined,
5902
+ y: tapTarget.hasTapPosition ? tapTarget.fromY : undefined,
5903
+ pointer_type: tapTarget.pointerType,
5904
+ input_dispatch: tapTarget.pointerType === "touch" || tapTarget.pointerType === "pen" ? "cdp" : "playwright_mouse",
5905
+ duration_ms: tapTarget.durationMs || undefined,
5906
+ };
5907
+ }
5743
5908
  function setupHasOwn(action, key) {
5744
5909
  return Boolean(action) && Object.keys(action).includes(key);
5745
5910
  }
@@ -6342,91 +6507,108 @@ async function executeSetupAction(action, ordinal, viewport) {
6342
6507
  if (type === "tap") {
6343
6508
  const scope = await setupActionScope(action, timeout);
6344
6509
  if (!scope.ok) return setupScopeFailure(base, scope);
6345
- const locator = scope.context.locator(action.selector);
6346
- const count = await locator.count();
6347
- if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
6348
- const targetIndex = Number.isInteger(action.index) ? action.index : 0;
6349
- if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
6350
- const target = locator.nth(targetIndex);
6351
- await target.waitFor({ state: "visible", timeout });
6352
- const box = await target.boundingBox();
6353
- if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
6354
- const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.x ?? action.click_x ?? action.clickX);
6355
- const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.y ?? action.click_y ?? action.clickY);
6356
- const hasTapPosition = fromX !== undefined || fromY !== undefined;
6357
- if (hasTapPosition && (fromX === undefined || fromY === undefined)) return { ...base, ...setupScopeEvidence(scope), reason: "missing_tap_coordinates", count, target_index: targetIndex };
6358
- const mode = String(action.coordinate_mode || action.coordinateMode || (hasTapPosition ? "pixels" : "ratio")).trim();
6359
- if (hasTapPosition && mode === "ratio" && [fromX, fromY].some((value) => value < 0 || value > 1)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_ratio_coordinates", count, target_index: targetIndex };
6360
- if (hasTapPosition && mode !== "ratio" && [fromX, fromY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
6361
- const coordinate = (value, size) => mode === "ratio" ? value * size : value;
6362
- const localX = hasTapPosition && fromX !== undefined ? fromX : 0.5;
6363
- const localY = hasTapPosition && fromY !== undefined ? fromY : 0.5;
6364
- const point = {
6365
- x: box.x + coordinate(localX, box.width),
6366
- y: box.y + coordinate(localY, box.height),
6510
+ const prepared = await resolveSetupTapTarget(action, base, scope, timeout);
6511
+ if (prepared.result) return prepared.result;
6512
+ await dispatchSetupTapPoint(prepared.target.point, prepared.target.pointerType, prepared.target.durationMs);
6513
+ return {
6514
+ ...base,
6515
+ ...setupScopeEvidence(scope),
6516
+ ok: true,
6517
+ ...setupTapTargetEvidence(prepared.target),
6367
6518
  };
6368
- const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
6369
- const pointerType = String(action.pointer_type || action.pointerType || "touch").trim().toLowerCase();
6370
- if (pointerType === "touch" || pointerType === "pen") {
6371
- const client = await page.context().newCDPSession(page);
6372
- try {
6373
- if (pointerType === "touch") {
6374
- const touchPoint = {
6375
- x: point.x,
6376
- y: point.y,
6377
- radiusX: 1,
6378
- radiusY: 1,
6379
- force: 1,
6380
- id: 11,
6381
- };
6382
- await client.send("Input.dispatchTouchEvent", {
6383
- type: "touchStart",
6384
- touchPoints: [touchPoint],
6385
- });
6386
- if (durationMs) await page.waitForTimeout(durationMs);
6387
- await client.send("Input.dispatchTouchEvent", {
6388
- type: "touchEnd",
6389
- touchPoints: [],
6390
- });
6391
- } else {
6392
- await client.send("Input.dispatchMouseEvent", {
6393
- type: "mousePressed",
6394
- x: point.x,
6395
- y: point.y,
6396
- button: "left",
6397
- buttons: 1,
6398
- clickCount: 1,
6399
- pointerType: "pen",
6400
- });
6401
- if (durationMs) await page.waitForTimeout(durationMs);
6402
- await client.send("Input.dispatchMouseEvent", {
6403
- type: "mouseReleased",
6404
- x: point.x,
6405
- y: point.y,
6406
- button: "left",
6407
- buttons: 0,
6408
- clickCount: 1,
6409
- pointerType: "pen",
6410
- });
6411
- }
6412
- } finally {
6413
- await client.detach().catch(() => {});
6519
+ }
6520
+ if (type === "tap_until") {
6521
+ const untilPath = String(action.until_path || action.untilPath || action.until_state_path || action.untilStatePath || action.until_window_path || action.untilWindowPath || action.until || "");
6522
+ const hasUntilExpected = setupHasOwn(action, "until_expected_value")
6523
+ || setupHasOwn(action, "untilExpectedValue")
6524
+ || setupHasOwn(action, "until_expected")
6525
+ || setupHasOwn(action, "untilExpected")
6526
+ || setupHasOwn(action, "until_value")
6527
+ || setupHasOwn(action, "untilValue")
6528
+ || setupHasOwn(action, "expected_value")
6529
+ || setupHasOwn(action, "expectedValue")
6530
+ || setupHasOwn(action, "expected");
6531
+ const untilExpected = setupHasOwn(action, "until_expected_value")
6532
+ ? action.until_expected_value
6533
+ : setupHasOwn(action, "untilExpectedValue")
6534
+ ? action.untilExpectedValue
6535
+ : setupHasOwn(action, "until_expected")
6536
+ ? action.until_expected
6537
+ : setupHasOwn(action, "untilExpected")
6538
+ ? action.untilExpected
6539
+ : setupHasOwn(action, "until_value")
6540
+ ? action.until_value
6541
+ : setupHasOwn(action, "untilValue")
6542
+ ? action.untilValue
6543
+ : setupHasOwn(action, "expected_value")
6544
+ ? action.expected_value
6545
+ : setupHasOwn(action, "expectedValue")
6546
+ ? action.expectedValue
6547
+ : action.expected;
6548
+ if (!untilPath) return { ...base, reason: "missing_until_path" };
6549
+ if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
6550
+ const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
6551
+ const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
6552
+ const scope = await setupActionScope(action, timeout);
6553
+ if (!scope.ok) return setupScopeFailure(base, scope);
6554
+ const prepared = await resolveSetupTapTarget(action, base, scope, timeout);
6555
+ if (prepared.result) return prepared.result;
6556
+ const startedAt = Date.now();
6557
+ let tapCount = 0;
6558
+ let lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
6559
+ const targetEvidence = setupTapTargetEvidence(prepared.target);
6560
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
6561
+ return {
6562
+ ...base,
6563
+ ...setupScopeEvidence(scope),
6564
+ ok: true,
6565
+ ...targetEvidence,
6566
+ until_path: untilPath,
6567
+ until_value: setupJsonValue(lastPredicateResult.value),
6568
+ until_expected_value: setupJsonValue(untilExpected),
6569
+ tap_count: tapCount,
6570
+ max_taps: maxTaps,
6571
+ max_calls: maxTaps,
6572
+ interval_ms: intervalMs,
6573
+ timeout_ms: timeout,
6574
+ };
6575
+ }
6576
+ while (tapCount < maxTaps && Date.now() - startedAt <= timeout) {
6577
+ await dispatchSetupTapPoint(prepared.target.point, prepared.target.pointerType, prepared.target.durationMs);
6578
+ tapCount += 1;
6579
+ lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
6580
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
6581
+ return {
6582
+ ...base,
6583
+ ...setupScopeEvidence(scope),
6584
+ ok: true,
6585
+ ...targetEvidence,
6586
+ until_path: untilPath,
6587
+ until_value: setupJsonValue(lastPredicateResult.value),
6588
+ until_expected_value: setupJsonValue(untilExpected),
6589
+ tap_count: tapCount,
6590
+ max_taps: maxTaps,
6591
+ max_calls: maxTaps,
6592
+ interval_ms: intervalMs,
6593
+ timeout_ms: timeout,
6594
+ };
6414
6595
  }
6415
- } else {
6416
- await page.mouse.click(point.x, point.y);
6596
+ if (tapCount < maxTaps && intervalMs) await page.waitForTimeout(intervalMs);
6417
6597
  }
6418
6598
  return {
6419
6599
  ...base,
6420
6600
  ...setupScopeEvidence(scope),
6421
- ok: true,
6422
- count,
6423
- target_index: targetIndex,
6424
- coordinate_mode: hasTapPosition ? mode : undefined,
6425
- x: hasTapPosition ? fromX : undefined,
6426
- y: hasTapPosition ? fromY : undefined,
6427
- pointer_type: pointerType,
6428
- input_dispatch: pointerType === "touch" || pointerType === "pen" ? "cdp" : "playwright_mouse",
6429
- duration_ms: durationMs || undefined,
6601
+ ...targetEvidence,
6602
+ until_path: untilPath,
6603
+ until_value: setupJsonValue(lastPredicateResult?.value),
6604
+ until_expected_value: setupJsonValue(untilExpected),
6605
+ tap_count: tapCount,
6606
+ max_taps: maxTaps,
6607
+ max_calls: maxTaps,
6608
+ interval_ms: intervalMs,
6609
+ timeout_ms: timeout,
6610
+ reason: Date.now() - startedAt > timeout ? "timeout" : "until_condition_not_met",
6611
+ missing_part: lastPredicateResult?.missing_part || undefined,
6430
6612
  };
6431
6613
  }
6432
6614
  if (type === "drag") {