@pcamarajr/scout 0.12.0 → 0.14.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/dist/store.js CHANGED
@@ -95,10 +95,13 @@ feature: Example
95
95
  # How to write a scout spec
96
96
 
97
97
  One file per feature/component. File-level frontmatter sets defaults
98
- (\`feature\`, \`profile\`, \`tags\`, \`viewports\`). Each \`##\` heading is one scenario;
99
- optional \`profile\`/\`notes\`/\`tags\`/\`viewports\` override lines may follow a heading
100
- before the prose. A scenario's \`viewports\` list (built-ins: mobile/desktop/tablet)
101
- REPLACES the file-level one and fans the scenario out into one run per viewport.
98
+ (\`feature\`, \`profile\`, \`tags\`, \`viewports\`, \`cookies\`, \`storage\`). Each \`##\`
99
+ heading is one scenario; optional \`profile\`/\`notes\`/\`tags\`/\`viewports\`/
100
+ \`cookies\`/\`storage\` override lines may follow a heading before the prose. A
101
+ scenario's \`viewports\` list (built-ins: mobile/desktop/tablet) REPLACES the
102
+ file-level one and fans the scenario out into one run per viewport.
103
+ \`cookies:\`/\`storage:\` seed browser preconditions before the app loads (never
104
+ recorded steps); a \`value\` may use a \`$ENV:VAR\` placeholder resolved at launch.
102
105
  Write the flow + expected behavior in plain language — no selectors, no code.
103
106
 
104
107
  Copy the block below into a new \`*.scout.md\` file (outside the fence) to start:
@@ -109,6 +112,14 @@ feature: Paywall
109
112
  profile: anon
110
113
  tags: [monetization]
111
114
  viewports: [mobile]
115
+ cookies: # list of objects (attributes go here)
116
+ - name: hn_checkout_variant
117
+ value: A
118
+ storage: # object with local / session / remove
119
+ local:
120
+ hn_app_open_count: "2"
121
+ remove:
122
+ - hn_pwa_prompt_dismissed
112
123
  ---
113
124
 
114
125
  ## Free user hits paywall on ep 3
@@ -117,6 +128,8 @@ Open ep 3 of series X without login; paywall appears with a signup CTA.
117
128
  ## Subscriber bypasses paywall
118
129
  profile: qa
119
130
  viewports: [mobile, desktop]
131
+ cookies: hn_checkout_variant=C # inline override: name=value[, n2=v2]
132
+ storage: local.hn_app_open_count=3, remove=flag # inline: local./session. keys, remove=key
120
133
 
121
134
  Logged-in subscriber opens ep 3; plays with no paywall.
122
135
  \`\`\`
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -5,9 +5,42 @@ export interface Target {
5
5
  name?: string;
6
6
  /** CSS path fallback when role+name is not unique */
7
7
  css?: string;
8
+ /**
9
+ * data-testid — the location strategy for elements OUTSIDE the accessibility
10
+ * tree (a gesture layer, an overlay `<div>` with no role/name). Preferred over
11
+ * `css` when present: stable across DOM refactors and resolved via Playwright's
12
+ * `getByTestId`. Populated by selector clicks / state assertions.
13
+ */
14
+ testId?: string;
8
15
  /** Human-readable label for reports */
9
16
  description: string;
10
17
  }
18
+ /**
19
+ * Matcher for a state assertion on an element found by a {@link Target}. Asserts
20
+ * VISUAL/structural state that text- and URL-based checks can't reach: a class
21
+ * token, an attribute, or a computed style. Its reason for being is the opacity
22
+ * toggle pattern — a control kept in the DOM but hidden with `opacity:0`, which
23
+ * Playwright's visibility (and therefore `assertNotVisible`) still counts as
24
+ * VISIBLE. Assert `opacity-0`/computed `opacity === "0"` instead. Every provided
25
+ * check must hold; polled until they do or the timeout elapses, so it is
26
+ * deterministic on replay.
27
+ */
28
+ export interface ElementStateMatcher {
29
+ /** A class token that MUST be present (e.g. "opacity-0"). Membership, not full-string equality. */
30
+ hasClass?: string;
31
+ /** A class token that must NOT be present (e.g. "opacity-100"). */
32
+ notHasClass?: string;
33
+ /** An attribute that must be present; with `value`, it must equal `value`. */
34
+ attribute?: {
35
+ name: string;
36
+ value?: string;
37
+ };
38
+ /** A computed style that must equal `value` (e.g. property "opacity", value "0"). */
39
+ computedStyle?: {
40
+ property: string;
41
+ value: string;
42
+ };
43
+ }
11
44
  /** HTTP status family used by network assertions when an exact code is too strict. */
12
45
  export type StatusClass = "2xx" | "3xx" | "4xx" | "5xx";
13
46
  /**
@@ -71,6 +104,28 @@ export interface ScenarioCookie {
71
104
  secure?: boolean;
72
105
  sameSite?: "Strict" | "Lax" | "None";
73
106
  }
107
+ /**
108
+ * Web storage seeded into the browser context before the app loads, resolved
109
+ * from a scenario's spec (profile default + file frontmatter + per-section
110
+ * override, merged per key per namespace; `remove` concatenated). It is a
111
+ * context-creation parameter — re-resolved fresh from the spec each run and
112
+ * applied via an init-script that runs before any page script, so the seed is
113
+ * in place before the app reads it. It is NEVER a recorded/replayable Step and
114
+ * never lands in the committed script JSON (like storageState/cookies).
115
+ *
116
+ * Use it to make verifiable a feature whose trigger lives in storage: a "seen"
117
+ * flag, an open-count threshold, a dismissed prompt. Values may carry a
118
+ * `$ENV:VAR` placeholder, resolved only at launch. `remove` clears keys from
119
+ * BOTH localStorage and sessionStorage, guaranteeing a clean precondition.
120
+ */
121
+ export interface ScenarioStorage {
122
+ /** localStorage key→value pairs to seed (value may be a `$ENV:VAR`). */
123
+ local?: Record<string, string>;
124
+ /** sessionStorage key→value pairs to seed (value may be a `$ENV:VAR`). */
125
+ session?: Record<string, string>;
126
+ /** Keys removed from both localStorage and sessionStorage before the seed. */
127
+ remove?: string[];
128
+ }
74
129
  /**
75
130
  * Browser permission policy resolved from a scenario's spec (file frontmatter
76
131
  * is the default, per-section keys override it, merged per-axis). Three states
@@ -135,7 +190,11 @@ export type Step = {
135
190
  kind: "assertNotVisible";
136
191
  text: string;
137
192
  timeout?: number;
138
- } | {
193
+ } | ({
194
+ kind: "assertState";
195
+ target: Target;
196
+ timeout?: number;
197
+ } & ElementStateMatcher) | {
139
198
  kind: "assertUrl";
140
199
  pattern: string;
141
200
  timeout?: number;
@@ -192,6 +251,14 @@ export interface Scenario {
192
251
  * these. Carries raw `$ENV:VAR` placeholders — resolved only at launch.
193
252
  */
194
253
  cookies?: ScenarioCookie[];
254
+ /**
255
+ * Web storage seeded on the scenario (profile default + file frontmatter +
256
+ * per-section override, merged per key per namespace). A context-creation
257
+ * parameter re-resolved each run, applied before the app loads — never baked
258
+ * into the recorded script. Carries raw `$ENV:VAR` placeholders — resolved
259
+ * only at launch.
260
+ */
261
+ storage?: ScenarioStorage;
195
262
  /** Source spec file, relative to the project root */
196
263
  file: string;
197
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pcamarajr/scout",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Self-healing browser QA: natural-language scenarios verified by an AI agent, replayed deterministically in CI",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,9 +53,9 @@ Logged-in subscriber opens ep 3; the episode plays with no paywall.
53
53
 
54
54
  Rules that matter when you author:
55
55
 
56
- - **Frontmatter** (YAML, optional): `feature` (defaults to the filename), `profile` (default auth profile), `tags`, `viewports`.
56
+ - **Frontmatter** (YAML, optional): `feature` (defaults to the filename), `profile` (default auth profile), `tags`, `viewports`, `cookies`, `storage`.
57
57
  - **Each `## heading` is one scenario.** Its logical slug is `<file-slug>/<scenario-slug>` (e.g. `paywall/free-user-hits-paywall-on-ep-3`) and must be unique across the suite. Duplicate headings in a file, or a scenario with no body text, are hard errors.
58
- - **Per-scenario overrides:** immediately under a heading you may place `profile:`, `notes:`, `tags:`, `viewports:`, `grantPermissions:`, `denyPermissions:`, and `geolocation:` lines (before the prose) to override the file-level defaults.
58
+ - **Per-scenario overrides:** immediately under a heading you may place `profile:`, `notes:`, `tags:`, `viewports:`, `grantPermissions:`, `denyPermissions:`, `geolocation:`, `cookies:`, and `storage:` lines (before the prose) to override the file-level defaults.
59
59
  - **Body = flow + expected behavior, in plain language.** Describe what the user does and what must (or must not) be true. No CSS selectors, no Playwright code — the agent discovers the real elements at run time and records them.
60
60
  - A `.scout.md` whose every `##` lives inside a fenced ```` ``` ```` block parses as **zero scenarios** (that is how `example.scout.md` documents the format without polluting the suite).
61
61
 
@@ -127,6 +127,75 @@ Notes:
127
127
  - **Grant wins over deny:** a scenario that grants a permission overrides a file-level deny of the same permission (as in the example above).
128
128
  - `denyPermissions` matters mainly in **headed** runs (headless already denies silently). What changes behavior in CI is `grantPermissions` and a granted `geolocation` with coordinates.
129
129
 
130
+ ## Cookies
131
+
132
+ When a flow's precondition is a cookie — forcing a server-side experiment variant, a feature flag, a consent state — declare it and Scout seeds it into the browser **before the first navigation**, for both the AI run and deterministic replay. Like `storageState`, it's a context-creation parameter, never a recorded step, so replay stays deterministic and the agent never needs a "set cookie" tool.
133
+
134
+ Two forms. In the **frontmatter** (file default) or a **profile** (shared base in `scout.config.json`), `cookies:` is a list of objects — the place for attributes:
135
+
136
+ ```markdown
137
+ ---
138
+ feature: Checkout
139
+ cookies:
140
+ - name: hn_checkout_variant
141
+ value: A # file default: variant A
142
+ - name: consent
143
+ value: "yes"
144
+ httpOnly: true
145
+ sameSite: Lax # Strict | Lax | None
146
+ ---
147
+
148
+ ## Default variant
149
+ Open checkout; the file-level variant A is in effect.
150
+
151
+ ## Force variant C
152
+ cookies: hn_checkout_variant=C # per-scenario override: inline name=value
153
+
154
+ Open checkout; variant C is forced.
155
+ ```
156
+
157
+ Notes:
158
+
159
+ - **Merge by name:** a profile's cookies are the base; the file frontmatter and then the per-scenario override win, keyed by cookie name. The per-`##` override is the terse inline `name=value[, n2=v2]` form (no attributes — those belong in the frontmatter/profile).
160
+ - **`name` and `value` are required;** `domain`, `path`, `expires` (unix seconds), `httpOnly`, `secure`, `sameSite` are optional. `domain`/`path` default to the host of `baseUrl` and `/`.
161
+ - **Secrets:** a `value` may use the `$ENV:VAR` placeholder (e.g. `cookies: session=$ENV:SESSION_TOKEN`) — resolved at launch, so the secret never lands in the committed spec or the agent's context. Only `value` is resolved.
162
+ - **Fail-fast:** an unknown field, a bad `sameSite`, or a missing env var is a hard error — a silently skipped cookie precondition would produce a misleading verdict.
163
+
164
+ ## Local & session storage
165
+
166
+ Some features are gated not by a cookie or the server, but by the browser's own **web storage** — an open-count threshold, a "you've seen this" flag, a dismissed prompt. The agent can't set storage (it has no `page.evaluate` tool), so declare a `storage:` seed and Scout applies it **before the app loads**, for both the AI run and deterministic replay. Like `cookies`/`storageState`, it's a context-creation parameter, never a recorded step — replay re-resolves it fresh from the spec, so it stays deterministic and the agent never needs a "set storage" tool.
167
+
168
+ It seeds **both** `localStorage` and `sessionStorage` (the latter is something `storageState` can't carry) via an init-script that runs before any page script, and it can `remove` keys to guarantee a clean precondition.
169
+
170
+ Two forms. In the **frontmatter** (file default) or a **profile** (shared base in `scout.config.json`), `storage:` is an object with `local`, `session` and/or `remove`:
171
+
172
+ ```markdown
173
+ ---
174
+ feature: PWA install prompt
175
+ storage:
176
+ local:
177
+ hn_app_open_count: "2" # file default: two prior opens
178
+ remove:
179
+ - hn_pwa_prompt_dismissed # start from a non-dismissed state
180
+ ---
181
+
182
+ ## Below the threshold — no prompt yet
183
+ Open the app; the install prompt does not appear.
184
+
185
+ ## At the threshold — prompt appears
186
+ storage: local.hn_app_open_count=3, remove=hn_other_flag
187
+
188
+ Open the app; the install prompt appears.
189
+ ```
190
+
191
+ Notes:
192
+
193
+ - **Inline override form:** the per-`##` override is a single line of comma-separated tokens — `local.<key>=<value>`, `session.<key>=<value>`, or `remove=<key>`. The namespace is spelled inline because a heading override line can't carry a nested YAML object; nested objects belong in the frontmatter/profile.
194
+ - **Merge:** a profile's storage is the base; the file frontmatter and then the per-scenario override win, **per key per namespace**. `remove` lists **concatenate and dedupe** across all levels.
195
+ - **`remove` clears both namespaces:** a removed key is dropped from `localStorage` **and** `sessionStorage`. Removals apply before the seed, so a declared value always wins over a removal of the same key.
196
+ - **Secrets:** a value may use the `$ENV:VAR` placeholder — resolved at launch, so the secret never lands in the committed spec or the agent's context.
197
+ - **Fail-fast:** an unknown field (only `local`/`session`/`remove` are allowed), a non-string value, or a malformed inline token is a hard error — a silently skipped storage precondition would produce a misleading verdict.
198
+
130
199
  ## New tabs / popups
131
200
 
132
201
  When a click opens a new tab (a `target="_blank"` link or `window.open`), Scout's `browser_click` result flags it. The agent then calls **`browser_switch_tab`** to move control to that tab; with no argument it switches to the newest tab, or pass a `urlGlob` (e.g. `**/booking**`) to target a specific one. The switch waits for the tab to finish loading, then becomes a recorded `switchTab` step that replays deterministically.
@@ -137,6 +206,14 @@ Console and network observers are **per-tab**: assertions (`browser_assert_no_co
137
206
 
138
207
  Beyond *absence* of errors, you can assert a **specific log was emitted** (e.g. a `DEBUG:[...]` line gated behind a debug flag). `browser_assert_console_message` requires a message on the **active tab** that contains **all** of the given substrings **within a single message**, optionally constrained to a `type` (`log`, `debug`, `error`, …). Inspect first (`browser_inspect_logs` now also lists `log`/`debug`/`info` messages), then assert on a **stable** substring — a prefix like `DEBUG:[FEATURE/x]`, never a volatile value — so the check tolerates unrelated console noise. It becomes a recorded step that fails the deterministic replay if the log goes missing.
139
208
 
209
+ ## Clicking role-less elements (by `data-testid`)
210
+
211
+ `browser_click` only takes a numbered `[ref]` from the accessibility snapshot, so an element with **no ARIA role or name** — a gesture/tap layer, an overlay `<div data-testid="…">`, a purely visual control — never gets a `[ref]` and can't be clicked that way. When you hit one, use **`browser_click_selector`**: it clicks by `data-testid` (**preferred** — stabler than a CSS path) or, if there is none, a `css` selector. It records as a plain deterministic `click` step, so replay needs no LLM. Reach for it only for elements the snapshot can't reference; a normal button still goes through `browser_click`.
212
+
213
+ ## Asserting visual/structural state (opacity, class, attribute)
214
+
215
+ `browser_assert` covers text and URL, but it **can't confirm an element is hidden by `opacity:0`** — Playwright counts an opacity-hidden node (still in the DOM, still laid out) as *visible*, so a `notVisibleText`-style check would false-pass. For show/hide toggles and other purely visual state, use **`browser_assert_state`**: locate the element by `data-testid` (preferred) or `css`, then assert one or more of a class token (`hasClass` / `notHasClass`, e.g. `opacity-0` vs `opacity-100`), an `attribute` (present, or equal to a value like `aria-expanded=true`), or a `computedStyle` (e.g. `opacity` = `0`). It polls until every check holds or the timeout, then records a deterministic `assertState` step. This is what lets a scenario verify a reveal/hide control that stays mounted — describe the toggle in plain prose ("the drawer becomes hidden") and the agent records the class/style check.
216
+
140
217
  ## Base URL and secrets
141
218
 
142
219
  There is a **default base URL** set at `scout init` (in `scout.config.json`). It can be **overridden per run** without editing the file:
@@ -20,6 +20,7 @@ Your job is to **co-author `.scout.md` scenarios** with QA, **verify** them with
20
20
  without running it.
21
21
 
22
22
  **Read `AGENTS.md` at the repo root — it is the canonical, always-current guide**
23
- to the authoring loop, the `.scout.md` format, base-URL/secret handling,
23
+ to the authoring loop, the `.scout.md` format, per-scenario overrides (viewports,
24
+ permissions, `cookies:` and `storage:` preconditions), base-URL/secret handling,
24
25
  verdicts, and failure triage. Follow it. For commands and flags, run
25
26
  `scout --help` / `scout <command> --help`.
@@ -16,6 +16,7 @@ Co-author `.scout.md` scenarios with QA, verify them with `scout go`, and report
16
16
  the **real** verdict — never claim a scenario passes without running it.
17
17
 
18
18
  **Read `AGENTS.md` at the repo root** — it is the canonical, always-current
19
- guide to the authoring loop, the `.scout.md` format, base-URL/secret handling,
20
- verdicts, and failure triage. For commands and flags, run `scout --help` /
21
- `scout <command> --help`.
19
+ guide to the authoring loop, the `.scout.md` format, per-scenario overrides
20
+ (viewports, permissions, `cookies:` and `storage:` preconditions),
21
+ base-URL/secret handling, verdicts, and failure triage. For commands and flags,
22
+ run `scout --help` / `scout <command> --help`.