@reticlehq/server 3.2.0 → 3.3.0
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/SKILL.md +16 -13
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/cli-doctor.js +25 -5
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +7 -1
- package/dist/command/cli/cli-parse.js +33 -5
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +129 -17
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +1 -1
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/launch/cli-launch.d.ts +1 -29
- package/dist/command/cli/launch/cli-launch.js +6 -40
- package/dist/command/cli/ports/resolve/cli-port.d.ts +13 -0
- package/dist/command/cli/ports/resolve/cli-port.js +45 -1
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli.js +5 -0
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +30 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +45 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +1 -0
- package/dist/features/harness/jev-driver.js +6 -9
- package/dist/features/harness/platform-config.d.ts +10 -0
- package/dist/features/harness/platform-config.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +40 -95
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +38 -9
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +31 -9
- package/dist/language/flows/flow-replay-run.js +146 -33
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +12 -11
- package/dist/language/flows/flow-replay.js +79 -109
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flows.d.ts +8 -4
- package/dist/language/flows/flows.js +11 -2
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +12 -8
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/verify-change-tools.js +14 -0
- package/dist/memory/cloud/cloud-sync.d.ts +0 -2
- package/dist/memory/cloud/cloud-sync.js +1 -1
- package/dist/memory/cloud/harness-config.d.ts +2 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +13 -0
- package/dist/memory/cloud/sync-daemon.js +21 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-store.js +6 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +252 -140
- package/dist/memory/intent/intent-shard.d.ts +10 -11
- package/dist/memory/intent/intent-shard.js +1 -1
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-recorder.d.ts +21 -0
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +14 -10
- package/dist/memory/journal/on-disk/retention.js +13 -12
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/session-end.d.ts +4 -1
- package/dist/memory/journal/session-end.js +64 -6
- package/dist/memory/journal/session-journal.d.ts +8 -1
- package/dist/memory/journal/session-journal.js +10 -0
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/reticle-dir.d.ts +1 -1
- package/dist/memory/project/fs/fs-port.js +44 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.js +5 -2
- package/dist/portal/input/real-input.js +2 -1
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.js +1 -0
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +9 -0
- package/dist/portal/session/session-manager.js +23 -2
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +5 -0
- package/dist/portal/session/session.js +19 -7
- package/dist/surface/mcp/mcp-proxy.js +3 -3
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.js +22 -7
- package/dist/surface/tools/act-tools.js +25 -24
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +30 -4
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/dynamic-tools.js +6 -0
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +17 -4
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/observe-tools.js +14 -6
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +5 -0
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools.js +64 -29
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/cli/cloud.mdx +9 -7
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/desktop.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +128 -4
- package/docs/tools/query.mdx +10 -10
- package/docs/usage.md +1 -1
- package/docs/what-is-recorded.md +62 -0
- package/package.json +6 -6
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
package/docs/packages.mdx
CHANGED
package/docs/predicates.mdx
CHANGED
|
@@ -48,7 +48,17 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `scope`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `present`.
|
|
51
|
+
`query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `inViewport`, `present`.
|
|
52
|
+
|
|
53
|
+
`value` is the one worth knowing about. `{ "role": "textbox", "name": "Amount" }` collapses to "a textbox called Amount exists", which passes against an empty field; adding `value` is what makes it an assertion about what the user will actually send. `by` selects the locator strategy explicitly, `component` and `source` match React component identity and source location, and `self` restricts a scoped match to the scope element itself rather than its descendants.
|
|
54
|
+
|
|
55
|
+
`attrs` is a list of attribute **names**, such as `["href", "data-status"]`. It **projects** those attributes onto each match so you can read them back, and it does not filter. `["data-status=complete"]` is refused: it would otherwise look up an attribute literally named `data-status=complete`, find none, and leave the predicate resting on the locator alone, which is a green for any value the element happens to hold.
|
|
56
|
+
|
|
57
|
+
To READ the values, call `reticle_query` with `attrs` and look at the `attrs` map on each match. To ASSERT one: a form field's value goes in the query as `value`, while a state expressed as a data attribute is better asserted as the consequence it stands for, such as a `signal`, a `net` call, or the text the user actually sees.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "kind": "element", "query": { "role": "textbox", "name": "Amount", "value": "274.58" } }
|
|
61
|
+
```
|
|
52
62
|
|
|
53
63
|
Add `"absent": true` to assert something is **gone**. The predicate for a removal, a dismissed toast, or a regression check.
|
|
54
64
|
|
|
@@ -62,6 +72,26 @@ Add `"absent": true` to assert something is **gone**. The predicate for a remova
|
|
|
62
72
|
|
|
63
73
|
Use `scope` to restrict the search to a CSS selector or element ref. If Reticle reports that the text is split across the scope's children, retry its generated predicate with `"self": true`; that checks the scope root's combined subtree text while still enforcing `contains`.
|
|
64
74
|
|
|
75
|
+
`satisfies` asserts a PROPERTY of the rendered text instead of its exact bytes: the same shape and the same properties [`state` accepts](#satisfies-asserting-a-property-instead-of-exact-bytes), applied where the user actually reads the value:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"kind": "text",
|
|
80
|
+
"scope": "[data-testid=\"release-note\"]",
|
|
81
|
+
"self": true,
|
|
82
|
+
"satisfies": { "property": "nonEmpty" }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That is the assertion a generated feature can pass twice. `contains` demands bytes a model's output does not repeat, so it either fails on correct output or gets weakened to a word so common it proves nothing; "the box rendered and there is something in it" is the failure the feature actually has.
|
|
87
|
+
|
|
88
|
+
Two rules, both refusals rather than guesses:
|
|
89
|
+
|
|
90
|
+
- **One of `contains` or `satisfies` is required.** With neither, the predicate matches any element with any text and cannot fail.
|
|
91
|
+
- **`satisfies` on its own needs a `scope`** (the element whose text is read, with `self: true` for the root's own subtree). Without one the locator is every element on the page, and the property would run against whichever matched first.
|
|
92
|
+
|
|
93
|
+
Both may be given, and then both must hold. `satisfies` narrows, it never excuses.
|
|
94
|
+
|
|
65
95
|
### net
|
|
66
96
|
|
|
67
97
|
```json
|
|
@@ -174,6 +204,72 @@ Omit `equals` entirely to assert presence. A real `$length` check against this f
|
|
|
174
204
|
|
|
175
205
|
Note `grade: state` in the `because` line, and that the evidence collapses each row to a size marker rather than returning forty objects.
|
|
176
206
|
|
|
207
|
+
#### satisfies: asserting a property instead of exact bytes
|
|
208
|
+
|
|
209
|
+
`equals` cannot express output that is right differently every run. A generated summary, a classification, a computed total: each is correct without being predictable, and an assertion that demands exact bytes either fails on correct output or gets weakened until it proves nothing. `satisfies` is the other half of `state`, and it stays decidable here with no model and no network.
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{ "kind": "state", "store": "app", "path": "summary.text", "satisfies": { "property": "nonEmpty" } }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| `property` | Extra fields | Holds when |
|
|
216
|
+
| --- | --- | --- |
|
|
217
|
+
| `nonEmpty` | none | it produced something at all, the honest floor for any output |
|
|
218
|
+
| `oneOf` | `values` | it landed inside the allowed set, exact membership, no coercion |
|
|
219
|
+
| `withinTolerance` | `of`, `tolerance` | a number near enough to the expected one, inclusive on the bound |
|
|
220
|
+
| `matchesPattern` | `pattern` | its string form matches that regular expression |
|
|
221
|
+
| `type` | `is` | it is that kind of thing; `array` and `object` are distinguished, which `typeof` cannot do |
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"kind": "allOf",
|
|
226
|
+
"predicates": [
|
|
227
|
+
{
|
|
228
|
+
"kind": "state",
|
|
229
|
+
"path": "invoice.total",
|
|
230
|
+
"satisfies": { "property": "withinTolerance", "of": 274.58, "tolerance": 0.01 }
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"kind": "state",
|
|
234
|
+
"path": "invoice.status",
|
|
235
|
+
"satisfies": { "property": "oneOf", "values": ["draft", "sent"] }
|
|
236
|
+
},
|
|
237
|
+
{ "kind": "state", "path": "invoice.lines", "satisfies": { "property": "type", "is": "array" } }
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`equals` and `satisfies` may both be given, and then both must hold. A failing check always says what it saw, because an assertion that reports only `false` sends you back for another call to find out why, and that round trip is most of what a verdict costs.
|
|
243
|
+
|
|
244
|
+
#### The relative properties: comparing with before
|
|
245
|
+
|
|
246
|
+
The properties above read the present tense. Four read the past one. They compare this value with the reading taken **before the action**, which is the window a verdict is already scoped to:
|
|
247
|
+
|
|
248
|
+
| property | holds when |
|
|
249
|
+
| ----------- | --------------------------- |
|
|
250
|
+
| `unchanged` | the value is the one it was |
|
|
251
|
+
| `changed` | it is not |
|
|
252
|
+
| `increased` | the number went up |
|
|
253
|
+
| `decreased` | the number went down |
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"kind": "state",
|
|
258
|
+
"path": "cart.total",
|
|
259
|
+
"satisfies": { "property": "decreased", "by": { "op": "equals", "value": 12 } }
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`increased` and `decreased` take an optional `by`, in the protocol's own comparison vocabulary: `op` is `equals`, `at-least` or `at-most`, with an optional `tolerance` on all three. Without `by`, any movement in the right direction holds.
|
|
264
|
+
|
|
265
|
+
**Give a money assertion a tolerance.** `100 - 88.13` is `11.870000000000005`, so `{ "op": "equals", "value": 11.87 }` misses by a rounding error and reads as the app charging the wrong amount. `"tolerance": 0.01` is the fix; the failure message says so when the miss is that small.
|
|
266
|
+
|
|
267
|
+
A number is read out of displayed text, so `"$1,234.50"` compares as `1234.5`. A reading with no digits in it is **not** zero. `"sold out"` reports that nothing was subtracted rather than pretending the total fell to nothing.
|
|
268
|
+
|
|
269
|
+
These only work where a before-reading was taken, which today is `reticle_act_and_wait`. Asked anywhere else they report `unknown` with "no before-reading was taken", never a failure, because nothing was compared and a `false` would blame the app for a reading nobody took. For the same reason a relative property is never `already_true`: `unchanged` is trivially true against a baseline taken a microsecond earlier, so the pre-action check skips it and lets the action decide.
|
|
270
|
+
|
|
271
|
+
A saved flow cannot keep one yet: `FlowExpect` has no slot for `satisfies`, and rather than record a weaker assertion than the one you made, the converter records none.
|
|
272
|
+
|
|
177
273
|
A miss quotes the real value back at you:
|
|
178
274
|
|
|
179
275
|
```json
|
|
@@ -207,6 +303,33 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
|
|
|
207
303
|
|
|
208
304
|
Network and DOM idle. This is the deterministic replacement for a fixed sleep, not a proof of anything: combine it with a real consequence rather than asserting it alone. Omitting `until` on [`reticle_act_and_wait`](/tools/act-and-wait) waits for exactly this.
|
|
209
305
|
|
|
306
|
+
## Asserting a failure on purpose
|
|
307
|
+
|
|
308
|
+
A fault-injection test (mock a 500, check the error UI) is asserting that a request **fails**. Say so in the predicate, or the verdict will disagree with you:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"kind": "allOf",
|
|
313
|
+
"predicates": [
|
|
314
|
+
{ "kind": "text", "contains": "Something went wrong" },
|
|
315
|
+
{ "kind": "net", "method": "POST", "urlContains": "/api/save", "status": 503 }
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Asserting only the error text is not enough, and this is the most common way to get a confusing `verified: "no"`. Reticle watches for "the UI moved forward while a request failed", a swallowed rejection, and one of the most expensive bugs it catches. A fault-injection test looks **exactly** like that bug from the outside: a request failed, the screen changed, nothing said that was the plan.
|
|
321
|
+
|
|
322
|
+
Naming the failing call is what tells the two apart. A `net` clause with `status` of 400 or more, or `"ok": false`, which is the honest field for IPC, where there is no status code, marks that call as expected to fail, and a failure there stops being a disagreement.
|
|
323
|
+
|
|
324
|
+
<Warning>
|
|
325
|
+
Declare it in the top level or inside `allOf`. A declaration inside `anyOf` is deliberately
|
|
326
|
+
ignored: only one branch of an `anyOf` has to hold, so honouring it would suppress a real
|
|
327
|
+
contradiction on the strength of a branch that never ran. `not` is ignored for the same reason in
|
|
328
|
+
reverse: `not { net status: 500 }` asserts the call did *not* fail.
|
|
329
|
+
</Warning>
|
|
330
|
+
|
|
331
|
+
One case is inferred for you: an `element` or `text` clause naming an access denial ("not authorised", "sign in to continue") marks the auth statuses as expected, because a denial screen is a correct outcome rather than a fault. Everything else has to be named.
|
|
332
|
+
|
|
210
333
|
## Combinators
|
|
211
334
|
|
|
212
335
|
```json
|
|
@@ -226,7 +349,8 @@ Network and DOM idle. This is the deterministic replacement for a fixed sleep, n
|
|
|
226
349
|
```
|
|
227
350
|
|
|
228
351
|
<Warning>
|
|
229
|
-
A combinator needs its own `kind`, and the children go in `predicates
|
|
352
|
+
A combinator needs its own `kind`, and the children go in `predicates`, or in `of`, which is
|
|
353
|
+
accepted as an alias for it. `not` takes a single child in `predicate`, or in `of`. A bare
|
|
230
354
|
`{ "allOf": [ … ] }` does not parse, and neither does `{ "kind": "allOf", "allOf": [ … ] }`.
|
|
231
355
|
|
|
232
356
|
Reticle refuses the call rather than running half of it. The real refusal names the field, the fix and the consequence: _"predicates: Required; unknown field allOf. Nothing ran, the predicate was not evaluated, so no verdict was produced. allOf accepts: predicates."_ Better to be told than to get a green from an assertion that never executed.
|
|
@@ -265,9 +389,9 @@ On a failure each child reports separately too, so you can see which one broke:
|
|
|
265
389
|
| Field | Where | What it does |
|
|
266
390
|
| --- | --- | --- |
|
|
267
391
|
| `timeout_ms` | `assert`, `wait_for`, `act_and_wait` | Wait up to N ms for it to become true |
|
|
268
|
-
| `since` | `net` and `
|
|
392
|
+
| `since` | `net`, `route`, `console`, `animation` and `signal` leaves, and the call itself | Only consider events after this cursor |
|
|
269
393
|
|
|
270
|
-
The `since` cursor comes from a prior `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago.
|
|
394
|
+
The `since` cursor comes from a prior `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago. That applies to every one of the five kinds above, not only to network calls: an unscoped `signal` matches a fire from an earlier action just as readily.
|
|
271
395
|
|
|
272
396
|
<Card title="Where predicates get used" icon="circle-check" href="/tools/act-and-wait">
|
|
273
397
|
Naming the consequence before the action is the whole point.
|
package/docs/tools/query.mdx
CHANGED
|
@@ -52,16 +52,16 @@ Seven buttons, each with a `ref` to act on and a source file to open, for 236 to
|
|
|
52
52
|
|
|
53
53
|
## Arguments
|
|
54
54
|
|
|
55
|
-
| Argument
|
|
56
|
-
|
|
|
57
|
-
| `by`
|
|
58
|
-
| `value`
|
|
59
|
-
| `name`
|
|
60
|
-
| `scope`
|
|
61
|
-
| `limit`
|
|
62
|
-
| `count_only` | Return just `{ count }`, around 30x smaller
|
|
63
|
-
| `attrs`
|
|
64
|
-
| `self`
|
|
55
|
+
| Argument | What it does |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `by` | `role` · `text` · `label` · `placeholder` · `testid` · `alt` · `component` |
|
|
58
|
+
| `value` | The query value for that strategy |
|
|
59
|
+
| `name` | Accessible-name filter. Narrows a broad `role` query |
|
|
60
|
+
| `scope` | CSS selector or ref, to search inside a subtree |
|
|
61
|
+
| `limit` | Cap the descriptors returned. Cuts tokens hard on broad queries |
|
|
62
|
+
| `count_only` | Return just `{ count }`, around 30x smaller |
|
|
63
|
+
| `attrs` | Attribute NAMES to project per match, e.g. `['href']` to inventory links. The values come back in each match's `attrs` map. It does not filter, so `name=value` is refused |
|
|
64
|
+
| `self` | Return the `scope` element itself instead of searching inside it |
|
|
65
65
|
|
|
66
66
|
Every argument also has a predicate spelling, so `{ "role": "button" }` works as shorthand for `{ "by": "role", "value": "button" }`.
|
|
67
67
|
|
package/docs/usage.md
CHANGED
|
@@ -200,7 +200,7 @@ Verify a predicate; optionally wait for it.
|
|
|
200
200
|
- **args:** `predicate`, `timeout_ms?` (0 = evaluate once), `since?`, `intent?`, `sessionId?`.
|
|
201
201
|
- Same `since` default as `reticle_assert { action: "wait" }`: scoped to your last act so a stale buffered event can't fake a pass; override with an explicit `since`.
|
|
202
202
|
- **returns:** `{ verified, because, pass, evidence, contradictions?, coverage?, failureReason?, session, warning? }`. On failure includes a **near-miss** (e.g. "found the dialog but not visible", or "no button named 'Submit'; saw: Cancel"). The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every assert; when throttled a `warning` is attached so you never assert against a tab that is silently no-oping.
|
|
203
|
-
- **`intent` declares what the change was FOR, inline.** Pass a sentence in your own words and it lands in `.reticle/intent
|
|
203
|
+
- **`intent` declares what the change was FOR, inline.** Pass a sentence in your own words and it lands in `.reticle/intent/` (one directory per subject, a flow's name being its subject), the same git-checked ledger `reticle_intent` writes, before the verdict is drawn; a green verdict then marks it proved and names itself as the proof. Pass the **id** of an intent you already declared instead, to point several verdicts at one statement rather than restating it. `reticle_act_and_wait` takes the same argument. Omit it and nothing is written.
|
|
204
204
|
- **Read `verified`, not `pass`.** `pass` says the predicate held; `verified` says whether that means anything. It is `"no"` when a channel contradicts the assertion (a failed write under a green screen, a batch whose body reports per-item failures, a request still in flight), and `"unknown"` when the outcome could not be known yet: a `202 Accepted` that has not reconciled, or a write whose response body was never recorded. `because` names the deciding evidence in one sentence.
|
|
205
205
|
|
|
206
206
|
### `reticle_reconcile`
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: What is recorded
|
|
3
|
+
description: What Reticle keeps on your machine, what is redacted, and what it takes for any of it to leave.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Reticle drives your app and reads what it did. That means it holds real content from real pages, so this page says exactly what it keeps, where it keeps it, and what it takes to make any of it leave.
|
|
7
|
+
|
|
8
|
+
If you read one line: **a verdict is entirely local and needs no account.** Nothing is sent anywhere until you run `reticle login` and `reticle link`. Without them there is nowhere for anything to go.
|
|
9
|
+
|
|
10
|
+
## On your machine
|
|
11
|
+
|
|
12
|
+
Everything lands in `.reticle/` in your project, and it is yours. Three kinds of thing:
|
|
13
|
+
|
|
14
|
+
| | what it is | what is in it |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| **runs** | a verdict and its evidence | what was claimed, whether it held, the request or state change that decided it, and the `file:line` of the element driven |
|
|
17
|
+
| **flows** | a journey you drove, saved so it can be replayed | the steps, the anchors they resolve by, and the values that were typed |
|
|
18
|
+
| **memory** | what this project has learned across sessions | intents you declared, envelopes of normal behaviour, notes about anchors that drift |
|
|
19
|
+
|
|
20
|
+
`.reticle/` splits into a part meant for git and a part meant to stay local. Flows are the part worth committing: a saved flow is a regression test. Evidence is the part that is not.
|
|
21
|
+
|
|
22
|
+
## What is redacted, and what is not
|
|
23
|
+
|
|
24
|
+
**Credential-shaped fields are redacted when a flow is saved.** A value typed into a field whose name looks like a secret is replaced with `<redacted: supply at replay>`, and replay reads the real value from `RETICLE_SECRET_<FIELD>` at run time. It keys off the field's name across every anchor kind (a testid, an accessible name, a signal), so an app without test ids is covered too.
|
|
25
|
+
|
|
26
|
+
**It is name-based, and that is the limit worth knowing.** It redacts a password. It does not redact a customer's name typed into a search box, because nothing about that field says "secret". A flow records the journey you actually drove.
|
|
27
|
+
|
|
28
|
+
So: **drive staging.** Not because the risk is large, but because it is real and avoiding it is free.
|
|
29
|
+
|
|
30
|
+
Network bodies are **not** captured by default. Page content that reaches a verdict is the element text and attributes the assertion needed, not a copy of the DOM.
|
|
31
|
+
|
|
32
|
+
## What leaves, only if you ask
|
|
33
|
+
|
|
34
|
+
`reticle login` signs this machine in. `reticle link` binds one repo to one cloud project. Until both have happened, `reticle push` has nothing to talk to and the sync path is a no-op. The code calls this the no-phone-home default.
|
|
35
|
+
|
|
36
|
+
Once linked, you choose what syncs:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @reticlehq/server config --runs on|off --flows on|off --memory on|off
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Telemetry, which is separate from all of the above
|
|
43
|
+
|
|
44
|
+
The CLI sends anonymous usage events: event names and a random installation id. No code, no page content, no URLs from your app. It is how we know which parts of the product are reached at all.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
RETICLE_TELEMETRY=0 # this shell
|
|
48
|
+
DO_NOT_TRACK=1 # the convention, honoured
|
|
49
|
+
npx @reticlehq/server telemetry disable # this machine, permanently
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Feedback is the only free text that ever leaves, it is never collected passively, and `RETICLE_FEEDBACK=0` disables it.
|
|
53
|
+
|
|
54
|
+
## What `init` writes to your project
|
|
55
|
+
|
|
56
|
+
Run it with `--dry-run` and it prints the whole plan without writing anything:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx @reticlehq/server init --dry-run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`--app <dir>` picks which app in a monorepo. `--no-mcp` skips registering the MCP server with your agents and skips the agent rule files with it. `--files-only` writes the files and stops, without booting your dev server.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Reticle bridge + MCP server. Hosts the browser WS endpoint and exposes MCP tools to the coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"ws": "^8.21.3",
|
|
24
24
|
"zod": "^3.24.1",
|
|
25
25
|
"zod-to-json-schema": "^3.24.0",
|
|
26
|
-
"@reticlehq/
|
|
27
|
-
"@reticlehq/
|
|
28
|
-
"@reticlehq/init": "3.
|
|
29
|
-
"
|
|
30
|
-
"
|
|
26
|
+
"@reticlehq/engine": "3.3.0",
|
|
27
|
+
"@reticlehq/browser": "3.3.0",
|
|
28
|
+
"@reticlehq/init": "3.3.0",
|
|
29
|
+
"open-verification": "1.0.0",
|
|
30
|
+
"@reticlehq/core": "3.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/pngjs": "^6.0.5",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The assertion the agent MADE, in the shape a saved flow can keep.
|
|
3
|
-
*
|
|
4
|
-
* `reticle_act_and_wait { until }` is how agents assert — nearly every asserting call in a day of telemetry
|
|
5
|
-
* carried an `until`. `compileActStep` recorded only the ACTION, so a flow saved after an asserted
|
|
6
|
-
* drive came back graded `assertion-free`: "performs actions but asserts no observable consequence —
|
|
7
|
-
* it will pass even if the feature is broken." The agent had already said what success meant;
|
|
8
|
-
* Reticle discarded it and then warned the agent that the flow asserts nothing.
|
|
9
|
-
*
|
|
10
|
-
* That is the regression-suite story failing at its last step. "Record once, verify forever" is only
|
|
11
|
-
* worth anything if the recorded flow can go RED, and locally 32 of 39 saved flows cannot.
|
|
12
|
-
*
|
|
13
|
-
* The inverse of `successToPredicate`. Only the kinds FlowExpect can express are carried: `settled`
|
|
14
|
-
* is a wait rather than a claim, and `animation`/`anyOf`/`not` have no representation at all (`route` gained one — see its case below).
|
|
15
|
-
* Inventing one would write an assertion into the file that the agent never made, which is worse
|
|
16
|
-
* than recording none — a flow that asserts something nobody chose is a false green with extra steps.
|
|
17
|
-
*/
|
|
18
|
-
import type { FlowExpect } from '@reticlehq/core';
|
|
19
|
-
import type { Predicate } from '@reticlehq/engine/question/predicate/predicate.js';
|
|
20
|
-
export declare function predicateToExpect(predicate: Predicate): FlowExpect | undefined;
|
|
21
|
-
/**
|
|
22
|
-
* The subset a REPLAY actually enforces.
|
|
23
|
-
*
|
|
24
|
-
* The rule has never changed: never write an assertion into a flow file that nothing evaluates. A
|
|
25
|
-
* flow reporting `grade: "asserted"` while its assertion is read by no one is a false green, in the
|
|
26
|
-
* feature whose entire purpose is preventing them.
|
|
27
|
-
*
|
|
28
|
-
* What changed is the SET. This kept only `element.testid` and `state`, because for a long time
|
|
29
|
-
* those were the only kinds replay checked — and the note here said lifting it "requires replay to
|
|
30
|
-
* evaluate them per step". Replay now does exactly that: `assertStepExpect` compiles every
|
|
31
|
-
* remaining kind through `successToPredicate` and waits on it. The condition was met and the filter
|
|
32
|
-
* was not updated, so the two drifted.
|
|
33
|
-
*
|
|
34
|
-
* That drift had a cost, and it fell on the agent. `reticle_act_and_wait { until }` IS the agent
|
|
35
|
-
* saying what success means, and `net` is overwhelmingly what it says. Every one of those was
|
|
36
|
-
* discarded at capture, so an agent-recorded flow reached disk assertion-free BY CONSTRUCTION and
|
|
37
|
-
* `reticle_verify` then correctly called it `unverifiable` — the record → save → verify path
|
|
38
|
-
* completing all the way to a run that could never be a pass. Found by driving it.
|
|
39
|
-
*
|
|
40
|
-
* The list below is exactly what `successToPredicate` reads, including an element located by
|
|
41
|
-
* role or name. A testid is also asserted directly against the DOM by the step runner. Anything
|
|
42
|
-
* it cannot compile is still dropped.
|
|
43
|
-
*/
|
|
44
|
-
export declare function enforcedOnReplay(expect: FlowExpect | undefined): FlowExpect | undefined;
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { PredicateKind } from '@reticlehq/core';
|
|
2
|
-
/** Merge two partial expectations; later keys win only where the earlier one said nothing. */
|
|
3
|
-
function merge(into, from) {
|
|
4
|
-
return { ...from, ...into };
|
|
5
|
-
}
|
|
6
|
-
export function predicateToExpect(predicate) {
|
|
7
|
-
switch (predicate.kind) {
|
|
8
|
-
case PredicateKind.SIGNAL: {
|
|
9
|
-
if (predicate.name === undefined)
|
|
10
|
-
return undefined;
|
|
11
|
-
const signal = { signal: predicate.name };
|
|
12
|
-
if (predicate.dataMatches !== undefined)
|
|
13
|
-
signal.signalData = predicate.dataMatches;
|
|
14
|
-
if (predicate.count !== undefined)
|
|
15
|
-
signal.signalCount = predicate.count;
|
|
16
|
-
return signal;
|
|
17
|
-
}
|
|
18
|
-
case PredicateKind.ROUTE: {
|
|
19
|
-
// Carried, not invented: the agent said "until the route is X" in so many words. The kinds
|
|
20
|
-
// below this comment's twin in the header — animation, anyOf, not — stay refused because
|
|
21
|
-
// there the file would gain a claim nobody made.
|
|
22
|
-
const route = {};
|
|
23
|
-
if (predicate.pathname !== undefined)
|
|
24
|
-
route.pathname = predicate.pathname;
|
|
25
|
-
if (predicate.contains !== undefined)
|
|
26
|
-
route.contains = predicate.contains;
|
|
27
|
-
return 0 === Object.keys(route).length ? undefined : { route };
|
|
28
|
-
}
|
|
29
|
-
case PredicateKind.NET: {
|
|
30
|
-
const net = {};
|
|
31
|
-
if (predicate.method !== undefined)
|
|
32
|
-
net.method = predicate.method;
|
|
33
|
-
if (predicate.urlContains !== undefined)
|
|
34
|
-
net.urlContains = predicate.urlContains;
|
|
35
|
-
if (predicate.status !== undefined)
|
|
36
|
-
net.status = predicate.status;
|
|
37
|
-
if (predicate.count !== undefined)
|
|
38
|
-
net.count = predicate.count;
|
|
39
|
-
return 0 === Object.keys(net).length ? undefined : { net };
|
|
40
|
-
}
|
|
41
|
-
case PredicateKind.CONSOLE: {
|
|
42
|
-
// `contains` has no representation in FlowExpect.console (core keeps `level` and `absent`
|
|
43
|
-
// and nothing else). Copying the rest through anyway would record an assertion the agent
|
|
44
|
-
// never made: {console, level:'warn', contains:'no-op', absent:true} would save "no warn
|
|
45
|
-
// entries at all", a false red on any unrelated warning, and in the presence direction it
|
|
46
|
-
// saves a strictly weaker claim than the one chosen. This file's own rule applies: record
|
|
47
|
-
// nothing rather than something different, so the flow stays assertion-free there instead.
|
|
48
|
-
if (predicate.contains !== undefined)
|
|
49
|
-
return undefined;
|
|
50
|
-
const console_ = {};
|
|
51
|
-
if (predicate.level !== undefined)
|
|
52
|
-
console_.level = predicate.level;
|
|
53
|
-
if (predicate.absent !== undefined)
|
|
54
|
-
console_.absent = predicate.absent;
|
|
55
|
-
return 0 === Object.keys(console_).length ? undefined : { console: console_ };
|
|
56
|
-
}
|
|
57
|
-
case PredicateKind.ELEMENT: {
|
|
58
|
-
const element = {};
|
|
59
|
-
if (predicate.query.testid !== undefined)
|
|
60
|
-
element.testid = predicate.query.testid;
|
|
61
|
-
if (predicate.query.role !== undefined)
|
|
62
|
-
element.role = predicate.query.role;
|
|
63
|
-
if (predicate.query.name !== undefined)
|
|
64
|
-
element.name = predicate.query.name;
|
|
65
|
-
return 0 === Object.keys(element).length ? undefined : { element };
|
|
66
|
-
}
|
|
67
|
-
case PredicateKind.STATE: {
|
|
68
|
-
const state = { path: predicate.path };
|
|
69
|
-
if (predicate.store !== undefined)
|
|
70
|
-
state.store = predicate.store;
|
|
71
|
-
if (predicate.equals !== undefined)
|
|
72
|
-
state.equals = predicate.equals;
|
|
73
|
-
return { state };
|
|
74
|
-
}
|
|
75
|
-
case PredicateKind.ALL_OF: {
|
|
76
|
-
// `settled` members drop out on their own by returning undefined.
|
|
77
|
-
let combined;
|
|
78
|
-
for (const part of predicate.predicates) {
|
|
79
|
-
const expect = predicateToExpect(part);
|
|
80
|
-
if (expect === undefined)
|
|
81
|
-
continue;
|
|
82
|
-
combined = combined === undefined ? expect : merge(combined, expect);
|
|
83
|
-
}
|
|
84
|
-
return combined;
|
|
85
|
-
}
|
|
86
|
-
default:
|
|
87
|
-
// settled | route | animation | anyOf | not — nothing FlowExpect can say honestly.
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* The subset a REPLAY actually enforces.
|
|
93
|
-
*
|
|
94
|
-
* The rule has never changed: never write an assertion into a flow file that nothing evaluates. A
|
|
95
|
-
* flow reporting `grade: "asserted"` while its assertion is read by no one is a false green, in the
|
|
96
|
-
* feature whose entire purpose is preventing them.
|
|
97
|
-
*
|
|
98
|
-
* What changed is the SET. This kept only `element.testid` and `state`, because for a long time
|
|
99
|
-
* those were the only kinds replay checked — and the note here said lifting it "requires replay to
|
|
100
|
-
* evaluate them per step". Replay now does exactly that: `assertStepExpect` compiles every
|
|
101
|
-
* remaining kind through `successToPredicate` and waits on it. The condition was met and the filter
|
|
102
|
-
* was not updated, so the two drifted.
|
|
103
|
-
*
|
|
104
|
-
* That drift had a cost, and it fell on the agent. `reticle_act_and_wait { until }` IS the agent
|
|
105
|
-
* saying what success means, and `net` is overwhelmingly what it says. Every one of those was
|
|
106
|
-
* discarded at capture, so an agent-recorded flow reached disk assertion-free BY CONSTRUCTION and
|
|
107
|
-
* `reticle_verify` then correctly called it `unverifiable` — the record → save → verify path
|
|
108
|
-
* completing all the way to a run that could never be a pass. Found by driving it.
|
|
109
|
-
*
|
|
110
|
-
* The list below is exactly what `successToPredicate` reads, including an element located by
|
|
111
|
-
* role or name. A testid is also asserted directly against the DOM by the step runner. Anything
|
|
112
|
-
* it cannot compile is still dropped.
|
|
113
|
-
*/
|
|
114
|
-
export function enforcedOnReplay(expect) {
|
|
115
|
-
if (expect === undefined)
|
|
116
|
-
return undefined;
|
|
117
|
-
const kept = {};
|
|
118
|
-
// The step runner asserts a testid against the live DOM before the predicate engine. Role and
|
|
119
|
-
// name are not that path: successToPredicate compiles them, and dropping them here made a
|
|
120
|
-
// recorded `until` by button name vanish so the saved flow could not go red.
|
|
121
|
-
const element = expect.element;
|
|
122
|
-
if (undefined !== element &&
|
|
123
|
-
(undefined !== element.testid || undefined !== element.role || undefined !== element.name)) {
|
|
124
|
-
kept.element = element;
|
|
125
|
-
}
|
|
126
|
-
// Everything `successToPredicate` can compile. Replay evaluates EVERY kind of expect through it
|
|
127
|
-
// (see assertStepExpect); this list is what that function actually reads.
|
|
128
|
-
if (expect.state !== undefined)
|
|
129
|
-
kept.state = expect.state;
|
|
130
|
-
if (expect.signal !== undefined)
|
|
131
|
-
kept.signal = expect.signal;
|
|
132
|
-
if (expect.signalData !== undefined)
|
|
133
|
-
kept.signalData = expect.signalData;
|
|
134
|
-
if (expect.signalCount !== undefined)
|
|
135
|
-
kept.signalCount = expect.signalCount;
|
|
136
|
-
if (expect.net !== undefined)
|
|
137
|
-
kept.net = expect.net;
|
|
138
|
-
if (expect.console !== undefined)
|
|
139
|
-
kept.console = expect.console;
|
|
140
|
-
// `route` joined the list the day FlowExpect gained the field. This filter and
|
|
141
|
-
// `successToPredicate` are two allowlists for one question — "can replay check this?" — and the
|
|
142
|
-
// note above records them drifting once already. They drifted again here, in the same direction,
|
|
143
|
-
// within an hour: the field was added, the mapping was added, and a route assertion still reached
|
|
144
|
-
// disk as nothing because THIS list had not heard of it. Found by driving, not by a test.
|
|
145
|
-
if (expect.route !== undefined)
|
|
146
|
-
kept.route = expect.route;
|
|
147
|
-
return 0 === Object.keys(kept).length ? undefined : kept;
|
|
148
|
-
}
|