@riddledc/riddle-proof 0.7.131 → 0.7.133

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
@@ -401,7 +401,7 @@ transport control, or other bounded interaction. Supported setup actions are
401
401
  `assert_text_absent`, `assert_selector_count`, `assert_window_value`,
402
402
  `assert_window_number`, `local_storage`, `session_storage`, `clear_storage`,
403
403
  `clear_console`, `screenshot`, `wait`, `wait_for_selector`, `wait_for_text`,
404
- and `window_call`;
404
+ `window_call`, and `window_call_until`;
405
405
  a failed setup action is recorded as a failed `setup_actions_succeeded` check so
406
406
  the profile cannot pass without reaching the intended state. Text-matched `click` actions prefer
407
407
  visible matching elements, which keeps responsive layouts from selecting hidden
@@ -447,6 +447,11 @@ errors, but keeps network mock hit evidence intact. Any setup action can include
447
447
  with `repeat_index` and `repeat_count`, and `after_ms` runs after each
448
448
  repetition. Use it for bounded game proof helpers, retry controls, or other
449
449
  workflows where one declarative action needs to advance the app several times.
450
+ Use `window_call_until` when a proof helper needs to advance randomized or
451
+ progressive state until a window-state receipt is true. It accepts `path` plus
452
+ optional `args`, `until_path`, `until_expected_value`, `max_calls` from 1 to
453
+ 100, and optional `interval_ms`; the action stops early when the predicate is
454
+ met and records `call_count`, final `returned`, and final `until_value`.
450
455
  Use `screenshot` with an optional `label` to capture durable Riddle screenshots
451
456
  at important setup milestones, such as after a route switch, terminal state, or
452
457
  reset. These labels are recorded in setup evidence and included in profile
@@ -61,7 +61,8 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
61
61
  "wait",
62
62
  "wait_for_selector",
63
63
  "wait_for_text",
64
- "window_call"
64
+ "window_call",
65
+ "window_call_until"
65
66
  ];
66
67
  var RIDDLE_PROOF_PROFILE_NETWORK_ABORT_ERROR_CODES = [
67
68
  "aborted",
@@ -515,14 +516,16 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
515
516
  action: profileSetupResultAction(result),
516
517
  selector: result.selector ?? null,
517
518
  frame_selector: result.frame_selector ?? null,
518
- reason: result.reason ?? result.error ?? null
519
+ reason: result.reason ?? result.error ?? null,
520
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text)
519
521
  })),
520
522
  optional_failed: optionalFailed.map((result) => ({
521
523
  ordinal: result.ordinal ?? null,
522
524
  action: profileSetupResultAction(result),
523
525
  selector: result.selector ?? null,
524
526
  frame_selector: result.frame_selector ?? null,
525
- reason: result.reason ?? result.error ?? null
527
+ reason: result.reason ?? result.error ?? null,
528
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text)
526
529
  }))
527
530
  };
528
531
  })
@@ -558,7 +561,7 @@ function isSupportedCheckType(value) {
558
561
  }
559
562
  function normalizeSetupActionType(value, index) {
560
563
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
561
- 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 === "keyboard_press" || normalizedInput === "key_press" ? "press" : 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;
564
+ 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 === "keyboard_press" || normalizedInput === "key_press" ? "press" : 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;
562
565
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
563
566
  return normalized;
564
567
  }
@@ -689,10 +692,10 @@ function normalizeSetupAction(input, index) {
689
692
  throw new Error(`target.setup_actions[${index}] ${type} requires value.`);
690
693
  }
691
694
  const path = stringFromOwn(input, "path", "function_path", "functionPath", "window_path", "windowPath", "state_path", "statePath");
