pi-ui-extend 1.0.24 → 1.0.30

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.
Files changed (39) hide show
  1. package/README.md +5 -1
  2. package/dist/app/app.d.ts +1 -0
  3. package/dist/app/app.js +12 -0
  4. package/dist/app/icons.d.ts +1 -0
  5. package/dist/app/icons.js +2 -0
  6. package/dist/app/input/input-controller.d.ts +2 -0
  7. package/dist/app/input/input-controller.js +8 -5
  8. package/dist/app/rendering/render-controller.js +2 -0
  9. package/dist/app/rendering/status-line-renderer.d.ts +4 -1
  10. package/dist/app/rendering/status-line-renderer.js +12 -0
  11. package/dist/app/screen/mouse-controller.d.ts +4 -1
  12. package/dist/app/screen/mouse-controller.js +9 -0
  13. package/dist/app/session/agent-pause-controller.d.ts +30 -0
  14. package/dist/app/session/agent-pause-controller.js +178 -0
  15. package/dist/app/session/session-event-controller.d.ts +1 -0
  16. package/dist/app/session/session-event-controller.js +22 -15
  17. package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
  18. package/dist/app/session/session-lifecycle-controller.js +1 -0
  19. package/dist/app/types.d.ts +10 -0
  20. package/dist/config.js +1 -1
  21. package/dist/default-pix-config.js +2 -2
  22. package/external/pi-tools-suite/README.md +1 -1
  23. package/external/pi-tools-suite/docs/browser-qa-subagent.md +34 -2
  24. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +24 -18
  25. package/external/pi-tools-suite/src/async-subagents/core/config.ts +14 -3
  26. package/external/pi-tools-suite/src/async-subagents/core/process.ts +19 -0
  27. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +97 -17
  28. package/external/pi-tools-suite/src/async-subagents/core/stop.ts +4 -2
  29. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +150 -19
  30. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +78 -0
  31. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +80 -0
  32. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-flow.example.jsonc +54 -1
  33. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +1260 -79
  34. package/external/pi-tools-suite/src/dcp/index.ts +21 -0
  35. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +92 -0
  36. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +21 -16
  37. package/external/pi-tools-suite/src/todo/index.ts +86 -46
  38. package/external/pi-tools-suite/src/todo/todo.ts +1 -1
  39. package/package.json +1 -1
@@ -15,6 +15,13 @@ Never read, print, grep, copy, or edit credential values from
15
15
 
16
16
  ## Workflow
17
17
 
18
+ Treat target discovery as a 30-second preflight and invoke the runner within 45
19
+ seconds of starting. Use at most one runner invocation unless the task explicitly
20
+ requests multiple auth profiles. If you cannot identify a reachable target and
21
+ a supported deterministic assertion inside that preflight, return a structured
22
+ `BLOCKED` result immediately. Do not consume the launcher budget on further
23
+ source reading, server polling, capability probing, or retries.
24
+
18
25
  1. Resolve `scripts/browser-qa-runner.mjs` relative to this skill.
19
26
  2. Use the launcher-provided `$PI_SUBAGENT_AGENT_DIR/browser-qa/` workspace.
20
27
  The launcher creates its private `flows/` directory and the runner rejects
@@ -28,10 +35,13 @@ Never read, print, grep, copy, or edit credential values from
28
35
  list without creating `.pi/qa_auth.jsonc`. Otherwise skip profile discovery
29
36
  and use public mode. Choose an auth profile only when the task names its id,
30
37
  safe profile traits make the choice unambiguous, or a public run proves that
31
- the requested page requires login.
38
+ the requested page requires login. If form authentication is required but
39
+ there is no usable profile, follow **Form-auth scaffolding** below instead of
40
+ asking the user to discover selectors.
32
41
  5. Inspect the target code and write a declarative JSONC flow under
33
- `$PI_SUBAGENT_AGENT_DIR/browser-qa/flows/`. Never put credentials or
34
- executable JavaScript in it.
42
+ `$PI_SUBAGENT_AGENT_DIR/browser-qa/flows/`. Never put credentials or raw
43
+ executable JavaScript in it. The `evaluate` action exposes only the safe
44
+ operations documented below; it does not accept expressions or scripts.
35
45
  6. Run public QA with
36
46
  `node <runner> run --base-url <url> --flow <flow.jsonc>`. The URL's exact
