@riddledc/riddle-proof 0.7.47 → 0.7.48

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
@@ -237,16 +237,21 @@ where the second request must carry newer state.
237
237
  `target.setup_actions` is optional. Use it when the meaningful proof surface
238
238
  appears only after a picker, tab, login stub, storage seed, form fill,
239
239
  transport control, or other bounded interaction. Supported setup actions are
240
- `click`, `fill`, `set_input_value`, `local_storage`, `session_storage`,
241
- `clear_storage`, `wait`, `wait_for_selector`, and `wait_for_text`; a failed
242
- setup action is recorded as a failed `setup_actions_succeeded` check so the
243
- profile cannot pass without reaching the intended state. Text-matched `click`
244
- actions prefer visible matching elements, which keeps responsive layouts from
245
- selecting hidden desktop or mobile-only links. `local_storage` and
246
- `session_storage` accept a `key` plus string `value` or JSON `json` /
247
- `value_json`, and can reload the page with `reload: true`. `clear_storage`
248
- clears `local`, `session`, or `both` browser storage scopes, defaults to
249
- `both`, and can also reload with `reload: true`.
240
+ `click`, `fill`, `set_input_value`, `assert_text_visible`,
241
+ `assert_text_absent`, `assert_selector_count`, `local_storage`,
242
+ `session_storage`, `clear_storage`, `wait`, `wait_for_selector`, and
243
+ `wait_for_text`; a failed setup action is recorded as a failed
244
+ `setup_actions_succeeded` check so the profile cannot pass without reaching
245
+ the intended state. Text-matched `click` actions prefer visible matching
246
+ elements, which keeps responsive layouts from selecting hidden desktop or
247
+ mobile-only links. Use setup assertions when the pre-click or pre-navigation
248
+ state is part of the contract, for example a fresh row must be present, stale
249
+ copy must be absent, or exactly one source link must exist before clicking into
250
+ the final route. `assert_selector_count` accepts `expected_count`.
251
+ `local_storage` and `session_storage` accept a `key` plus string `value` or
252
+ JSON `json` / `value_json`, and can reload the page with `reload: true`.
253
+ `clear_storage` clears `local`, `session`, or `both` browser storage scopes,
254
+ defaults to `both`, and can also reload with `reload: true`.
250
255
 
251
256
  `target.timeout_sec` is optional. Use it for known-heavy profile targets so the
252
257
  profile carries its own hosted Riddle worker budget; an explicit CLI `--timeout`
@@ -32,6 +32,9 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
32
32
  "click",
33
33
  "fill",
34
34
  "set_input_value",
35
+ "assert_text_visible",
36
+ "assert_text_absent",
37
+ "assert_selector_count",
35
38
  "local_storage",
36
39
  "session_storage",
37
40
  "clear_storage",
@@ -205,12 +208,19 @@ function normalizeSetupAction(input, index) {
205
208
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
206
209
  const type = normalizeSetupActionType(stringValue(input.type), index);
207
210
  const selector = stringValue(input.selector);
208
- if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
211
+ if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
209
212
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
210
213
  }
211
214
  if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
212
215
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
213
216
  }