692
- if ((type === "window_call" || type === "assert_window_value" || type === "assert_window_number") && !path) {
695
+ if ((type === "window_call" || type === "window_call_until" || type === "assert_window_value" || type === "assert_window_number") && !path) {
693
696
  throw new Error(`target.setup_actions[${index}] ${type} requires path.`);
694
697
  }
695
- const args = type === "window_call" ? normalizeSetupActionArgs(input, index) : void 0;
698
+ const args = type === "window_call" || type === "window_call_until" ? normalizeSetupActionArgs(input, index) : void 0;
696
699
  const hasExpectedValue = hasOwn(input, "expected_value") || hasOwn(input, "expectedValue") || hasOwn(input, "expected") || hasOwn(input, "expect_value") || hasOwn(input, "expectValue") || hasOwn(input, "expect");
697
700
  if (type === "assert_window_value" && !hasExpectedValue) {
698
701
  throw new Error(`target.setup_actions[${index}] ${type} requires expected_value.`);
@@ -709,6 +712,24 @@ function normalizeSetupAction(input, index) {
709
712
  }
710
713
  }
711
714
  const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
715
+ const untilPath = stringFromOwn(input, "until_path", "untilPath", "until_state_path", "untilStatePath", "until_window_path", "untilWindowPath", "until");
716
+ 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");
717
+ if (type === "window_call_until") {
718
+ if (!untilPath) {
719
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_path.`);
720
+ }
721
+ if (!hasUntilExpectedValue) {
722
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_expected_value.`);
723
+ }
724
+ }
725
+ const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts"));
726
+ if (type === "window_call_until" && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
727
+ throw new Error(`target.setup_actions[${index}].max_calls must be an integer from 1 to 100.`);
728
+ }
729
+ const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
730
+ if (type === "window_call_until" && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
731
+ throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
732
+ }
712
733
  const steps = numberValue(input.steps);
713
734
  if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
714
735
  throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
@@ -735,6 +756,10 @@ function normalizeSetupAction(input, index) {
735
756
  path,
736
757
  args,
737
758
  expect_return: hasExpectedReturn ? toJsonValue(valueFromOwn(input, "expect_return", "expectReturn", "expected_return", "expectedReturn")) : void 0,
759
+ until_path: untilPath,
760
+ until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
761
+ max_calls: maxCalls,
762
+ interval_ms: intervalMs,
738
763
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
739
764
  min_value: minValue,
740
765
  max_value: maxValue,
@@ -3687,6 +3712,7 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
3687
3712
  selector: result.selector ?? null,
3688
3713
  frame_selector: result.frame_selector ?? null,
3689
3714
  reason: result.reason || result.error || null,
3715
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text),
3690
3716
  })),
3691
3717
  optional_failed: optionalFailed.map((result) => ({
3692
3718
  ordinal: result.ordinal ?? null,
@@ -3694,6 +3720,7 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
3694
3720
  selector: result.selector ?? null,
3695
3721
  frame_selector: result.frame_selector ?? null,
3696
3722
  reason: result.reason || result.error || null,
3723
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text),
3697
3724
  })),
3698
3725
  };
3699
3726
  }),
@@ -4609,6 +4636,34 @@ async function setupReadWindowValue(context, path) {
4609
4636
  return { ok: true, value: toJsonValue(current) };
4610
4637
  }, { path });
4611
4638
  }
4639
+ async function setupCallWindowFunction(context, path, args) {
4640
+ return await context.evaluate(async ({ path, args }) => {
4641
+ const toJsonValue = (value) => {
4642
+ if (value === null || value === undefined) return null;
4643
+ if (typeof value === "string" || typeof value === "boolean") return value;
4644
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
4645
+ if (Array.isArray(value)) return value.map(toJsonValue);
4646
+ if (typeof value === "object") {
4647
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
4648
+ }
4649
+ return String(value);
4650
+ };
4651
+ const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
4652
+ let parent = window;
4653
+ let current = window;
4654
+ for (const part of pathParts) {
4655
+ parent = current;
4656
+ current = current?.[part];
4657
+ }
4658
+ if (typeof current !== "function") return { ok: false, reason: "missing_function" };
4659
+ try {
4660
+ const returned = await current.apply(parent, Array.isArray(args) ? args : []);
4661
+ return { ok: true, returned: toJsonValue(returned) };
4662
+ } catch (error) {
4663
+ return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
4664
+ }
4665
+ }, { path, args });
4666
+ }
4612
4667
  function setupFrameSelector(action) {
4613
4668
  return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
4614
4669
  }