37
47
  origin becomes the fail-closed allowlist. Only for authenticated QA, add
@@ -39,7 +49,7 @@ Never read, print, grep, copy, or edit credential values from
39
49
  Profile id, URL, and flow path are non-secret; never pass credentials as
40
50
  arguments or environment variables.
41
51
  7. Report deterministic assertions and every artifact returned by the runner.
42
- For each screenshot, video, or trace, emit a separate clickable Markdown
52
+ For each screenshot, video, trace, or retained download, emit a separate clickable Markdown
43
53
  link using its `uri` and also show its absolute `path`. Do this for failed
44
54
  runs too whenever `artifacts` is present; never report only `evidenceDir`.
45
55
  Visual inspection supplements assertions; it does not replace them.
@@ -56,9 +66,10 @@ Never read, print, grep, copy, or edit credential values from
56
66
  plus accessible `name`; `label`; `placeholder`; visible `text`; CSS only as a
57
67
  last resort. Use `exact: true` when similar elements could make a match
58
68
  ambiguous.
59
- - Let locator actions auto-wait. Use `waitFor` for an explicit UI state and use
60
- `waitForTimeout` only for a short, unavoidable animation/debounce—not as a
61
- substitute for an assertion. Set flow `timeoutMs` only as high as the target
69
+ - Let locator actions auto-wait. Assertions retry until the flow timeout, so
70
+ prefer them over a preceding sleep. Use `waitFor` for an explicit setup state
71
+ and `waitForTimeout` only for short input settling or an unavoidable
72
+ animation/debounce. Set flow `timeoutMs` only as high as the target
62
73
  legitimately needs.
63
74
  - Place `authRejectedIf` immediately after navigation or any transition that
64
75
  may reveal expired authentication.
@@ -71,22 +82,97 @@ ambiguous failure, or deciding what evidence proves the result.
71
82
 
72
83
  ## Flow contract
73
84
 
74
- The flow is `{ "steps": [...] }` with at most 100 steps. Supported actions:
85
+ The flow is `{ "steps": [...] }`, no larger than 16 MiB, with at most 100
86
+ steps. The larger bound exists only for bounded in-memory upload payloads.
87
+ Supported actions:
75
88
 
76
89
  - navigation: `goto`, `reload`, `waitFor`, `waitForTimeout`
77
90
  - interaction: `click`, `doubleClick`, `hover`, `fill`, `press`, `check`,
78
- `uncheck`, `selectOption`
91
+ `uncheck`, `selectOption`, `wheel`, `evaluate`, `dragTo`, `uploadFiles`,
92
+ `openPopup`, `download`
79
93
  - assertions: `assertVisible`, `assertHidden`, `assertEnabled`,
80
94
  `assertDisabled`, `assertChecked`, `assertUnchecked`, `assertText`,
81
- `assertValue`, `assertCount`, `assertURL`
95
+ `assertValue`, `assertAttribute`, `assertCount`, `assertURL`,
96
+ `assertDOMMetric`
82
97
  - evidence/auth: `screenshot`, `authRejectedIf`
83
98
 
84
99
  Locators accept one of `testId`, `role` (plus optional `name`), `label`,
85
100
  `placeholder`, `text`, or `css`; add `exact: true` where useful. String
86
101
  assertions require exactly one of `equals` or `includes`.