217
+ if ((type === "assert_text_visible" || type === "assert_text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
218
+ throw new Error(`target.setup_actions[${index}] ${type} requires text or pattern.`);
219
+ }
220
+ const expectedCount = numberValue(input.expected_count ?? input.expectedCount ?? input.count);
221
+ if (type === "assert_selector_count" && (expectedCount === void 0 || !Number.isInteger(expectedCount) || expectedCount < 0)) {
222
+ throw new Error(`target.setup_actions[${index}] ${type} requires non-negative integer expected_count.`);
223
+ }
214
224
  const value = stringFromOwn(input, "value", "input_value", "inputValue");
215
225
  const hasJsonValue = hasOwn(input, "value_json") || hasOwn(input, "valueJson") || hasOwn(input, "json");
216
226
  if ((type === "fill" || type === "set_input_value") && value === void 0 && !hasJsonValue) {
@@ -233,6 +243,7 @@ function normalizeSetupAction(input, index) {
233
243
  pattern: stringValue(input.pattern),
234
244
  flags: stringValue(input.flags),
235
245
  index: numberValue(input.index),
246
+ expected_count: expectedCount,
236
247
  ms: numberValue(input.ms) ?? numberValue(input.wait_ms) ?? numberValue(input.waitMs),
237
248
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
238
249
  after_ms: numberValue(input.after_ms) ?? numberValue(input.afterMs),
@@ -2150,6 +2161,19 @@ async function executeSetupAction(action, ordinal) {
2150
2161
  await locator.nth(targetIndex).fill(value, { timeout });
2151
2162
  return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
2152
2163
  }
2164
+ if (type === "assert_selector_count") {
2165
+ const locator = page.locator(action.selector);
2166
+ const expectedCount = setupNumber(action.expected_count, -1);
2167
+ if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
2168
+ const startedAt = Date.now();
2169
+ let count = 0;
2170
+ while (Date.now() - startedAt <= timeout) {
2171
+ count = await locator.count().catch(() => 0);
2172
+ if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2173
+ await page.waitForTimeout(100);
2174
+ }
2175
+ return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2176
+ }
2153
2177
  if (type === "wait_for_text") {
2154
2178
  const locator = page.locator(action.selector);
2155
2179
  const startedAt = Date.now();
@@ -2167,6 +2191,47 @@ async function executeSetupAction(action, ordinal) {
2167
2191
  }
2168
2192
  return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2169
2193
  }
2194
+ if (type === "assert_text_visible" || type === "assert_text_absent") {
2195
+ const locator = page.locator(action.selector);
2196
+ const startedAt = Date.now();
2197
+ let lastText = "";
2198
+ let matchedText = "";
2199
+ let hiddenMatch = false;
2200
+ while (Date.now() - startedAt <= timeout) {
2201
+ const count = await locator.count().catch(() => 0);
2202
+ let matched = false;
2203
+ matchedText = "";
2204
+ hiddenMatch = false;
2205
+ for (let index = 0; index < count; index += 1) {
2206
+ const text = await setupLocatorText(locator, index);
2207
+ lastText = text || lastText;
2208
+ if (setupTextMatches(text, action)) {
2209
+ matched = true;
2210
+ matchedText = text;
2211
+ if (type === "assert_text_visible") {
2212
+ const visible = await setupLocatorVisible(locator, index);
2213
+ if (visible) {
2214
+ return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2215
+ }
2216
+ hiddenMatch = true;
2217
+ break;
2218
+ }
2219
+ break;
2220
+ }
2221
+ }
2222
+ if (type === "assert_text_absent" && !matched) {
2223
+ return { ...base, ok: true, count, timeout_ms: timeout };
2224
+ }
2225
+ await page.waitForTimeout(100);
2226
+ }
2227
+ if (type === "assert_text_visible") {
2228
+ if (hiddenMatch) {
2229
+ return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2230
+ }
2231
+ return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2232
+ }
2233
+ return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2234
+ }
2170
2235
  return { ...base, reason: "unsupported_action" };
2171
2236
  } catch (error) {
2172
2237
  return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
package/dist/cli.cjs CHANGED
@@ -6905,6 +6905,9 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
6905
6905
  "click",
6906
6906
  "fill",
6907
6907
  "set_input_value",
6908
+ "assert_text_visible",
6909
+ "assert_text_absent",
6910
+ "assert_selector_count",
6908
6911
  "local_storage",
6909
6912
  "session_storage",
6910
6913
  "clear_storage",
@@ -7078,12 +7081,19 @@ function normalizeSetupAction(input, index) {
7078
7081
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
7079
7082
  const type = normalizeSetupActionType(stringValue2(input.type), index);
7080
7083
  const selector = stringValue2(input.selector);
7081
- if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
7084
+ if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
7082
7085
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
7083
7086
  }
7084
7087
  if (type === "wait_for_text" && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7085
7088
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
7086
7089
  }
7090
+ if ((type === "assert_text_visible" || type === "assert_text_absent") && !stringValue2(input.text) && !stringValue2(input.pattern)) {
7091
+ throw new Error(`target.setup_actions[${index}] ${type} requires text or pattern.`);
7092
+ }
7093
+ const expectedCount = numberValue(input.expected_count ?? input.expectedCount ?? input.count);
7094
+ if (type === "assert_selector_count" && (expectedCount === void 0 || !Number.isInteger(expectedCount) || expectedCount < 0)) {
7095
+ throw new Error(`target.setup_actions[${index}] ${type} requires non-negative integer expected_count.`);
7096
+ }
7087
7097
  const value = stringFromOwn(input, "value", "input_value", "inputValue");
7088
7098
  const hasJsonValue = hasOwn(input, "value_json") || hasOwn(input, "valueJson") || hasOwn(input, "json");
7089
7099
  if ((type === "fill" || type === "set_input_value") && value === void 0 && !hasJsonValue) {
@@ -7106,6 +7116,7 @@ function normalizeSetupAction(input, index) {
7106
7116
  pattern: stringValue2(input.pattern),
7107
7117
  flags: stringValue2(input.flags),
7108
7118
  index: numberValue(input.index),
7119
+ expected_count: expectedCount,
7109
7120
  ms: numberValue(input.ms) ?? numberValue(input.wait_ms) ?? numberValue(input.waitMs),
7110
7121
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
7111
7122
  after_ms: numberValue(input.after_ms) ?? numberValue(input.afterMs),
@@ -9007,6 +9018,19 @@ async function executeSetupAction(action, ordinal) {
9007
9018
  await locator.nth(targetIndex).fill(value, { timeout });
9008
9019
  return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
9009
9020
  }
9021
+ if (type === "assert_selector_count") {
9022
+ const locator = page.locator(action.selector);
9023
+ const expectedCount = setupNumber(action.expected_count, -1);
9024
+ if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
9025
+ const startedAt = Date.now();
9026
+ let count = 0;
9027
+ while (Date.now() - startedAt <= timeout) {
9028
+ count = await locator.count().catch(() => 0);
9029
+ if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
9030
+ await page.waitForTimeout(100);
9031
+ }
9032
+ return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
9033
+ }
9010
9034
  if (type === "wait_for_text") {
9011
9035
  const locator = page.locator(action.selector);
9012
9036
  const startedAt = Date.now();
@@ -9024,6 +9048,47 @@ async function executeSetupAction(action, ordinal) {
9024
9048
  }
9025
9049
  return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
9026
9050
  }
9051
+ if (type === "assert_text_visible" || type === "assert_text_absent") {
9052
+ const locator = page.locator(action.selector);
9053
+ const startedAt = Date.now();
9054
+ let lastText = "";
9055
+ let matchedText = "";
9056
+ let hiddenMatch = false;
9057
+ while (Date.now() - startedAt <= timeout) {
9058
+ const count = await locator.count().catch(() => 0);
9059
+ let matched = false;
9060
+ matchedText = "";
9061
+ hiddenMatch = false;
9062
+ for (let index = 0; index < count; index += 1) {
9063
+ const text = await setupLocatorText(locator, index);
9064
+ lastText = text || lastText;
9065
+ if (setupTextMatches(text, action)) {
9066
+ matched = true;
9067
+ matchedText = text;
9068
+ if (type === "assert_text_visible") {
9069
+ const visible = await setupLocatorVisible(locator, index);
9070
+ if (visible) {
9071
+ return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
9072
+ }
9073
+ hiddenMatch = true;
9074
+ break;
9075
+ }
9076
+ break;
9077
+ }
9078
+ }
9079
+ if (type === "assert_text_absent" && !matched) {
9080
+ return { ...base, ok: true, count, timeout_ms: timeout };
9081
+ }
9082
+ await page.waitForTimeout(100);
9083
+ }
9084
+ if (type === "assert_text_visible") {
9085
+ if (hiddenMatch) {
9086
+ return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
9087
+ }
9088
+ return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
9089
+ }
9090
+ return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
9091
+ }
9027
9092
  return { ...base, reason: "unsupported_action" };
9028
9093
  } catch (error) {
9029
9094
  return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  profileStatusExitCode,
11
11
  resolveRiddleProofProfileTargetUrl,
12
12
  resolveRiddleProofProfileTimeoutSec
13
- } from "./chunk-NJDY7HYE.js";
13
+ } from "./chunk-V2KNQTQD.js";
14
14
  import {
15
15
  createRiddleApiClient,
16
16
  parseRiddleViewport
package/dist/index.cjs CHANGED
@@ -8746,6 +8746,9 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
8746
8746
  "click",
8747
8747
  "fill",
8748
8748
  "set_input_value",
8749
+ "assert_text_visible",
8750
+ "assert_text_absent",
8751
+ "assert_selector_count",
8749
8752
  "local_storage",
8750
8753
  "session_storage",
8751
8754
  "clear_storage",
@@ -8919,12 +8922,19 @@ function normalizeSetupAction(input, index) {
8919
8922
  if (!isRecord2(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
8920
8923
  const type = normalizeSetupActionType(stringValue5(input.type), index);
8921
8924
  const selector = stringValue5(input.selector);
8922
- if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
8925
+ if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
8923
8926
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
8924
8927
  }
8925
8928
  if (type === "wait_for_text" && !stringValue5(input.text) && !stringValue5(input.pattern)) {
8926
8929
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
8927
8930
  }
8931
+ if ((type === "assert_text_visible" || type === "assert_text_absent") && !stringValue5(input.text) && !stringValue5(input.pattern)) {
8932
+ throw new Error(`target.setup_actions[${index}] ${type} requires text or pattern.`);
8933
+ }
8934
+ const expectedCount = numberValue3(input.expected_count ?? input.expectedCount ?? input.count);
8935
+ if (type === "assert_selector_count" && (expectedCount === void 0 || !Number.isInteger(expectedCount) || expectedCount < 0)) {
8936
+ throw new Error(`target.setup_actions[${index}] ${type} requires non-negative integer expected_count.`);
8937
+ }
8928
8938
  const value = stringFromOwn(input, "value", "input_value", "inputValue");
8929
8939
  const hasJsonValue = hasOwn(input, "value_json") || hasOwn(input, "valueJson") || hasOwn(input, "json");
8930
8940
  if ((type === "fill" || type === "set_input_value") && value === void 0 && !hasJsonValue) {
@@ -8947,6 +8957,7 @@ function normalizeSetupAction(input, index) {
8947
8957
  pattern: stringValue5(input.pattern),
8948
8958
  flags: stringValue5(input.flags),
8949
8959
  index: numberValue3(input.index),
8960
+ expected_count: expectedCount,
8950
8961
  ms: numberValue3(input.ms) ?? numberValue3(input.wait_ms) ?? numberValue3(input.waitMs),
8951
8962
  timeout_ms: numberValue3(input.timeout_ms) ?? numberValue3(input.timeoutMs),
8952
8963
  after_ms: numberValue3(input.after_ms) ?? numberValue3(input.afterMs),
@@ -10864,6 +10875,19 @@ async function executeSetupAction(action, ordinal) {
10864
10875
  await locator.nth(targetIndex).fill(value, { timeout });
10865
10876
  return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
10866
10877
  }
10878
+ if (type === "assert_selector_count") {
10879
+ const locator = page.locator(action.selector);
10880
+ const expectedCount = setupNumber(action.expected_count, -1);
10881
+ if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
10882
+ const startedAt = Date.now();
10883
+ let count = 0;
10884
+ while (Date.now() - startedAt <= timeout) {
10885
+ count = await locator.count().catch(() => 0);
10886
+ if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
10887
+ await page.waitForTimeout(100);
10888
+ }
10889
+ return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
10890
+ }
10867
10891
  if (type === "wait_for_text") {
10868
10892
  const locator = page.locator(action.selector);
10869
10893
  const startedAt = Date.now();
@@ -10881,6 +10905,47 @@ async function executeSetupAction(action, ordinal) {
10881
10905
  }
10882
10906
  return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
10883
10907
  }
10908
+ if (type === "assert_text_visible" || type === "assert_text_absent") {
10909
+ const locator = page.locator(action.selector);
10910
+ const startedAt = Date.now();
10911
+ let lastText = "";
10912
+ let matchedText = "";
10913
+ let hiddenMatch = false;
10914
+ while (Date.now() - startedAt <= timeout) {
10915
+ const count = await locator.count().catch(() => 0);
10916
+ let matched = false;
10917
+ matchedText = "";
10918
+ hiddenMatch = false;
10919
+ for (let index = 0; index < count; index += 1) {
10920
+ const text = await setupLocatorText(locator, index);
10921
+ lastText = text || lastText;
10922
+ if (setupTextMatches(text, action)) {
10923
+ matched = true;
10924
+ matchedText = text;
10925
+ if (type === "assert_text_visible") {
10926
+ const visible = await setupLocatorVisible(locator, index);
10927
+ if (visible) {
10928
+ return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
10929
+ }
10930
+ hiddenMatch = true;
10931
+ break;
10932
+ }
10933
+ break;
10934
+ }
10935
+ }
10936
+ if (type === "assert_text_absent" && !matched) {
10937
+ return { ...base, ok: true, count, timeout_ms: timeout };
10938
+ }
10939
+ await page.waitForTimeout(100);
10940
+ }
10941
+ if (type === "assert_text_visible") {
10942
+ if (hiddenMatch) {
10943
+ return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
10944
+ }
10945
+ return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
10946
+ }
10947
+ return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
10948
+ }
10884
10949
  return { ...base, reason: "unsupported_action" };
10885
10950
  } catch (error) {
10886
10951
  return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  resolveRiddleProofProfileTimeoutSec,
59
59
  slugifyRiddleProofProfileName,
60
60
  summarizeRiddleProofProfileResult
61
- } from "./chunk-NJDY7HYE.js";
61
+ } from "./chunk-V2KNQTQD.js";
62
62
  import {
63
63
  DEFAULT_RIDDLE_API_BASE_URL,
64
64
  DEFAULT_RIDDLE_API_KEY_FILE,
package/dist/profile.cjs CHANGED
@@ -75,6 +75,9 @@ var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
75
75
  "click",
76
76
  "fill",
77
77
  "set_input_value",
78
+ "assert_text_visible",
79
+ "assert_text_absent",
80
+ "assert_selector_count",
78
81
  "local_storage",
79
82
  "session_storage",
80
83
  "clear_storage",
@@ -248,12 +251,19 @@ function normalizeSetupAction(input, index) {
248
251
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
249
252
  const type = normalizeSetupActionType(stringValue(input.type), index);
250
253
  const selector = stringValue(input.selector);
251
- if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text") && !selector) {
254
+ if ((type === "click" || type === "fill" || type === "set_input_value" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
252
255
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
253
256
  }
254
257
  if (type === "wait_for_text" && !stringValue(input.text) && !stringValue(input.pattern)) {
255
258
  throw new Error(`target.setup_actions[${index}] wait_for_text requires text or pattern.`);
256
259
  }
260
+ if ((type === "assert_text_visible" || type === "assert_text_absent") && !stringValue(input.text) && !stringValue(input.pattern)) {
261
+ throw new Error(`target.setup_actions[${index}] ${type} requires text or pattern.`);
262
+ }
263
+ const expectedCount = numberValue(input.expected_count ?? input.expectedCount ?? input.count);
264
+ if (type === "assert_selector_count" && (expectedCount === void 0 || !Number.isInteger(expectedCount) || expectedCount < 0)) {
265
+ throw new Error(`target.setup_actions[${index}] ${type} requires non-negative integer expected_count.`);
266
+ }
257
267
  const value = stringFromOwn(input, "value", "input_value", "inputValue");
258
268
  const hasJsonValue = hasOwn(input, "value_json") || hasOwn(input, "valueJson") || hasOwn(input, "json");
259
269
  if ((type === "fill" || type === "set_input_value") && value === void 0 && !hasJsonValue) {
@@ -276,6 +286,7 @@ function normalizeSetupAction(input, index) {
276
286
  pattern: stringValue(input.pattern),
277
287
  flags: stringValue(input.flags),
278
288
  index: numberValue(input.index),
289
+ expected_count: expectedCount,
279
290
  ms: numberValue(input.ms) ?? numberValue(input.wait_ms) ?? numberValue(input.waitMs),
280
291
  timeout_ms: numberValue(input.timeout_ms) ?? numberValue(input.timeoutMs),
281
292
  after_ms: numberValue(input.after_ms) ?? numberValue(input.afterMs),
@@ -2193,6 +2204,19 @@ async function executeSetupAction(action, ordinal) {
2193
2204
  await locator.nth(targetIndex).fill(value, { timeout });
2194
2205
  return { ...base, ok: true, count, target_index: targetIndex, value_length: value.length };
2195
2206
  }
2207
+ if (type === "assert_selector_count") {
2208
+ const locator = page.locator(action.selector);
2209
+ const expectedCount = setupNumber(action.expected_count, -1);
2210
+ if (!Number.isInteger(expectedCount) || expectedCount < 0) return { ...base, reason: "invalid_expected_count", expected_count: action.expected_count };
2211
+ const startedAt = Date.now();
2212
+ let count = 0;
2213
+ while (Date.now() - startedAt <= timeout) {
2214
+ count = await locator.count().catch(() => 0);
2215
+ if (count === expectedCount) return { ...base, ok: true, count, expected_count: expectedCount, timeout_ms: timeout };
2216
+ await page.waitForTimeout(100);
2217
+ }
2218
+ return { ...base, reason: "selector_count_mismatch", count, expected_count: expectedCount, timeout_ms: timeout };
2219
+ }
2196
2220
  if (type === "wait_for_text") {
2197
2221
  const locator = page.locator(action.selector);
2198
2222
  const startedAt = Date.now();
@@ -2210,6 +2234,47 @@ async function executeSetupAction(action, ordinal) {
2210
2234
  }
2211
2235
  return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2212
2236
  }
2237
+ if (type === "assert_text_visible" || type === "assert_text_absent") {
2238
+ const locator = page.locator(action.selector);
2239
+ const startedAt = Date.now();
2240
+ let lastText = "";
2241
+ let matchedText = "";
2242
+ let hiddenMatch = false;
2243
+ while (Date.now() - startedAt <= timeout) {
2244
+ const count = await locator.count().catch(() => 0);
2245
+ let matched = false;
2246
+ matchedText = "";
2247
+ hiddenMatch = false;
2248
+ for (let index = 0; index < count; index += 1) {
2249
+ const text = await setupLocatorText(locator, index);
2250
+ lastText = text || lastText;
2251
+ if (setupTextMatches(text, action)) {
2252
+ matched = true;
2253
+ matchedText = text;
2254
+ if (type === "assert_text_visible") {
2255
+ const visible = await setupLocatorVisible(locator, index);
2256
+ if (visible) {
2257
+ return { ...base, ok: true, count, text: compactSetupResultText(text), target_index: index, timeout_ms: timeout };
2258
+ }
2259
+ hiddenMatch = true;
2260
+ break;
2261
+ }
2262
+ break;
2263
+ }
2264
+ }
2265
+ if (type === "assert_text_absent" && !matched) {
2266
+ return { ...base, ok: true, count, timeout_ms: timeout };
2267
+ }
2268
+ await page.waitForTimeout(100);
2269
+ }
2270
+ if (type === "assert_text_visible") {
2271
+ if (hiddenMatch) {
2272
+ return { ...base, reason: "matching_element_not_visible", text: compactSetupResultText(matchedText), timeout_ms: timeout };
2273
+ }
2274
+ return { ...base, reason: "text_not_found", text: compactSetupResultText(lastText), timeout_ms: timeout };
2275
+ }
2276
+ return { ...base, reason: "text_still_present", text: compactSetupResultText(matchedText || lastText), timeout_ms: timeout };
2277
+ }
2213
2278
  return { ...base, reason: "unsupported_action" };
2214
2279
  } catch (error) {
2215
2280
  return { ...base, error: String(error && error.message ? error.message : error).slice(0, 1000) };
@@ -5,7 +5,7 @@ declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evide
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
7
  declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
- declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
8
+ declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
11
11
  type RiddleProofProfileSetupActionType = typeof RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES[number];
@@ -27,6 +27,7 @@ interface RiddleProofProfileSetupAction {
27
27
  pattern?: string;
28
28
  flags?: string;
29
29
  index?: number;
30
+ expected_count?: number;
30
31
  ms?: number;
31
32
  timeout_ms?: number;
32
33
  after_ms?: number;
package/dist/profile.d.ts CHANGED
@@ -5,7 +5,7 @@ declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evide
5
5
  declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
6
6
  declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
7
7
  declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_order", "frame_text_visible", "frame_no_horizontal_overflow", "text_visible", "text_absent", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
8
- declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
8
+ declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "local_storage", "session_storage", "clear_storage", "wait", "wait_for_selector", "wait_for_text"];
9
9
  type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
10
10
  type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
11
11
  type RiddleProofProfileSetupActionType = typeof RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES[number];
@@ -27,6 +27,7 @@ interface RiddleProofProfileSetupAction {
27
27
  pattern?: string;
28
28
  flags?: string;
29
29
  index?: number;
30
+ expected_count?: number;
30
31
  ms?: number;
31
32
  timeout_ms?: number;
32
33
  after_ms?: number;
package/dist/profile.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveRiddleProofProfileTimeoutSec,
20
20
  slugifyRiddleProofProfileName,
21
21
  summarizeRiddleProofProfileResult
22
- } from "./chunk-NJDY7HYE.js";
22
+ } from "./chunk-V2KNQTQD.js";
23
23
  export {
24
24
  RIDDLE_PROOF_PROFILE_CHECK_TYPES,
25
25
  RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
@@ -292,7 +292,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
292
292
  blocking?: boolean;
293
293
  details?: Record<string, unknown>;
294
294
  ok: boolean;
295
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
295
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
296
296
  state_path: string;
297
297
  stage: any;
298
298
  summary: string;
@@ -382,7 +382,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
382
382
  continueWithStage?: WorkflowStage | null;
383
383
  blocking?: boolean;
384
384
  details?: Record<string, unknown>;
385
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
385
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
386
386
  state_path: string;
387
387
  stage: any;
388
388
  checkpoint: string;
@@ -659,7 +659,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
659
659
  error?: undefined;
660
660
  } | {
661
661
  ok: boolean;
662
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
662
+ action: "author" | "recon" | "ship" | "implement" | "verify" | "setup" | "run";
663
663
  state_path: string;
664
664
  stage: any;
665
665
  summary: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.7.47",
3
+ "version": "0.7.48",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",