@@ -5126,32 +5181,7 @@ async function executeSetupAction(action, ordinal, viewport) {
5126
5181
  if (!path) return { ...base, path, reason: "missing_path" };
5127
5182
  const scope = await setupActionScope(action, timeout);
5128
5183
  if (!scope.ok) return setupScopeFailure(base, scope);
5129
- const result = await scope.context.evaluate(async ({ path, args }) => {
5130
- const toJsonValue = (value) => {
5131
- if (value === null || value === undefined) return null;
5132
- if (typeof value === "string" || typeof value === "boolean") return value;
5133
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
5134
- if (Array.isArray(value)) return value.map(toJsonValue);
5135
- if (typeof value === "object") {
5136
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
5137
- }
5138
- return String(value);
5139
- };
5140
- const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
5141
- let parent = window;
5142
- let current = window;
5143
- for (const part of pathParts) {
5144
- parent = current;
5145
- current = current?.[part];
5146
- }
5147
- if (typeof current !== "function") return { ok: false, reason: "missing_function" };
5148
- try {
5149
- const returned = await current.apply(parent, Array.isArray(args) ? args : []);
5150
- return { ok: true, returned: toJsonValue(returned) };
5151
- } catch (error) {
5152
- return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
5153
- }
5154
- }, { path, args });
5184
+ const result = await setupCallWindowFunction(scope.context, path, args);
5155
5185
  const hasExpectation = setupHasOwn(action, "expect_return")
5156
5186
  || setupHasOwn(action, "expectReturn")
5157
5187
  || setupHasOwn(action, "expected_return")
@@ -5176,6 +5206,126 @@ async function executeSetupAction(action, ordinal, viewport) {
5176
5206
  error: result.error || undefined,
5177
5207
  };
5178
5208
  }
