@riddledc/riddle-proof 0.7.132 → 0.7.134

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",
@@ -451,6 +452,19 @@ function profileSetupActionCounts(results) {
451
452
  function profileSetupScreenshotLabels(results) {
452
453
  return results.filter((result) => profileSetupResultAction(result) === "screenshot" && result.ok !== false && typeof result.screenshot_label === "string").map((result) => result.screenshot_label).filter(Boolean);
453
454
  }
455
+ function profileSetupWindowCallUntilReceipts(results) {
456
+ return results.filter((result) => profileSetupResultAction(result) === "window_call_until").map((result) => ({
457
+ ordinal: result.ordinal ?? null,
458
+ ok: result.ok !== false,
459
+ path: result.path ?? null,
460
+ until_path: result.until_path ?? null,
461
+ until_value: result.until_value ?? null,
462
+ until_expected_value: result.until_expected_value ?? null,
463
+ call_count: result.call_count ?? null,
464
+ max_calls: result.max_calls ?? null,
465
+ reason: result.reason ?? result.error ?? null
466
+ }));
467
+ }
454
468
  function sampleProfileSetupSummaryItems(items, limit) {
455
469
  if (items.length <= limit) return items;
456
470
  const firstCount = Math.floor(limit / 2);
@@ -476,6 +490,9 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
476
490
  const optionalFailed = results.filter((result) => result.ok === false && result.optional === true);
477
491
  const successfulClicks = results.filter((result) => profileSetupResultAction(result) === "click" && result.ok !== false);
478
492
  const clickCountValues = successfulClicks.map((result) => typeof result.click_count === "number" && Number.isFinite(result.click_count) && result.click_count > 1 ? result.click_count : void 0).filter((value) => value !== void 0);
493
+ const windowCallUntilReceipts = profileSetupWindowCallUntilReceipts(results);
494
+ const windowCallUntilCallCounts = windowCallUntilReceipts.map((result) => typeof result.call_count === "number" && Number.isFinite(result.call_count) ? result.call_count : void 0).filter((value) => value !== void 0);
495
+ const sampledWindowCallUntilReceipts = sampleProfileSetupSummaryItems(windowCallUntilReceipts, 8);
479
496
  const clickedItems = results.filter((result) => profileSetupResultAction(result) === "click" && result.ok !== false).map((result) => {
480
497
  const clickCount = typeof result.click_count === "number" && Number.isFinite(result.click_count) && result.click_count > 1 ? result.click_count : void 0;
481
498
  return {
@@ -508,6 +525,10 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
508
525
  clicked_truncated: clickedItems.length > clicked.length,
509
526
  click_count_action_total: clickCountValues.length,
510
527
  click_count_value_total: clickCountValues.reduce((sum, value) => sum + value, 0),
528
+ window_call_until_total: windowCallUntilReceipts.length,
529
+ window_call_until_call_total: windowCallUntilCallCounts.reduce((sum, value) => sum + value, 0),
530
+ window_call_until_truncated: windowCallUntilReceipts.length > sampledWindowCallUntilReceipts.length,
531
+ window_call_until: sampledWindowCallUntilReceipts,
511
532
  clicked,
512
533
  text_samples,
513
534
  failed: failed.map((result) => ({
@@ -560,7 +581,7 @@ function isSupportedCheckType(value) {
560
581
  }
561
582
  function normalizeSetupActionType(value, index) {
562
583
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
563
- 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;
584
+ 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;
564
585
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
565
586
  return normalized;
566
587
  }
@@ -691,10 +712,10 @@ function normalizeSetupAction(input, index) {
691
712
  throw new Error(`target.setup_actions[${index}] ${type} requires value.`);
692
713
  }
693
714
  const path = stringFromOwn(input, "path", "function_path", "functionPath", "window_path", "windowPath", "state_path", "statePath");
694
- if ((type === "window_call" || type === "assert_window_value" || type === "assert_window_number") && !path) {
715
+ if ((type === "window_call" || type === "window_call_until" || type === "assert_window_value" || type === "assert_window_number") && !path) {
695
716
  throw new Error(`target.setup_actions[${index}] ${type} requires path.`);
696
717
  }
697
- const args = type === "window_call" ? normalizeSetupActionArgs(input, index) : void 0;
718
+ const args = type === "window_call" || type === "window_call_until" ? normalizeSetupActionArgs(input, index) : void 0;
698
719
  const hasExpectedValue = hasOwn(input, "expected_value") || hasOwn(input, "expectedValue") || hasOwn(input, "expected") || hasOwn(input, "expect_value") || hasOwn(input, "expectValue") || hasOwn(input, "expect");
699
720
  if (type === "assert_window_value" && !hasExpectedValue) {
700
721
  throw new Error(`target.setup_actions[${index}] ${type} requires expected_value.`);
@@ -711,6 +732,24 @@ function normalizeSetupAction(input, index) {
711
732
  }
712
733
  }
713
734
  const hasExpectedReturn = hasOwn(input, "expect_return") || hasOwn(input, "expectReturn") || hasOwn(input, "expected_return") || hasOwn(input, "expectedReturn");
735
+ const untilPath = stringFromOwn(input, "until_path", "untilPath", "until_state_path", "untilStatePath", "until_window_path", "untilWindowPath", "until");
736
+ 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");
737
+ if (type === "window_call_until") {
738
+ if (!untilPath) {
739
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_path.`);
740
+ }
741
+ if (!hasUntilExpectedValue) {
742
+ throw new Error(`target.setup_actions[${index}] ${type} requires until_expected_value.`);
743
+ }
744
+ }
745
+ const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts"));
746
+ if (type === "window_call_until" && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
747
+ throw new Error(`target.setup_actions[${index}].max_calls must be an integer from 1 to 100.`);
748
+ }
749
+ const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
750
+ if (type === "window_call_until" && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
751
+ throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
752
+ }
714
753
  const steps = numberValue(input.steps);
715
754
  if (type === "drag" && steps !== void 0 && (!Number.isInteger(steps) || steps < 1 || steps > 100)) {
716
755
  throw new Error(`target.setup_actions[${index}].steps must be an integer from 1 to 100.`);
@@ -737,6 +776,10 @@ function normalizeSetupAction(input, index) {
737
776
  path,
738
777
  args,
739
778
  expect_return: hasExpectedReturn ? toJsonValue(valueFromOwn(input, "expect_return", "expectReturn", "expected_return", "expectedReturn")) : void 0,
779
+ until_path: untilPath,
780
+ until_expected_value: hasUntilExpectedValue ? toJsonValue(valueFromOwn(input, "until_expected_value", "untilExpectedValue", "until_expected", "untilExpected", "until_value", "untilValue", "expected_value", "expectedValue", "expected")) : void 0,
781
+ max_calls: maxCalls,
782
+ interval_ms: intervalMs,
740
783
  expected_value: hasExpectedValue ? toJsonValue(rawExpectedValue) : void 0,
741
784
  min_value: minValue,
742
785
  max_value: maxValue,
@@ -4613,6 +4656,34 @@ async function setupReadWindowValue(context, path) {
4613
4656
  return { ok: true, value: toJsonValue(current) };
4614
4657
  }, { path });
4615
4658
  }
4659
+ async function setupCallWindowFunction(context, path, args) {
4660
+ return await context.evaluate(async ({ path, args }) => {
4661
+ const toJsonValue = (value) => {
4662
+ if (value === null || value === undefined) return null;
4663
+ if (typeof value === "string" || typeof value === "boolean") return value;
4664
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
4665
+ if (Array.isArray(value)) return value.map(toJsonValue);
4666
+ if (typeof value === "object") {
4667
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
4668
+ }
4669
+ return String(value);
4670
+ };
4671
+ const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
4672
+ let parent = window;
4673
+ let current = window;
4674
+ for (const part of pathParts) {
4675
+ parent = current;
4676
+ current = current?.[part];
4677
+ }
4678
+ if (typeof current !== "function") return { ok: false, reason: "missing_function" };
4679
+ try {
4680
+ const returned = await current.apply(parent, Array.isArray(args) ? args : []);
4681
+ return { ok: true, returned: toJsonValue(returned) };
4682
+ } catch (error) {
4683
+ return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
4684
+ }
4685
+ }, { path, args });
4686
+ }
4616
4687
  function setupFrameSelector(action) {
4617
4688
  return String(action?.frame_selector || action?.frameSelector || action?.iframe_selector || action?.iframeSelector || "").trim();
4618
4689
  }
@@ -5130,32 +5201,7 @@ async function executeSetupAction(action, ordinal, viewport) {
5130
5201
  if (!path) return { ...base, path, reason: "missing_path" };
5131
5202
  const scope = await setupActionScope(action, timeout);
5132
5203
  if (!scope.ok) return setupScopeFailure(base, scope);
5133
- const result = await scope.context.evaluate(async ({ path, args }) => {
5134
- const toJsonValue = (value) => {
5135
- if (value === null || value === undefined) return null;
5136
- if (typeof value === "string" || typeof value === "boolean") return value;
5137
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
5138
- if (Array.isArray(value)) return value.map(toJsonValue);
5139
- if (typeof value === "object") {
5140
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
5141
- }
5142
- return String(value);
5143
- };
5144
- const pathParts = String(path || "").split(".").map((part) => part.trim()).filter(Boolean);
5145
- let parent = window;
5146
- let current = window;
5147
- for (const part of pathParts) {
5148
- parent = current;
5149
- current = current?.[part];
5150
- }
5151
- if (typeof current !== "function") return { ok: false, reason: "missing_function" };
5152
- try {
5153
- const returned = await current.apply(parent, Array.isArray(args) ? args : []);
5154
- return { ok: true, returned: toJsonValue(returned) };
5155
- } catch (error) {
5156
- return { ok: false, reason: "function_threw", error: String(error && error.message ? error.message : error).slice(0, 1000) };
5157
- }
5158
- }, { path, args });
5204
+ const result = await setupCallWindowFunction(scope.context, path, args);
5159
5205
  const hasExpectation = setupHasOwn(action, "expect_return")
5160
5206
  || setupHasOwn(action, "expectReturn")
5161
5207
  || setupHasOwn(action, "expected_return")
@@ -5180,6 +5226,126 @@ async function executeSetupAction(action, ordinal, viewport) {
5180
5226
  error: result.error || undefined,
5181
5227
  };
5182
5228
  }
5229
+ if (type === "window_call_until") {
5230
+ const path = String(action.path || action.function_path || action.functionPath || "");
5231
+ const untilPath = String(action.until_path || action.untilPath || action.until_state_path || action.untilStatePath || action.until_window_path || action.untilWindowPath || action.until || "");
5232
+ const args = Array.isArray(action.args) ? action.args : [];
5233
+ if (!path) return { ...base, path, reason: "missing_path" };
5234
+ if (!untilPath) return { ...base, path, reason: "missing_until_path" };
5235
+ const hasUntilExpected = setupHasOwn(action, "until_expected_value")
5236
+ || setupHasOwn(action, "untilExpectedValue")
5237
+ || setupHasOwn(action, "until_expected")
5238
+ || setupHasOwn(action, "untilExpected")
5239
+ || setupHasOwn(action, "until_value")
5240
+ || setupHasOwn(action, "untilValue")
5241
+ || setupHasOwn(action, "expected_value")
5242
+ || setupHasOwn(action, "expectedValue")
5243
+ || setupHasOwn(action, "expected");
5244
+ const untilExpected = setupHasOwn(action, "until_expected_value")
5245
+ ? action.until_expected_value
5246
+ : setupHasOwn(action, "untilExpectedValue")
5247
+ ? action.untilExpectedValue
5248
+ : setupHasOwn(action, "until_expected")
5249
+ ? action.until_expected
5250
+ : setupHasOwn(action, "untilExpected")
5251
+ ? action.untilExpected
5252
+ : setupHasOwn(action, "until_value")
5253
+ ? action.until_value
5254
+ : setupHasOwn(action, "untilValue")
5255
+ ? action.untilValue
5256
+ : setupHasOwn(action, "expected_value")
5257
+ ? action.expected_value
5258
+ : setupHasOwn(action, "expectedValue")
5259
+ ? action.expectedValue
5260
+ : action.expected;
5261
+ if (!hasUntilExpected) return { ...base, path, until_path: untilPath, reason: "missing_until_expected_value" };
5262
+ 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)));
5263
+ 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)));
5264
+ const hasReturnExpectation = setupHasOwn(action, "expect_return")
5265
+ || setupHasOwn(action, "expectReturn")
5266
+ || setupHasOwn(action, "expected_return")
5267
+ || setupHasOwn(action, "expectedReturn");
5268
+ const expectedReturn = setupHasOwn(action, "expect_return")
5269
+ ? action.expect_return
5270
+ : setupHasOwn(action, "expectReturn")
5271
+ ? action.expectReturn
5272
+ : setupHasOwn(action, "expected_return")
5273
+ ? action.expected_return
5274
+ : action.expectedReturn;
5275
+ const scope = await setupActionScope(action, timeout);
5276
+ if (!scope.ok) return setupScopeFailure(base, scope);
5277
+ const startedAt = Date.now();
5278
+ let callCount = 0;
5279
+ let lastCallResult = null;
5280
+ let lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
5281
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
5282
+ return {
5283
+ ...base,
5284
+ ...setupScopeEvidence(scope),
5285
+ ok: true,
5286
+ path,
5287
+ arg_count: args.length,
5288
+ until_path: untilPath,
5289
+ until_value: setupJsonValue(lastPredicateResult.value),
5290
+ until_expected_value: setupJsonValue(untilExpected),
5291
+ call_count: callCount,
5292
+ max_calls: maxCalls,
5293
+ interval_ms: intervalMs,
5294
+ timeout_ms: timeout,
5295
+ };
5296
+ }
5297
+ while (callCount < maxCalls && Date.now() - startedAt <= timeout) {
5298
+ lastCallResult = await setupCallWindowFunction(scope.context, path, args);
5299
+ callCount += 1;
5300
+ if (!lastCallResult.ok) break;
5301
+ if (hasReturnExpectation && !setupValuesEqual(lastCallResult.returned, expectedReturn)) break;
5302
+ lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
5303
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
5304
+ return {
5305
+ ...base,
5306
+ ...setupScopeEvidence(scope),
5307
+ ok: true,
5308
+ path,
5309
+ arg_count: args.length,
5310
+ returned: setupJsonValue(lastCallResult.returned),
5311
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
5312
+ until_path: untilPath,
5313
+ until_value: setupJsonValue(lastPredicateResult.value),
5314
+ until_expected_value: setupJsonValue(untilExpected),
5315
+ call_count: callCount,
5316
+ max_calls: maxCalls,
5317
+ interval_ms: intervalMs,
5318
+ timeout_ms: timeout,
5319
+ };
5320
+ }
5321
+ if (callCount < maxCalls && intervalMs) await page.waitForTimeout(intervalMs);
5322
+ }
5323
+ const returnExpectationMet = !hasReturnExpectation || setupValuesEqual(lastCallResult?.returned, expectedReturn);
5324
+ return {
5325
+ ...base,
5326
+ ...setupScopeEvidence(scope),
5327
+ path,
5328
+ arg_count: args.length,
5329
+ returned: setupJsonValue(lastCallResult?.returned),
5330
+ expected_return: hasReturnExpectation ? setupJsonValue(expectedReturn) : undefined,
5331
+ until_path: untilPath,
5332
+ until_value: setupJsonValue(lastPredicateResult?.value),
5333
+ until_expected_value: setupJsonValue(untilExpected),
5334
+ call_count: callCount,
5335
+ max_calls: maxCalls,
5336
+ interval_ms: intervalMs,
5337
+ timeout_ms: timeout,
5338
+ reason: lastCallResult && !lastCallResult.ok
5339
+ ? lastCallResult.reason
5340
+ : hasReturnExpectation && !returnExpectationMet
5341
+ ? "unexpected_return_value"
5342
+ : Date.now() - startedAt > timeout
5343
+ ? "timeout"
5344
+ : "until_condition_not_met",
5345
+ error: lastCallResult?.error || undefined,
5346
+ missing_part: lastPredicateResult?.missing_part || undefined,
5347
+ };
5348
+ }
5183
5349
  if (type === "assert_window_value") {
5184
5350
  const path = String(action.path || action.window_path || action.windowPath || "");
5185
5351
  const hasExpected = setupHasOwn(action, "expected_value")