102
+ `assertAttribute` additionally requires a bounded `attribute` name and is
103
+ useful for `aria-*`, `data-*`, `href`, and similar observable state. All
104
+ assertions retry until `timeoutMs` and report generic failures without exposing
105
+ the actual text, value, or attribute content.
106
+
107
+ Set an optional top-level `viewport` with integer `width` and `height` from
108
+ `320×240` through `3840×2160`; the default is `1280×720`. The same dimensions
109
+ are used for the browser viewport and recorded video.
110
+
111
+ The top-level `environment` may set `locale`, `timezoneId`, `colorScheme`, and
112
+ `reducedMotion`. Defaults are deterministic: `en-US`, `UTC`, `light`, and
113
+ `reduce`. Color scheme accepts `light`, `dark`, or `no-preference`; reduced
114
+ motion accepts `reduce` or `no-preference`. The resolved environment is returned
115
+ in the result alongside the viewport.
116
+
117
+ Triggering interactions (`click`, `doubleClick`, `press`, `check`, `uncheck`,
118
+ and `selectOption`) may declare race-free expectations that are armed before
119
+ the interaction:
120
+
121
+ - `expectResponse`: exact origin-relative `path` (without query/fragment),
122
+ uppercase `method`, and integer `status` from 100 through 599;
123
+ - `expectDialog`: `type` (`alert`, `beforeunload`, `confirm`, or `prompt`), a
124
+ nested `message` matcher with exactly one of `equals`/`includes`, and boolean
125
+ `accept`.
126
+
127
+ The runner never records response bodies, headers, URLs, actual dialog text, or
128
+ prompt defaults in observations or failure reasons. A mismatching dialog is
129
+ dismissed so it cannot deadlock the browser.
130
+
131
+ `dragTo` requires a source `locator` and `dropTarget`, with optional bounded
132
+ `sourcePosition` and `dropPosition` `{ x, y }`. `uploadFiles` accepts only
133
+ in-memory entries `{ name, mimeType, base64 }`; up to 10 files, 5 MiB each and
134
+ 10 MiB total. An empty array clears the input. Filesystem paths and directories
135
+ are not supported.
136
+
137
+ `download` atomically clicks its locator and requires a nested `filename`
138
+ matcher. `maxBytes` defaults to 5 MiB and is capped at 25 MiB. Downloads are
139
+ deleted after validation unless `retain: true` and a safe `name` are supplied;
140
+ retained bytes appear in `artifacts.downloads` under a runner-generated `.bin`
141
+ name. The runner cancels while its private copy grows past `maxBytes`, but this
142
+ is an evidence-retention bound rather than a network-bandwidth guarantee because
143
+ the browser may already hold temporary bytes. Retained bytes are scanned for
144
+ configured authentication before publication. The actual server filename is
145
+ never placed in diagnostics.
146
+
147
+ `openPopup` atomically clicks a locator, captures a same-origin popup, and stores
148
+ it under a safe `name` (maximum three). Target later actions with
149
+ `{ "target": { "type": "popup", "name": "..." } }`. Target same-origin
150
+ iframes with `{ "target": { "type": "frame", "locator": { ... } } }`; add
151
+ `page` with a popup name for a frame inside that popup. Frame origin is checked
152
+ from its live document before every scoped step. Cross-origin popups/frames are
153
+ rejected. Popup recordings are returned as separate video artifacts.
154
+
155
+ `wheel` accepts finite `deltaX`/`deltaY` values and requires at least one
156
+ non-zero delta. With a locator, the runner hovers that element before sending
157
+ the wheel input. Safe `evaluate` operations are:
158
+
159
+ - `scrollTo`: optional locator plus numeric `x`/`y` or the string `"max"`;
160
+ - `scrollBy`: optional locator plus numeric `deltaX`/`deltaY`;
161
+ - `metrics`: optional locator plus an optional safe `name`; values are returned
162
+ in the runner's `observations` array.
163
+
164
+ Element metrics are `scrollLeft`, `scrollTop`, `scrollWidth`, `scrollHeight`,
165
+ `clientWidth`, `clientHeight`, `x`, `y`, `width`, and `height`. Page metrics are
166
+ `scrollX`, `scrollY`, `scrollWidth`, `scrollHeight`, `clientWidth`,
167
+ `clientHeight`, `viewportWidth`, and `viewportHeight`. Use `assertDOMMetric`
168
+ with a `metric` and exactly one of `equals`, `greaterThan`,
169
+ `greaterThanOrEqual`, `lessThan`, or `lessThanOrEqual` for a deterministic
170
+ oracle. Raw JavaScript remains intentionally unsupported.
87
171
 