5209
+ if (type === "window_call_until") {
5210
+ const path = String(action.path || action.function_path || action.functionPath || "");
5211
+ const untilPath = String(action.until_path || action.untilPath || action.until_state_path || action.untilStatePath || action.until_window_path || action.untilWindowPath || action.until || "");
5212
+ const args = Array.isArray(action.args) ? action.args : [];
5213
+ if (!path) return { ...base, path, reason: "missing_path" };
5214
+ if (!untilPath) return { ...base, path, reason: "missing_until_path" };
5215
+ const hasUntilExpected = setupHasOwn(action, "until_expected_value")
5216
+ || setupHasOwn(action, "untilExpectedValue")
5217
+ || setupHasOwn(action, "until_expected")
5218
+ || setupHasOwn(action, "untilExpected")
5219
+ || setupHasOwn(action, "until_value")
5220
+ || setupHasOwn(action, "untilValue")
5221
+ || setupHasOwn(action, "expected_value")
5222
+ || setupHasOwn(action, "expectedValue")
5223
+ || setupHasOwn(action, "expected");
5224
+ const untilExpected = setupHasOwn(action, "until_expected_value")
5225
+ ? action.until_expected_value
5226
+ : setupHasOwn(action, "untilExpectedValue")
5227
+ ? action.untilExpectedValue
5228
+ : setupHasOwn(action, "until_expected")
5229
+ ? action.until_expected
5230
+ : setupHasOwn(action, "untilExpected")
5231
+ ? action.untilExpected
5232
+ : setupHasOwn(action, "until_value")
5233
+ ? action.until_value
5234
+ : setupHasOwn(action, "untilValue")
5235
+ ? action.untilValue
5236
+ : setupHasOwn(action, "expected_value")
5237
+ ? action.expected_value
5238
+ : setupHasOwn(action, "expectedValue")
5239
+ ? action.expectedValue
5240
+ : action.expected;
5241
+ if (!hasUntilExpected) return { ...base, path, until_path: untilPath, reason: "missing_until_expected_value" };
5242
+ const maxCalls = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
5243
+ const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.call_interval_ms ?? action.callIntervalMs, 100) || 0)));
5244
+ const hasReturnExpectation = setupHasOwn(action, "expect_return")
5245
+ || setupHasOwn(action, "expectReturn")
5246
+ || setupHasOwn(action, "expected_return")
5247
+ || setupHasOwn(action, "expectedReturn");
5248
+ const expectedReturn = setupHasOwn(action, "expect_return")
5249
+ ? action.expect_return
5250
+ : setupHasOwn(action, "expectReturn")
5251
+ ? action.expectReturn
5252
+ : setupHasOwn(action, "expected_return")
5253
+ ? action.expected_return
5254
+ : action.expectedReturn;
5255
+ const scope = await setupActionScope(action, timeout);
5256
+ if (!scope.ok) return setupScopeFailure(base, scope);
5257
+ const startedAt = Date.now();
5258
+ let callCount = 0;
5259
+ let lastCallResult = null;
5260
+ let lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
5261
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
5262
+ return {
5263
+ ...base,
5264
+ ...setupScopeEvidence(scope),
5265
+ ok: true,
5266
+ path,
5267
+ arg_count: args.length,
5268
+ until_path: untilPath,
5269
+ until_value: setupJsonValue(lastPredicateResult.value),
5270
+ until_expected_value: setupJsonValue(untilExpected),
5271
+ call_count: callCount,
5272
+ max_calls: maxCalls,
5273
+ interval_ms: intervalMs,
5274
+ timeout_ms: timeout,
5275
+ };
5276
+ }
5277
+ while (callCount < maxCalls && Date.now() - startedAt <= timeout) {
5278
+ lastCallResult = await setupCallWindowFunction(scope.context, path, args);
5279
+ callCount += 1;
5280
+ if (!lastCallResult.ok) break;
5281
+ if (hasReturnExpectation && !setupValuesEqual(lastCallResult.returned, expectedReturn)) break;
5282
+ lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
5283
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
5284
+ return {
5285
+ ...base,
5286
+ ...setupScopeEvidence(scope),
5287
+ ok: true,
5288
+ path,
5289
+ arg_count: args.length,
5290
+ returned: setupJsonValue(lastCallResult.returned),
5291
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
5292
+ until_path: untilPath,
5293
+ until_value: setupJsonValue(lastPredicateResult.value),
5294
+ until_expected_value: setupJsonValue(untilExpected),
5295
+ call_count: callCount,
5296
+ max_calls: maxCalls,
5297
+ interval_ms: intervalMs,
5298
+ timeout_ms: timeout,
5299
+ };
5300
+ }
5301
+ if (callCount < maxCalls && intervalMs) await page.waitForTimeout(intervalMs);
5302
+ }
5303
+ const returnExpectationMet = !hasReturnExpectation || setupValuesEqual(lastCallResult?.returned, expectedReturn);
5304
+ return {
5305
+ ...base,
5306
+ ...setupScopeEvidence(scope),
5307
+ path,
5308
+ arg_count: args.length,
5309
+ returned: setupJsonValue(lastCallResult?.returned),
5310
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
5311
+ until_path: untilPath,
5312
+ until_value: setupJsonValue(lastPredicateResult?.value),
5313
+ until_expected_value: setupJsonValue(untilExpected),
5314
+ call_count: callCount,
5315
+ max_calls: maxCalls,
5316
+ interval_ms: intervalMs,
5317
+ timeout_ms: timeout,
5318
+ reason: lastCallResult && !lastCallResult.ok
5319
+ ? lastCallResult.reason
5320
+ : hasReturnExpectation && !returnExpectationMet
5321
+ ? "unexpected_return_value"
5322
+ : Date.now() - startedAt > timeout
5323
+ ? "timeout"
5324
+ : "until_condition_not_met",
5325
+ error: lastCallResult?.error || undefined,
5326
+ missing_part: lastPredicateResult?.missing_part || undefined,
5327
+ };
5328
+ }
5179
5329
  if (type === "assert_window_value") {
5180
5330
  const path = String(action.path || action.window_path || action.windowPath || "");
5181
5331
  const hasExpected = setupHasOwn(action, "expected_value")
package/dist/cli.cjs CHANGED
@@ -6998,7 +6998,8 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
6998
6998
  "wait",
6999
6999
  "wait_for_selector",
7000
7000
  "wait_for_text",
7001
- "window_call"
7001
+ "window_call",
7002
+ "window_call_until"
7002
7003
  ];
7003
7004
  var RIDDLE_PROOF_PROFILE_NETWORK_ABORT_ERROR_CODES = [
7004
7005
  "aborted",
@@ -7452,14 +7453,16 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
7452
7453
  action: profileSetupResultAction(result),
7453
7454
  selector: result.selector ?? null,
7454
7455
  frame_selector: result.frame_selector ?? null,
7455
- reason: result.reason ?? result.error ?? null
7456
+ reason: result.reason ?? result.error ?? null,
7457
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text)
7456
7458
  })),
7457
7459
  optional_failed: optionalFailed.map((result) => ({
7458
7460
  ordinal: result.ordinal ?? null,
7459
7461
  action: profileSetupResultAction(result),
7460
7462
  selector: result.selector ?? null,
7461
7463
  frame_selector: result.frame_selector ?? null,
7462
- reason: result.reason ?? result.error ?? null
7464
+ reason: result.reason ?? result.error ?? null,
7465
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text)
7463
7466
  }))
7464
7467
  };
7465
7468
  })
@@ -7495,7 +7498,7 @@ function isSupportedCheckType(value) {
7495
7498
  }
7496
7499
  function normalizeSetupActionType(value, index) {
7497
7500
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
7498
- 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 === "keyboard_press" || normalizedInput === "key_press" ? "press" : 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;
7501
+ 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 === "keyboard_press" || normalizedInput === "key_press" ? "press" : 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;
7499
7502
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
7500
7503
  return normalized;
7501
7504
  }
@@ -7626,10 +7629,10 @@ function normalizeSetupAction(input, index) {
7626
7629
  throw new Error(`target.setup_actions[${index}] ${type} requires value.`);
7627
7630
  }
7628
7631
  const path7 = stringFromOwn(input, "path", "function_path", "functionPath", "window_path", "windowPath", "state_path", "statePath");
7629
- if ((type === "window_call" || type === "assert_window_value" || type === "assert_window_number") && !path7) {
7632
+ if ((type === "window_call" || type === "window_call_until" || type === "assert_window_value" || type === "assert_window_number") && !path7) {
7630
7633
  throw new Error(`target.setup_actions[${index}] ${type} requires path.`);
7631
7634
  }
7632
- const args = type === "window_call" ? normalizeSetupActionArgs(input, index) : void 0;
7635
+ const args = type === "window_call" || type === "window_call_until" ? normalizeSetupActionArgs(input, index) : void 0;
7633
7636
  const hasExpectedValue = hasOwn(input, "expected_value") || hasOwn(input, "expectedValue") || hasOwn(input, "expected") || hasOwn(input, "expect_value") || hasOwn(input, "expectValue") || hasOwn(input, "expect");
7634
7637
  if (type === "assert_window_value" && !hasExpectedValue) {
7635
7638
  throw new Error(`target.setup_actions[${index}] ${type} requires expected_value.`);
@@ -7646,6 +7649,24 @@ function normalizeSetupAction(input, index) {
7646
7649
  }
7647
7650
  }
7648
7651
  const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
7652
+ const untilPath = stringFromOwn(input, "until_path", "untilPath", "until_state_path", "untilStatePath", "until_window_path", "untilWindowPath", "until");
7653
+ 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");
7654
+ if (type === "window_call_until") {
7655
+ if (!untilPath) {
7656
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_path.`);
7657
+ }
7658
+ if (!hasUntilExpectedValue) {
7659
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_expected_value.`);
7660
+ }
7661
+ }
7662
+ const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts"));
7663
+ if (type === "window_call_until" && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
7664
+ throw new Error(`target.setup_actions[${index}].max_calls must be an integer from 1 to 100.`);
7665
+ }
7666
+ const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
7667
+ if (type === "window_call_until" && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
7668
+ throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
7669
+ }
7649
7670
  const steps = numberValue(input.steps);
7650
7671
  if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
7651
7672
  throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