88
172
  ```jsonc
89
173
  {
174
+ "viewport": { "width": 844, "height": 847 },
175
+ "environment": { "locale": "en-GB", "timezoneId": "Europe/London", "colorScheme": "dark" },
90
176
  "steps": [
91
177
  { "action": "goto", "path": "/settings" },
92
178
  { "action": "authRejectedIf", "urlIncludes": "/login" },
@@ -94,7 +180,18 @@ assertions require exactly one of `equals` or `includes`.
94
180
  "action": "assertVisible",
95
181
  "locator": { "role": "heading", "name": "Settings", "exact": true }
96
182
  },
97
- { "action": "click", "locator": { "testId": "save-settings" } },
183
+ { "action": "wheel", "locator": { "css": ".settings-panel" }, "deltaY": 500 },
184
+ {
185
+ "action": "assertDOMMetric",
186
+ "locator": { "css": ".settings-panel" },
187
+ "metric": "scrollTop",
188
+ "greaterThan": 0
189
+ },
190
+ {
191
+ "action": "click",
192
+ "locator": { "testId": "save-settings" },
193
+ "expectResponse": { "path": "/api/settings", "method": "PUT", "status": 200 }
194
+ },
98
195
  { "action": "assertText", "locator": { "testId": "toast" }, "includes": "Saved" },
99
196
  { "action": "screenshot", "name": "settings-saved" }
100
197
  ]
@@ -104,7 +201,7 @@ assertions require exactly one of `equals` or `includes`.
104
201
  For multiple profiles, invoke the runner separately. Every invocation gets an
105
202
  isolated browser context and exclusive evidence directory; the runner closes
106
203
  all owned browser resources on success and failure. Flows, screenshots, video,
107
- sanitized traces, and runner result manifests remain under
204
+ sanitized traces, retained downloads, and runner result manifests remain under
108
205
  `$PI_SUBAGENT_AGENT_DIR/browser-qa/` so normal sub-agent shutdown or cleanup
109
206
  deletes them with the run directory. For form auth, recording starts on the login
110
207
  page and includes field filling and submission; password inputs remain masked,
@@ -113,11 +210,43 @@ after login succeeds so credentials are not captured in the trace.
113
210
 
114
211
  ## Credentials and blocked runs
115
212
 
213
+ ### Form-auth scaffolding
214
+
215
+ When a public HTTPS login page (or loopback HTTP page for local development) is
216
+ known and there is no usable profile, invoke the trusted runner once:
217
+
218
+ ```sh
219
+ node <runner> auth scaffold \
220
+ --profile <safe-id> \
221
+ --login-url <public-login-url>
222
+ ```
223
+
224
+ The runner discovers the login form and privately writes a target-specific
225
+ `.pi/qa_auth.jsonc` containing `__PI_QA_SECRET_n__` values. It may create the
226
+ file or replace only the runner's own empty generated template; it refuses to
227
+ overwrite invalid or non-empty auth configuration. Never inspect the result.
228
+ Relay `QA_AUTH_SCAFFOLD_CREATED`, the reported file, profile, placeholder count,
229
+ and action, then ask the user to replace only those placeholder values. Browser
230
+ QA must resume in a new run after the user confirms that edit.
231
+
232
+ By default, login succeeds when the discovered form becomes hidden. A form-less
233
+ page therefore requires an explicit deterministic signal using either
234
+ `--success-url <glob>` or `--success-selector <selector>` with optional
235
+ `--success-state attached|detached|visible|hidden`. Use `--base-url <url>` only
236
+ to override the profile's same-origin application root. These are public
237
+ metadata; never put credential values in command arguments or environment
238
+ variables.
239
+
240
+ Do not scaffold cookie, bearer, storage, storage-state, MFA, CAPTCHA, or
241
+ federated login. For those modes, use the generic empty-template flow below and
242
+ ask the user to configure the profile themselves.
243
+
116
244
  Do not request credentials merely because `.pi/qa_auth.jsonc` is absent. If the
117
245
  task explicitly requires authenticated behavior, or a public run reaches the
118
246
  flow's `authRejectedIf` check, and `profiles` returned no usable profile, run
119
- `node <runner> profiles --require-auth`. Only this explicit authenticated path
120
- may create the private empty template.
247
+ form-auth scaffolding when supported. Otherwise run
248
+ `node <runner> profiles --require-auth`. Only these explicit authenticated paths
249
+ may create the private auth file.
121
250
 
122
251
  If that command or an authenticated run returns `QA_AUTH_UPDATE_REQUIRED`, stop
123
252
  and explicitly report that authenticated browser QA requires credentials or an
@@ -125,15 +254,17 @@ auth-config update. Ask the user to fill the reported file and rerun QA. If
125
254
  `templateCreated` is true, say that a private empty template was created at that
126
255
  path. Relay only the runner's profile, file, reason, action, and template-created
127
256
  state; never read the generated file or attempt to recover by exposing or
128
- replaying credentials.
257
+ replaying credentials. If the action is `fill_credentials`, also relay the
258
+ placeholder count and tell the user to replace only the generated placeholder
259
+ values.
129
260
 
130
261
  For any other blocked run, report the runner status and redacted reason. Do not
131
262
  claim that browser QA passed based on source inspection, unit tests, or a build.
132
263
 
133
264
  After any runner invocation that actually performed browser testing, include
134
265
  all non-empty `artifacts.screenshots`, `artifacts.videos`, and
135
- `artifacts.traces` groups in the final response. These links are mandatory so
136
- the user can open the evidence directly.
266
+ `artifacts.traces`, and `artifacts.downloads` groups in the final response.
267
+ These links are mandatory so the user can open the evidence directly.
137
268
 
138
- See `references/qa-auth.example.jsonc`, `references/qa-flow.example.jsonc`, and
139
- `references/qa-design.md`.
269
+ See `references/qa-auth.example.jsonc`, `references/qa-flow.example.jsonc`,
270
+ `references/qa-design.md`, and `references/auth-scaffold-spec.md`.
@@ -0,0 +1,78 @@
1
+ # Spec: Browser QA form-auth scaffold
2
+
3
+ ## Type
4
+
5
+ Change
6
+
7
+ ## Goal
8
+
9
+ Let the browser-QA sub-agent prepare a target-specific form-auth configuration
10
+ without reading, writing, receiving, or printing credential values. The user
11
+ should only need to replace clearly marked secret placeholders.
12
+
13
+ ## Scope
14
+
15
+ - Add a trusted runner command that inspects a public login form.
16
+ - Generate `.pi/qa_auth.jsonc` with discovered field and submit selectors.
17
+ - Keep generated values as non-secret placeholders until the user edits them.
18
+ - Preserve the rule that agents never read or edit `.pi/qa_auth.jsonc`.
19
+
20
+ ## Non-goals
21
+
22
+ - Collecting credentials interactively through the agent.
23
+ - Guessing cookie, token, storage-state, MFA, or federated-login credentials.
24
+ - Modifying an existing non-empty auth configuration.
25
+
26
+ ## Behavior
27
+
28
+ - `auth scaffold` requires a project-local browser-QA sub-agent workspace, an
29
+ HTTPS login URL (or loopback HTTP for local development), and a safe profile
30
+ id.
31
+ - The trusted runner opens the unauthenticated login page with fail-closed
32
+ origin routing and discovers the most likely login form, fillable fields,
33
+ submit control, and form container.
34
+ - It writes a private `0600` `.pi/qa_auth.jsonc` containing only generated
35
+ selectors and `__PI_QA_SECRET_n__` placeholders. It may replace the runner's
36
+ empty generated template, but never a non-empty profiles object.
37
+ - By default, successful login means that the discovered form becomes hidden.
38
+ A form-less page requires an explicit success URL pattern or selector/state.
39
+ - A run cannot use a profile while generated placeholders remain. The runner
40
+ returns `QA_AUTH_UPDATE_REQUIRED` without exposing selectors or values.
41
+
42
+ ## Invariants
43
+
44
+ - No credential value is accepted through command arguments or environment
45
+ variables.
46
+ - Scaffold status output contains only path, profile id, counts, and action.
47
+ - The command never prints inspected DOM text, input values, URLs, or selectors.
48
+ - Agents continue to treat `.pi/qa_auth.jsonc` as opaque and user-owned.
49
+
50
+ ## Edge cases
51
+
52
+ - Reject pages without a discoverable fillable field or submit control, and
53
+ form-less pages without an explicit success condition.
54
+ - Reject remote plain-HTTP login pages.
55
+ - Reject cross-origin base URLs and malformed success options.
56
+ - Reject symlinked or permissive auth paths.
57
+ - Refuse to overwrite invalid or non-empty auth configuration.
58
+
59
+ ## Related files
60
+
61
+ - `scripts/browser-qa-runner.mjs`
62
+ - `../SKILL.md`
63
+ - `../../../../../test/async-subagents/browser-qa-runner.test.ts`
64
+
65
+ ## Verification
66
+
67
+ - Unit tests for discovery output, placeholder blocking, private permissions,
68
+ empty-template replacement, and non-empty-config refusal.
69
+ - Existing browser-QA runner tests and repository checks continue to pass.
70
+
71
+ ## Evidence
72
+
73
+ - Confirmed by code: current runner owns Playwright, origin isolation, private
74
+ auth-file creation, validation, and redacted status output.
75
+ - Confirmed by tests: current runner rejects permissive/symlinked auth files and
76
+ creates its empty template with private permissions.
77
+ - Confirmed by docs: the private skill forbids agents from reading or editing
78
+ `.pi/qa_auth.jsonc`.
@@ -47,6 +47,86 @@ observable intermediate state. Sleeping longer hides races instead of proving
47
47
  behavior. If a normal operation legitimately needs more time, adjust the flow's
48
48
  `timeoutMs` rather than inserting repeated sleeps.
49
49
 
50
+ All assertion actions retry until that timeout. This makes an action followed
51
+ directly by `assertText`, `assertVisible`, `assertURL`, or another assertion
52
+ safe for asynchronously rendered outcomes. Use `assertAttribute` for
53
+ observable state such as `aria-expanded`, `aria-invalid`, or `data-state`
54
+ instead of reading DOM state through executable JavaScript.
55
+
56
+ ## Responsive and scrolling scenarios
57
+
58
+ Set the flow's top-level `viewport` whenever the bug depends on a breakpoint or
59
+ available height. Assert `viewportWidth` or `viewportHeight` with
60
+ `assertDOMMetric` when the dimensions themselves are part of the proof; the
61
+ runner also includes the applied viewport in its result.
62
+
63
+ Use `wheel` to reproduce real pointer-wheel input. Add a locator when the wheel
64
+ must target a nested scrolling container—the runner hovers it before sending
65
+ the input. Because browser scrolling may be scheduled after the wheel event,
66
+ wait only for a short known settling interval when a direct metric assertion is
67
+ otherwise racy.
68
+
69
+ Use safe `evaluate` `scrollTo`/`scrollBy` operations for deterministic setup or
70
+ to distinguish input handling from layout behavior. Use the `metrics` operation
71
+ to retain a named page/element snapshot in result `observations`, and use
72
+ `assertDOMMetric` for pass/fail. Raw JavaScript expressions are intentionally
73
+ excluded: flows remain declarative and cannot inspect authentication storage or
74
+ execute arbitrary same-origin requests.
75
+
76
+ ## Deterministic browser environment
77
+
78
+ Set top-level `environment` when locale, timezone, color scheme, or motion
79
+ preferences can change the behavior. The runner otherwise uses stable defaults
80
+ (`en-US`, `UTC`, `light`, `reduce`) instead of inheriting host settings. Prefer
81
+ asserting product-visible copy or state derived from those settings; do not use
82
+ screenshot pixels as the only oracle.
83
+
84
+ ## Causal network and dialog expectations
85
+
86
+ Put `expectResponse` or `expectDialog` on the interaction that causes the event.
87
+ The runner arms both listeners before the interaction, avoiding the race in a
88
+ separate “click, then wait” sequence. Response expectations deliberately match
89
+ only an exact allowlisted-origin pathname, HTTP method, and status. This proves
90
+ that a matching request started and received a response within the action
91
+ window without retaining its URL query, headers, or body.
92
+
93
+ Dialog expectations match a fixed dialog type and exact/included message, then
94
+ accept or dismiss it declaratively. A mismatch is dismissed before the step
95
+ fails so the page cannot freeze. Actual event metadata is never included in
96
+ failure diagnostics. Do not place secrets in expected messages or response
97
+ paths even though the runner keeps diagnostics generic.
98
+
99
+ ## Drag, upload, and download scenarios
100
+
101
+ Use `dragTo` for native DOM drag/drop and assert the resulting product state.
102
+ Optional source/drop positions are relative bounded coordinates. Canvas-only,
103
+ OS-native, or custom synthetic-event drag protocols remain unsupported; do not
104
+ work around that with executable JavaScript.
105
+
106
+ Uploads are memory-only base64 payloads declared in the flow. This intentionally
107
+ prevents a flow from selecting arbitrary project files, credential config, or
108
+ directories. Keep fixtures minimal and non-secret. An empty file list clears a
109
+ file input.
110
+
111
+ Use the atomic `download` action rather than clicking a download link directly.
112
+ Always match the suggested filename and choose a tight `maxBytes`. Retain a
113
+ download only when its contents are needed as evidence; otherwise the runner
114
+ deletes it after validation. Retained downloads use generated private names,
115
+ not server-provided paths, and are scanned for configured authentication before
116
+ publication. `maxBytes` bounds the runner's private evidence copy and triggers
117
+ cancellation while it grows, but it is not a network-bandwidth guarantee: the
118
+ browser can receive temporary bytes before cancellation.
119
+
120
+ ## Same-origin frames and popups
121
+
122
+ Use a scoped `target` for iframe or named popup interactions. The runner checks
123
+ the live frame origin before every scoped step and checks a popup after it loads;
124
+ both must remain in `allowedOrigins`. This supports embedded application UI and
125
+ same-origin auxiliary windows without opening a route around the network guard.
126
+ Cross-origin login, payment, and third-party widgets remain intentionally out of
127
+ scope. Each popup adds a separate private video artifact, so open only the
128
+ windows needed for the proof.
129
+
50
130
  ## Authentication transitions
51
131
 
52
132
  Add `authRejectedIf` directly after initial navigation and after transitions
@@ -1,5 +1,12 @@
1
1
  {
2
2
  "timeoutMs": 15000,
3
+ "viewport": { "width": 844, "height": 847 },
4
+ "environment": {
5
+ "locale": "en-GB",
6
+ "timezoneId": "Europe/London",
7
+ "colorScheme": "dark",
8
+ "reducedMotion": "reduce"
9
+ },
3
10
  "steps": [
4
11
  { "action": "goto", "path": "/settings" },
5
12
  { "action": "authRejectedIf", "urlIncludes": "/login" },
@@ -12,8 +19,54 @@
12
19
  "locator": { "label": "Display name", "exact": true },
13
20
  "value": "QA Example"
14
21
  },
15
- { "action": "click", "locator": { "testId": "save-settings" } },
22
+ { "action": "evaluate", "operation": "scrollTo", "locator": { "css": ".settings-panel" }, "y": 0 },
23
+ { "action": "wheel", "locator": { "css": ".settings-panel" }, "deltaY": 400 },
24
+ { "action": "assertDOMMetric", "locator": { "css": ".settings-panel" }, "metric": "scrollTop", "greaterThan": 0 },
25
+ { "action": "evaluate", "operation": "metrics", "locator": { "css": ".settings-panel" }, "name": "settings-scroll" },
26
+ {
27
+ "action": "dragTo",
28
+ "locator": { "testId": "available-widget" },
29
+ "dropTarget": { "testId": "enabled-widgets" }
30
+ },
31
+ {
32
+ "action": "uploadFiles",
33
+ "locator": { "label": "Import settings", "exact": true },
34
+ "files": [
35
+ { "name": "settings.json", "mimeType": "application/json", "base64": "eyJ0aGVtZSI6ImRhcmsifQ==" }
36
+ ]
37
+ },
38
+ {
39
+ "action": "click",
40
+ "locator": { "testId": "save-settings" },
41
+ "expectResponse": { "path": "/api/settings", "method": "PUT", "status": 200 },
42
+ "expectDialog": {
43
+ "type": "confirm",
44
+ "message": { "equals": "Save these settings?" },
45
+ "accept": true
46
+ }
47
+ },
48
+ { "action": "assertAttribute", "locator": { "testId": "save-settings" }, "attribute": "aria-busy", "equals": "false" },
16
49
  { "action": "assertText", "locator": { "testId": "toast" }, "includes": "Saved" },
50
+ { "action": "openPopup", "locator": { "testId": "preview-settings" }, "name": "preview" },
51
+ {
52
+ "action": "assertText",
53
+ "target": { "type": "popup", "name": "preview" },
54
+ "locator": { "role": "heading", "name": "Settings preview", "exact": true },
55
+ "equals": "Settings preview"
56
+ },
57
+ {
58
+ "action": "assertVisible",
59
+ "target": { "type": "frame", "locator": { "testId": "settings-help" } },
60
+ "locator": { "role": "heading", "name": "Settings help", "exact": true }
61
+ },
62
+ {
63
+ "action": "download",
64
+ "locator": { "testId": "export-settings" },
65
+ "filename": { "equals": "settings.json" },
66
+ "maxBytes": 1048576,
67
+ "retain": true,
68
+ "name": "settings-export"
69
+ },
17
70
  { "action": "screenshot", "name": "settings-saved" }
18
71
  ]
19
72
  }