@@ -7672,6 +7693,10 @@ function normalizeSetupAction(input, index) {
7672
7693
  path: path7,
7673
7694
  args,
7674
7695
  expect_return: hasExpectedReturn ? toJsonValue(valueFromOwn(input, "expect_return", "expectReturn", "expected_return", "expectedReturn")) : void 0,
7696
+ until_path: untilPath,
7697
+ until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
7698
+ max_calls: maxCalls,
7699
+ interval_ms: intervalMs,
7675
7700
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
7676
7701
  min_value: minValue,
7677
7702
  max_value: maxValue,
@@ -10608,6 +10633,7 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
10608
10633
  selector: result.selector ?? null,
10609
10634
  frame_selector: result.frame_selector ?? null,
10610
10635
  reason: result.reason || result.error || null,
10636
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text),
10611
10637
  })),
10612
10638
  optional_failed: optionalFailed.map((result) => ({
10613
10639
  ordinal: result.ordinal ?? null,
@@ -10615,6 +10641,7 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
10615
10641
  selector: result.selector ?? null,
10616
10642
  frame_selector: result.frame_selector ?? null,
10617
10643
  reason: result.reason || result.error || null,
10644
+ case_insensitive_text: compactProfileSetupSummaryText(result.case_insensitive_text),
10618
10645
  })),
10619
10646
  };
10620
10647
  }),
@@ -11530,6 +11557,34 @@ async function setupReadWindowValue(context, path) {
11530
11557
  return { ok: true, value: toJsonValue(current) };
11531
11558
  }, { path });
11532
11559
  }
11560
+ async function setupCallWindowFunction(context, path, args) {
11561
+ return await context.evaluate(async ({ path, args }) => {
11562
+ const toJsonValue = (value) => {
11563
+ if (value === null || value === undefined) return null;
11564
+ if (typeof value === "string" || typeof value === "boolean") return value;
11565
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
11566
+ if (Array.isArray(value)) return value.map(toJsonValue);
11567
+ if (typeof value === "object") {
11568
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
11569
+ }
11570
+ return String(value);
11571
+ };
11572
+ const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
11573
+ let parent = window;
11574
+ let current = window;
11575
+ for (const part of pathParts) {
11576
+ parent = current;
11577
+ current = current?.[part];
11578
+ }
11579
+ if (typeof current !== "function") return { ok: false, reason: "missing_function" };
11580
+ try {
11581
+ const returned = await current.apply(parent, Array.isArray(args) ? args : []);
11582
+ return { ok: true, returned: toJsonValue(returned) };
11583
+ } catch (error) {
11584
+ return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
11585
+ }
11586
+ }, { path, args });
11587
+ }
11533
11588
  function setupFrameSelector(action) {
11534
11589
  return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
11535
11590
  }
@@ -12047,32 +12102,7 @@ async function executeSetupAction(action, ordinal, viewport) {
12047
12102
  if (!path) return { ...base, path, reason: "missing_path" };
12048
12103
  const scope = await setupActionScope(action, timeout);
12049
12104
  if (!scope.ok) return setupScopeFailure(base, scope);
12050
- const result = await scope.context.evaluate(async ({ path, args }) => {
12051
- const toJsonValue = (value) => {
12052
- if (value === null || value === undefined) return null;
12053
- if (typeof value === "string" || typeof value === "boolean") return value;
12054
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
12055
- if (Array.isArray(value)) return value.map(toJsonValue);
12056
- if (typeof value === "object") {
12057
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
12058
- }
12059
- return String(value);
12060
- };
12061
- const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
12062
- let parent = window;
12063
- let current = window;
12064
- for (const part of pathParts) {
12065
- parent = current;
12066
- current = current?.[part];
12067
- }
12068
- if (typeof current !== "function") return { ok: false, reason: "missing_function" };
12069
- try {
12070
- const returned = await current.apply(parent, Array.isArray(args) ? args : []);
12071
- return { ok: true, returned: toJsonValue(returned) };
12072
- } catch (error) {
12073
- return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
12074
- }
12075
- }, { path, args });
12105
+ const result = await setupCallWindowFunction(scope.context, path, args);
12076
12106
  const hasExpectation = setupHasOwn(action, "expect_return")
12077
12107
  || setupHasOwn(action, "expectReturn")
12078
12108
  || setupHasOwn(action, "expected_return")
@@ -12097,6 +12127,126 @@ async function executeSetupAction(action, ordinal, viewport) {
12097
12127
  error: result.error || undefined,
12098
12128
  };
12099
12129
  }
12130
+ if (type === "window_call_until") {
12131
+ const path = String(action.path || action.function_path || action.functionPath || "");
12132
+ const untilPath = String(action.until_path || action.untilPath || action.until_state_path || action.untilStatePath || action.until_window_path || action.untilWindowPath || action.until || "");
12133
+ const args = Array.isArray(action.args) ? action.args : [];
12134
+ if (!path) return { ...base, path, reason: "missing_path" };
12135
+ if (!untilPath) return { ...base, path, reason: "missing_until_path" };
12136
+ const hasUntilExpected = setupHasOwn(action, "until_expected_value")
12137
+ || setupHasOwn(action, "untilExpectedValue")
12138
+ || setupHasOwn(action, "until_expected")
12139
+ || setupHasOwn(action, "untilExpected")
12140
+ || setupHasOwn(action, "until_value")
12141
+ || setupHasOwn(action, "untilValue")
12142
+ || setupHasOwn(action, "expected_value")
12143
+ || setupHasOwn(action, "expectedValue")
12144
+ || setupHasOwn(action, "expected");
12145
+ const untilExpected = setupHasOwn(action, "until_expected_value")
12146
+ ? action.until_expected_value
12147
+ : setupHasOwn(action, "untilExpectedValue")
12148
+ ? action.untilExpectedValue
12149
+ : setupHasOwn(action, "until_expected")
12150
+ ? action.until_expected
12151
+ : setupHasOwn(action, "untilExpected")
12152
+ ? action.untilExpected
12153
+ : setupHasOwn(action, "until_value")
12154
+ ? action.until_value
12155
+ : setupHasOwn(action, "untilValue")
12156
+ ? action.untilValue
12157
+ : setupHasOwn(action, "expected_value")
12158
+ ? action.expected_value
12159
+ : setupHasOwn(action, "expectedValue")
12160
+ ? action.expectedValue
12161
+ : action.expected;
12162
+ if (!hasUntilExpected) return { ...base, path, until_path: untilPath, reason: "missing_until_expected_value" };
12163
+ const maxCalls = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
12164
+ const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.call_interval_ms ?? action.callIntervalMs, 100) || 0)));
12165
+ const hasReturnExpectation = setupHasOwn(action, "expect_return")
12166
+ || setupHasOwn(action, "expectReturn")
12167
+ || setupHasOwn(action, "expected_return")
12168
+ || setupHasOwn(action, "expectedReturn");
12169
+ const expectedReturn = setupHasOwn(action, "expect_return")
12170
+ ? action.expect_return
12171
+ : setupHasOwn(action, "expectReturn")
12172
+ ? action.expectReturn
12173
+ : setupHasOwn(action, "expected_return")
12174
+ ? action.expected_return
12175
+ : action.expectedReturn;
12176
+ const scope = await setupActionScope(action, timeout);
12177
+ if (!scope.ok) return setupScopeFailure(base, scope);
12178
+ const startedAt = Date.now();
12179
+ let callCount = 0;
12180
+ let lastCallResult = null;
12181
+ let lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
12182
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
12183
+ return {
12184
+ ...base,
12185
+ ...setupScopeEvidence(scope),
12186
+ ok: true,
12187
+ path,
12188
+ arg_count: args.length,
12189
+ until_path: untilPath,
12190
+ until_value: setupJsonValue(lastPredicateResult.value),
12191
+ until_expected_value: setupJsonValue(untilExpected),
12192
+ call_count: callCount,
12193
+ max_calls: maxCalls,
12194
+ interval_ms: intervalMs,
12195
+ timeout_ms: timeout,
12196
+ };
12197
+ }
12198
+ while (callCount < maxCalls && Date.now() - startedAt <= timeout) {
12199
+ lastCallResult = await setupCallWindowFunction(scope.context, path, args);
12200
+ callCount += 1;
12201
+ if (!lastCallResult.ok) break;
12202
+ if (hasReturnExpectation && !setupValuesEqual(lastCallResult.returned, expectedReturn)) break;
12203
+ lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
12204
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
12205
+ return {
12206
+ ...base,
12207
+ ...setupScopeEvidence(scope),
12208
+ ok: true,
12209
+ path,
12210
+ arg_count: args.length,
12211
+ returned: setupJsonValue(lastCallResult.returned),
12212
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
12213
+ until_path: untilPath,
12214
+ until_value: setupJsonValue(lastPredicateResult.value),
12215
+ until_expected_value: setupJsonValue(untilExpected),
12216
+ call_count: callCount,
12217
+ max_calls: maxCalls,
12218
+ interval_ms: intervalMs,
12219
+ timeout_ms: timeout,
12220
+ };
12221
+ }
12222
+ if (callCount < maxCalls && intervalMs) await page.waitForTimeout(intervalMs);
12223
+ }
12224
+ const returnExpectationMet = !hasReturnExpectation || setupValuesEqual(lastCallResult?.returned, expectedReturn);
12225
+ return {
12226
+ ...base,
12227
+ ...setupScopeEvidence(scope),
12228
+ path,
12229
+ arg_count: args.length,
12230
+ returned: setupJsonValue(lastCallResult?.returned),
12231
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
12232
+ until_path: untilPath,
12233
+ until_value: setupJsonValue(lastPredicateResult?.value),
12234
+ until_expected_value: setupJsonValue(untilExpected),
12235
+ call_count: callCount,
12236
+ max_calls: maxCalls,
12237
+ interval_ms: intervalMs,
12238
+ timeout_ms: timeout,
12239
+ reason: lastCallResult && !lastCallResult.ok
12240
+ ? lastCallResult.reason
12241
+ : hasReturnExpectation && !returnExpectationMet
12242
+ ? "unexpected_return_value"
12243
+ : Date.now() - startedAt > timeout
12244
+ ? "timeout"
12245
+ : "until_condition_not_met",
12246
+ error: lastCallResult?.error || undefined,
12247
+ missing_part: lastPredicateResult?.missing_part || undefined,
12248
+ };
12249
+ }
12100
12250
  if (type === "assert_window_value") {
12101
12251
  const path = String(action.path || action.window_path || action.windowPath || "");
12102
12252
  const hasExpected = setupHasOwn(action, "expected_value")
@@ -14325,6 +14475,19 @@ function profileSetupSummaryMarkdown(result) {
14325
14475
  const observedPath = cliString(viewport.observed_path);
14326
14476
  lines.push(`- ${name}: ${ok}, ${resultCount} result(s), ${screenshotCount} setup screenshot(s), ${clicked} click(s)${clickCountActions ? `, ${clickCountActions} click_count action(s)` : ""}${observedPath ? `, path ${observedPath}` : ""}`);
14327
14477
  }
14478
+ const failedDetails = viewports.flatMap((viewport) => {
14479
+ const name = cliString(viewport.name) || "viewport";
14480
+ const failed = Array.isArray(viewport.failed) ? viewport.failed.map(cliRecord).filter((item) => Boolean(item)) : [];
14481
+ return failed.map((failure) => ({ name, failure }));
14482
+ });
14483
+ for (const { name, failure } of failedDetails.slice(0, 8)) {
14484
+ const action = cliString(failure.action) || "setup_action";
14485
+ const selector = cliString(failure.selector);
14486
+ const reason = cliString(failure.reason);
14487
+ const caseInsensitiveText = cliString(failure.case_insensitive_text);
14488
+ lines.push(`- failed ${name}: ${action}${selector ? ` ${markdownInlineCode(selector)}` : ""}${reason ? ` reason ${markdownInlineCode(reason)}` : ""}${caseInsensitiveText ? `; case-insensitive sample ${markdownInlineCode(caseInsensitiveText, 140)}` : ""}`);
14489
+ }
14490
+ if (failedDetails.length > 8) lines.push(`- ${failedDetails.length - 8} additional failed setup action(s) omitted.`);
14328
14491
  if (viewports.length > 8) lines.push(`- ${viewports.length - 8} additional viewport(s) omitted from setup summary.`);
14329
14492
  return lines;
14330
14493
  }