flowstride 1.0.20 → 1.0.22

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 (99) hide show
  1. package/NATIVE_BROWSER.md +381 -0
  2. package/NATIVE_CI.md +17 -5
  3. package/NATIVE_WORKFLOWS.md +44 -0
  4. package/README.md +11 -4
  5. package/dist/adapters/api/undici.d.ts +11 -1
  6. package/dist/adapters/api/undici.js +28 -3
  7. package/dist/adapters/web/playwright.d.ts +22 -2
  8. package/dist/adapters/web/playwright.js +254 -107
  9. package/dist/ci/config.js +14 -1
  10. package/dist/ci/distribution.d.ts +38 -0
  11. package/dist/ci/distribution.js +134 -0
  12. package/dist/ci/init.d.ts +4 -1
  13. package/dist/ci/init.js +13 -2
  14. package/dist/ci/runtime.js +33 -6
  15. package/dist/cli/commands/run.js +17 -4
  16. package/dist/cli/index.js +24 -5
  17. package/dist/cli/manual_server/workspace_server.js +90 -74
  18. package/dist/manual/security.d.ts +6 -6
  19. package/dist/native/accessibility.d.ts +4 -0
  20. package/dist/native/accessibility.js +84 -0
  21. package/dist/native/assertions.d.ts +3 -0
  22. package/dist/native/assertions.js +223 -0
  23. package/dist/native/compatibility.d.ts +3 -0
  24. package/dist/native/compatibility.js +16 -0
  25. package/dist/native/components.d.ts +12 -0
  26. package/dist/native/components.js +122 -0
  27. package/dist/native/dashboard.d.ts +7 -0
  28. package/dist/native/dashboard.js +32 -0
  29. package/dist/native/deadline.d.ts +2 -0
  30. package/dist/native/deadline.js +13 -0
  31. package/dist/native/events.d.ts +35 -0
  32. package/dist/native/events.js +308 -0
  33. package/dist/native/evidence.d.ts +3 -0
  34. package/dist/native/evidence.js +31 -0
  35. package/dist/native/functions.d.ts +10 -0
  36. package/dist/native/functions.js +131 -0
  37. package/dist/native/runtime.d.ts +17 -0
  38. package/dist/native/runtime.js +160 -0
  39. package/dist/native/validation.d.ts +107 -0
  40. package/dist/native/validation.js +84 -0
  41. package/dist/parser/assertion_contract.d.ts +12 -0
  42. package/dist/parser/assertion_contract.js +56 -0
  43. package/dist/parser/lexer.js +15 -4
  44. package/dist/parser/parser.js +15 -7
  45. package/dist/reporting/assertion_result.d.ts +36 -0
  46. package/dist/reporting/assertion_result.js +104 -0
  47. package/dist/reporting/pdf_generator.js +25 -20
  48. package/dist/reporting/runtime_failure.d.ts +10 -0
  49. package/dist/reporting/runtime_failure.js +64 -0
  50. package/dist/reporting/step_status.d.ts +10 -0
  51. package/dist/reporting/step_status.js +44 -0
  52. package/dist/runner/flow_orchestrator.d.ts +5 -0
  53. package/dist/runner/flow_orchestrator.js +30 -8
  54. package/dist/runner/flow_worker.d.ts +15 -1
  55. package/dist/runner/flow_worker.js +219 -42
  56. package/dist/runner/plugin_manager.js +3 -0
  57. package/dist/runner/temporary_store.d.ts +1 -0
  58. package/dist/runner/temporary_store.js +3 -0
  59. package/dist/sdk/api_proxy.d.ts +3 -0
  60. package/dist/sdk/api_proxy.js +16 -0
  61. package/dist/security/localWorkspace.d.ts +2 -0
  62. package/dist/security/localWorkspace.js +17 -0
  63. package/dist/security/redaction.js +1 -1
  64. package/dist/types/index.d.ts +13 -0
  65. package/examples/native-workflows/README.md +144 -0
  66. package/examples/native-workflows/api-and-ui/README.md +80 -0
  67. package/examples/native-workflows/api-and-ui/flows/api-and-ui.flow +27 -0
  68. package/examples/native-workflows/api-and-ui/negative/wrong-backend-state.flow +27 -0
  69. package/examples/native-workflows/api-ui-otp/README.md +140 -0
  70. package/examples/native-workflows/api-ui-otp/templates/api-ui-otp.flow +31 -0
  71. package/examples/native-workflows/api-ui-otp/templates/existing-account-otp.flow +20 -0
  72. package/examples/native-workflows/audio-to-text/README.md +131 -0
  73. package/examples/native-workflows/audio-to-text/fixtures/microphone-tone.wav +0 -0
  74. package/examples/native-workflows/audio-to-text/flows/microphone-demo.flow +23 -0
  75. package/examples/native-workflows/audio-to-text/negative/wrong-transcript.flow +23 -0
  76. package/examples/native-workflows/audio-to-text/templates/real-transcription.flow +25 -0
  77. package/examples/native-workflows/iframes/README.md +116 -0
  78. package/examples/native-workflows/iframes/flows/explicit-frame-plugin.flow +20 -0
  79. package/examples/native-workflows/iframes/flows/readable-frame.flow +13 -0
  80. package/examples/native-workflows/iframes/negative/wrong-frame-result.flow +13 -0
  81. package/examples/native-workflows/iframes/plugins/frame-actions.ts +7 -0
  82. package/examples/native-workflows/manual-testing/README.md +85 -0
  83. package/examples/native-workflows/manual-testing/fixtures/profile-review-mismatch.json +93 -0
  84. package/examples/native-workflows/manual-testing/fixtures/profile-review.json +93 -0
  85. package/examples/native-workflows/shared/server.mjs +80 -0
  86. package/examples/native-workflows/tables-and-cells/README.md +118 -0
  87. package/examples/native-workflows/tables-and-cells/flows/coordinates.flow +17 -0
  88. package/examples/native-workflows/tables-and-cells/flows/search-extract-reuse.flow +27 -0
  89. package/examples/native-workflows/tables-and-cells/negative/wrong-cell-value.flow +27 -0
  90. package/examples/native-workflows/variables-and-reuse/README.md +140 -0
  91. package/examples/native-workflows/variables-and-reuse/flows/reuse.flow +9 -0
  92. package/examples/native-workflows/variables-and-reuse/flows/setup.first.flow +27 -0
  93. package/examples/native-workflows/variables-and-reuse/negative/wrong-reused-value.flow +27 -0
  94. package/examples/native-workflows/variables-and-reuse/sessions/restore.flow +10 -0
  95. package/examples/native-workflows/variables-and-reuse/sessions/save.flow +29 -0
  96. package/package.json +23 -9
  97. package/src/ui/dist/assets/index-s0WYkGPt.js +636 -0
  98. package/src/ui/dist/index.html +1 -1
  99. package/src/ui/dist/assets/index-B7MuBTWm.js +0 -460
@@ -0,0 +1,381 @@
1
+ # Native browser workflows
2
+
3
+ For API/UI composition, email OTP, iframe examples, audio-to-text, tables, variables and manual testing, start with [Core Flowstride workflows](NATIVE_WORKFLOWS.md).
4
+
5
+ OS and Cloud support these commands directly in `.flow` files. Existing commands, plugins and saved sessions continue to work. Use `flow.<area>.<action>` followed by a JSON object. Unknown actions and options fail explicitly.
6
+
7
+ Keep both engine installations current when using these features. If an installed Cloud engine lacks the required capability protocol, the public runner warns and uses OS fallback for the run. Existing commands can still use an older compatible Cloud engine; Cloud-only services require a compatible Cloud installation.
8
+
9
+ ## Separate users in one test
10
+
11
+ A **profile** is an independent browser context: its cookies, local storage, session storage, pages and permissions are separate. The original profile is named `default`. Creating a profile selects it; switching profiles returns to the page that user was using.
12
+
13
+ ```flow
14
+ flow.browser.create {
15
+ "name": "customer",
16
+ "options": {
17
+ "locale": "en-GB",
18
+ "colorScheme": "dark"
19
+ }
20
+ };
21
+ flow.open "/login";
22
+ flow.type input "Email" "{{env.CUSTOMER_EMAIL}}";
23
+ flow.type input "Password" "{{env.CUSTOMER_PASSWORD}}";
24
+ flow.click button "Sign in";
25
+ flow.browser.create {
26
+ "name": "admin"
27
+ };
28
+ flow.open "/admin/login";
29
+ flow.browser.use {
30
+ "name": "customer"
31
+ };
32
+ ```
33
+
34
+ `flow.save session "customer" persist;` still saves a snapshot of the **currently selected** profile. `flow.use session "customer";` still injects a saved snapshot into the current profile. Saving a snapshot does not create another live user. Sequential customer/admin scenarios remain valid without profiles; use profiles when the users must stay independently logged in.
35
+
36
+ `flow.browser.close` closes a named profile. The `default` profile remains available until file completion. Profiles are reset between files. `flow.switchTo` still selects tabs within the current profile.
37
+
38
+ ## Browser selection and settings
39
+
40
+ ```sh
41
+ npx flowstride install chromium firefox webkit
42
+ npx flowstride run flows --browser firefox --ci
43
+ ```
44
+
45
+ Use `--browser firefox` or `--browser webkit` to select the test engine. The terminal prints the verified engine and version after launch; the dashboard shows **Test browser**, and CI's `summary.json` includes the actual engine for each file. `install` downloads browser binaries and does not open a browser window.
46
+
47
+ Interactive runs display the dashboard in a separate Firefox or WebKit window when either engine is selected. The dashboard remains in the system default browser for Chromium runs. Test pages run headlessly by default; add `--headed` to see the application's own browser window:
48
+
49
+ ```sh
50
+ npx flowstride run flows --browser firefox --headed
51
+ npx flowstride run flows --browser webkit --headed
52
+ ```
53
+
54
+ CI forces headless execution and opens no dashboard. Firefox and WebKit keep their native user-agent unless you explicitly configure `browserOptions.userAgent`. WebKit is Safari's engine, rather than the installed Safari application.
55
+
56
+ Chromium remains the default. Browser installation must match the installed Flowstride package. In Linux CI, add `--with-deps` to the install command. Browser support does not mean every operating-system/browser/device combination is qualified. Firefox does not support Playwright's mobile emulation setting; phone profiles use its viewport/touch settings without that flag. Chromium-specific game touch and audio-file injection still require Chromium.
57
+
58
+ ```json
59
+ {
60
+ "browser": "chromium",
61
+ "browserOptions": {
62
+ "locale": "en-GB",
63
+ "timezoneId": "Europe/London",
64
+ "colorScheme": "dark",
65
+ "permissions": ["geolocation"],
66
+ "geolocation": {
67
+ "latitude": 6.5244,
68
+ "longitude": 3.3792
69
+ },
70
+ "serviceWorkers": "block"
71
+ }
72
+ }
73
+ ```
74
+
75
+ Validated settings include viewport/screen, device scale, touch/mobile emulation, locale/timezone, color/contrast/motion, JavaScript, HTTPS handling, CSP bypass, permissions/geolocation, offline mode, headers, HTTP credentials, proxy, downloads, strict selectors, storage state and project-local client certificate paths. Use `browserOptions` for the default profile and `options` in `flow.browser.create` for a separate profile. Secrets belong in environment variables or private local files.
76
+
77
+ Set `BASE_URL` directly in a project `.env` file when you want a common application URL. String values in native `.flow` command payloads support environment templates. The JSON configuration loader does not generally expand `{{env.NAME}}` throughout arbitrary `browserOptions`; use supported native profile options for command-time credential interpolation.
78
+
79
+ Runtime commands include `flow.browser.offline` with `value`, `flow.browser.permissions` with `permissions` and optional `origin`, `flow.browser.clearPermissions`, and `flow.browser.geolocation` with latitude/longitude. Recording paths are controlled by evidence settings, rather than arbitrary browser path options. JavaScript logger callbacks are not JSON DSL settings.
80
+
81
+ ## Assertions that wait for a condition
82
+
83
+ ```flow
84
+ flow.assert.that {
85
+ "selector": "#order-status",
86
+ "condition": "text",
87
+ "value": "Approved",
88
+ "timeout": 10000
89
+ };
90
+ flow.assert.that {
91
+ "role": "button",
92
+ "name": "Submit",
93
+ "condition": "enabled"
94
+ };
95
+ flow.assert.that {
96
+ "selector": ".loading",
97
+ "condition": "visible",
98
+ "not": true
99
+ };
100
+ ```
101
+
102
+ Choose one CSS/Playwright `selector` or accessible `role` with an optional exact `name`. An optional `frame` selects an iframe. Conditions include:
103
+
104
+ | Conditions | Expected input |
105
+ | --- | --- |
106
+ | `visible`, `hidden`, `attached`, `enabled`, `disabled`, `editable`, `checked`, `empty`, `focused`, `inViewport` | No value |
107
+ | `text`, `containsText`, `value`, `id`, `accessibleName`, `accessibleDescription` | `value`, or `matches` and optional regex `flags` |
108
+ | `values` | Array of selected option values |
109
+ | `count` | Nonnegative integer `value` |
110
+ | `attribute` | `attribute`; optional expected `value`; omit it to check presence |
111
+ | `css`, `property` | `property` and expected `value` |
112
+ | `url`, `title` | Expected `value` or regex `matches`; no selector needed |
113
+ | `localStorage`, `sessionStorage` | `key` and expected string `value`, or `null` for absence |
114
+
115
+ Text comparisons normalize whitespace. Assertions retry until `timeout` or the configured default; they do not repeatedly click or repeat requests. Existing `flow.expect` syntax remains supported.
116
+
117
+ ## Mock requests and wait for network events
118
+
119
+ Use `serviceWorkers: "block"` when mocking an application that registers service workers. Mocking applies to browser traffic in the current profile; it does not intercept `flow.get` or other direct API commands.
120
+
121
+ ```flow
122
+ flow.network.route {
123
+ "name": "customer-response",
124
+ "url": "**/api/customer",
125
+ "method": "GET",
126
+ "json": {
127
+ "name": "Documentation Reader"
128
+ },
129
+ "times": 1
130
+ };
131
+ flow.network.arm {
132
+ "name": "customer-request",
133
+ "url": "**/api/customer",
134
+ "kind": "response"
135
+ };
136
+ flow.click button "Load customer";
137
+ flow.network.wait {
138
+ "name": "customer-request"
139
+ };
140
+ flow.network.expect {
141
+ "name": "customer-request",
142
+ "status": 200,
143
+ "path": "name",
144
+ "equals": "Documentation Reader"
145
+ };
146
+ ```
147
+
148
+ **Arm before the trigger, then wait.** This captures fast responses and prevents an unobserved timeout while a click is still finishing. Waiters are cleaned up on retry, file completion and shutdown.
149
+
150
+ Routes accept one of `json`, text `body`, or a project-local `fixture`. Optional `status`, `headers`, `contentType`, `method` and positive `times` control the response. URL patterns support `*` within a path segment and `**` across segments.
151
+
152
+ | Route `mode` | Behavior |
153
+ | --- | --- |
154
+ | `fulfill` (default) | Supply the configured response |
155
+ | `abort` | Block the matching request |
156
+ | `continue` | Send the real request; optional `request` changes URL, method, headers or body |
157
+ | `patch` | Fetch the real response and merge configured JSON fields or replace its text body |
158
+
159
+ Remove a route with `flow.network.unroute` and its `name`. Rules and captured events belong to the selected profile. `network.arm` also supports `kind: "request"`, a method, response status filter and timeout. `network.expect` checks status/method and captured body paths or a response/request `header`, using `equals` or `contains`.
160
+
161
+ ## Downloads
162
+
163
+ ```flow
164
+ flow.download.arm {
165
+ "name": "invoice"
166
+ };
167
+ flow.click link "Download invoice";
168
+ flow.download.save {
169
+ "name": "invoice",
170
+ "file": "downloads/invoice.json"
171
+ };
172
+ flow.download.expect {
173
+ "name": "invoice",
174
+ "filename": "invoice.json",
175
+ "minSize": 1,
176
+ "json": {
177
+ "paid": true
178
+ }
179
+ };
180
+ ```
181
+
182
+ The destination must stay inside the project and must not already exist. Assertions support the suggested `filename`, exact `size`, `minSize`, text `contains`, exact `json`, and a SHA-256 `sha256`. Content assertions have a size limit; use size/hash checks for larger downloads. Downloads do not need a custom plugin.
183
+
184
+ ## Control time and observe functions
185
+
186
+ ```flow
187
+ flow.clock.install {
188
+ "time": "2026-01-01T00:00:00Z"
189
+ };
190
+ flow.open "/timer";
191
+ flow.clock.runFor {
192
+ "milliseconds": 5000
193
+ };
194
+ ```
195
+
196
+ Clock commands include `pauseAt`, `resume`, `runFor`, `fastForward`, `setFixedTime` and `setSystemTime`. Date operations accept `time`; advancing accepts `milliseconds`. Install the clock before the application installs its timers. Time control affects the browser page, not the backend or operating-system clock.
197
+
198
+ ```flow
199
+ flow.stub.install {
200
+ "name": "analytics",
201
+ "target": "analytics.track",
202
+ "mode": "spy"
203
+ };
204
+ flow.click button "Purchase";
205
+ flow.stub.expect {
206
+ "name": "analytics",
207
+ "count": 1,
208
+ "args": ["purchase"]
209
+ };
210
+ flow.stub.restore {
211
+ "name": "analytics"
212
+ };
213
+ ```
214
+
215
+ A spy records calls and invokes the original function. Modes `return` and `throw` replace it with a configured `value` or `error`; `async: true` returns a promise. `beforeLoad: true` installs on the next document, for functions available before application initialization. It cannot replace a function that does not yet exist. `stub.expect` accepts `count`, `minCount`, arguments and timeout. Doubles belong to a page and are restored on cleanup.
216
+
217
+ ## Traces, HAR and DOM evidence
218
+
219
+ ```json
220
+ {
221
+ "trace": true,
222
+ "har": true,
223
+ "domSnapshots": true,
224
+ "browserOptions": {
225
+ "serviceWorkers": "block"
226
+ }
227
+ }
228
+ ```
229
+
230
+ Trace ZIPs include browser actions, DOM snapshots and screenshots. HAR files capture HTTP requests and responses and finalize when their browser context closes. Evidence files include flow/profile names; retries retain earlier attempt videos/traces. A trace records browser operations; Flowstride assertion results and attempts are in `summary.json`.
231
+
232
+ Use `flow.trace.start` (optional `name`) and `flow.trace.stop` for an explicitly bounded trace instead of automatic `trace: true`. Open a local ZIP with:
233
+
234
+ ```sh
235
+ npx flowstride trace flowstride-artifacts/example-trace.zip
236
+ ```
237
+
238
+ HAR replay uses a local recorded file:
239
+
240
+ ```flow
241
+ flow.har.replay {
242
+ "file": "fixtures/customer.har",
243
+ "notFound": "abort"
244
+ };
245
+ flow.open "/customer";
246
+ ```
247
+
248
+ An optional `url` limits matching; `notFound: "fallback"` allows unmatched real requests. Replay does not restore application databases or a WebSocket session.
249
+
250
+ The dashboard preserves each step's profile and attempt, lets you choose earlier attempt screenshots, and links to optional DOM snapshots. DOM files are served as inert text. They do not rewind the live application or execute an earlier page.
251
+
252
+ Traces, HAR, downloaded files and DOM/accessibility snapshots can contain application secrets or personal data. They remain local artifacts; these new formats are not automatically uploaded to Cloud. Choose private CI artifacts and an appropriate retention period.
253
+
254
+ ## Accessibility and ARIA baselines
255
+
256
+ ```flow
257
+ flow.a11y.scan {
258
+ "name": "checkout",
259
+ "include": ["main"],
260
+ "tags": ["wcag2a", "wcag2aa"]
261
+ };
262
+ flow.a11y.snapshot {
263
+ "name": "checkout",
264
+ "selector": "main",
265
+ "baseline": "baselines/checkout.aria.yml"
266
+ };
267
+ ```
268
+
269
+ Scans use axe, write a structured report, and fail on violations by default. Options include `exclude`, explicit `rules`, `disableRules`, selected `impacts`, and `maxViolations`. Automated scans cover only part of accessibility; keyboard and assistive-technology review remain necessary.
270
+
271
+ Snapshots compare the complete selected accessibility tree with a reviewed YAML baseline. `flow.a11y.match` compares an inline `yaml` string with the complete captured tree and retries until timeout. These are exact tree comparisons, not Playwright Test's partial snapshot matcher.
272
+
273
+ Create or deliberately update a baseline locally with `--update-baselines`; the snapshot command must specify `baseline`. CI refuses updates. Review baseline changes before accepting them. To capture evidence without comparing or writing a baseline, omit `baseline`.
274
+
275
+ ## Component tests
276
+
277
+ Flowstride starts and cleans up a local Vite harness. Keep your normal application framework packages installed in the project. Supported integrations are vanilla JavaScript factories, React components and Vue single-file components. Other frameworks need a dedicated integration and qualification.
278
+
279
+ ```flow
280
+ flow.component.mount {
281
+ "framework": "react",
282
+ "module": "src/components/AccountButton.tsx",
283
+ "export": "default",
284
+ "props": {
285
+ "label": "Open account"
286
+ }
287
+ };
288
+ flow.click button "Open account";
289
+ flow.assert.that {
290
+ "selector": "button",
291
+ "condition": "text",
292
+ "value": "Account opened"
293
+ };
294
+ flow.component.update {
295
+ "props": {
296
+ "label": "Updated account"
297
+ }
298
+ };
299
+ flow.component.unmount {};
300
+ ```
301
+
302
+ An optional `css` array imports project styles. Supply required application providers through a small test component wrapper. The harness does not load your application Vite configuration, framework router, backend or production build automatically. Vanilla exports receive `(target, props)` and can return a DOM node or an object with `unmount()`.
303
+
304
+ ## Scenario retries and flaky outcomes
305
+
306
+ ```json
307
+ {
308
+ "retries": 1,
309
+ "failOnFlaky": true
310
+ }
311
+ ```
312
+
313
+ Retries are disabled by default. A retry reruns the entire failed scenario, restores its starting browser profiles/storage and local variables, and retains previous attempt diagnostics. A later pass is marked `flaky`; `failOnFlaky` turns it into a failing CI outcome. Bail takes effect after the final failed attempt.
314
+
315
+ Retries cannot undo backend writes, emails, payments, persistent files, or other external effects. Use idempotent test data and explicit application fixtures. Keep retries low while investigating unstable tests.
316
+
317
+ ## Browser matrices and multiple CI machines
318
+
319
+ ```sh
320
+ npx flowstride ci init github --browsers chromium,firefox,webkit --shards 3
321
+ ```
322
+
323
+ The generated workflow creates separate CI jobs with unique evidence names. It uses normal CI runners. Review project dependency/secrets setup as usual. Cancellation stops that job and records an interrupted result; it does not delete other machines' evidence.
324
+
325
+ ```sh
326
+ npx flowstride run flows --ci --shard 1/3
327
+ npx flowstride run flows --ci --shard 2/3
328
+ npx flowstride run flows --ci --shard 3/3
329
+ ```
330
+
331
+ Run each command on its assigned machine. Preserve its `summary.json` beside its `run-*` evidence folder. Collect all shards for **one browser configuration** into separate project directories and merge:
332
+
333
+ ```sh
334
+ npx flowstride merge shard-1/summary.json shard-2/summary.json shard-3/summary.json
335
+ ```
336
+
337
+ Merging rejects missing/duplicate shards, mixed source suites and mixed browser configurations. A failed or interrupted shard cannot become a passing merge. Setup `.first.flow` files execute independently on every machine, so setup must be safe to repeat. Metrics count actual shard executions, including repeated setup work.
338
+
339
+ For later runs, use `--shard-history path/to/timings.json` from a previous complete merge. Every machine must use the same file: history assigns longer files first while preserving deterministic, disjoint standard-file coverage. There is no hosted scheduling service or shared in-memory state between machines.
340
+
341
+ ## Browser compatibility notes
342
+
343
+ Chromium's current stealth behavior can override an explicitly requested locale. Use Firefox or WebKit for locale-sensitive checks and verify the observed language. For HAR playback in Firefox/WebKit, leave forced browser-offline mode unset and stop the real server to verify the recording; combining forced offline mode and HAR can reject navigation before fulfillment.
344
+
345
+ The maintained command reference, browser compatibility guide and API-to-UI examples are available in the [Flowstride documentation](https://docs.flowstride.io/guide/native-browser-workflows).
346
+
347
+ ## Assertion results and `And`
348
+
349
+ Native checks `flow.assert.that`, `flow.network.expect`, `flow.download.expect`,
350
+ `flow.stub.expect`, `flow.a11y.scan` and `flow.a11y.match` can appear under
351
+ `Then` or `And`. State-changing browser, route and clock commands belong under
352
+ `Given` or `When`.
353
+
354
+ ```flow
355
+ Then "The login button is ready"
356
+ flow.assert.that {
357
+ "role": "button",
358
+ "name": "Submit",
359
+ "condition": "enabled",
360
+ "timeout": 10000
361
+ };
362
+ And "The heading is visible"
363
+ flow.assert.that {
364
+ "role": "heading",
365
+ "name": "Welcome",
366
+ "condition": "visible"
367
+ };
368
+ ```
369
+
370
+ Terminal, CI JSON/JUnit and dashboard evidence retain individual lookup and
371
+ condition results. A failing condition does not erase successful target checks.
372
+ Missing prerequisites and later steps are marked not evaluated / not tested.
373
+ Expected and observed values, timeout, profile and source location explain a
374
+ failure. These child diagnostics do not count as extra test steps.
375
+
376
+ All command failures retain the original adapter error, call log and nested
377
+ causes, plus command, file/line and relevant target/resource. This applies to
378
+ browser actions, APIs, plugins, sessions, games, audio and email workflows.
379
+ Timeouts are reported as observed timeouts, without inventing a root cause when
380
+ the application does not provide one. Secrets and terminal control characters
381
+ are protected in diagnostics.
package/NATIVE_CI.md CHANGED
@@ -12,9 +12,9 @@ npx flowstride install
12
12
  npx flowstride run flows --ci
13
13
  ```
14
14
 
15
- In CI, use `npm ci` before browser installation and test execution. Browser installation uses the Chromium version matching the installed Flowstride engine. Linux runners may require system browser dependencies; the generated GitHub workflow handles installation for its runner.
15
+ In CI, use `npm ci` before browser installation and test execution. Browser installation uses the Chromium version matching the installed Flowstride engine by default. See [native browser workflows](NATIVE_BROWSER.md) for Firefox, WebKit and browser configuration. Linux runners may require system browser dependencies; the generated GitHub workflow handles installation for its runner.
16
16
 
17
- Eligible Pro users authenticate using `npx flowstride login`. In CI, supply the developer token through the `FLOWSTRIDE_TOKEN` secret, never in committed files. Install project dependencies before login, then install the matching browser and run the tests. The OS CLI uses compatible, authorized Pro installations and falls back to OS when Pro is unavailable.
17
+ Cloud users authenticate using `npx flowstride login`. In CI, supply the developer token through the `FLOWSTRIDE_TOKEN` secret, never in committed files. Install project dependencies before login, then install the matching browser and run the tests. The OS CLI uses compatible, authorized Cloud engine installations and falls back to OS when the Cloud engine is unavailable.
18
18
 
19
19
  Generate a GitHub Actions workflow explicitly:
20
20
 
@@ -32,7 +32,7 @@ Malformed configuration and unsafe output paths fail before execution. Configura
32
32
 
33
33
  Native CI is selected by `--ci` or a recognized CI environment. It runs headlessly without a dashboard server and returns nonzero for failed, incomplete, crashed or interrupted execution. JSON and JUnit reports are written at the configured artifact root, which defaults to `flowstride-artifacts`. Media and attachments use a unique run directory referenced by the summary.
34
34
 
35
- Flow discovery is recursive. `.first.flow` files run before ordinary files. A failed scenario leaves its remaining steps untested and execution continues to later scenarios. `--bail` stops later work; Pro executes bail runs sequentially.
35
+ Flow discovery is recursive. `.first.flow` files run before ordinary files. A failed scenario leaves its remaining steps untested and execution continues to later scenarios. `--bail` stops later work; the Cloud engine executes bail runs sequentially.
36
36
 
37
37
  An optional configured local web server is started and stopped with the run. Flowstride rejects a readiness URL that is already serving.
38
38
 
@@ -85,9 +85,9 @@ flow.use session "browser";
85
85
 
86
86
  Attribute comparisons wait up to the configured timeout. Exact matching is case-sensitive; `contains` checks a substring. Missing attributes fail even when the expected value is empty.
87
87
 
88
- Sessions capture cookies, local storage, session storage and the current HTTP(S) storage origin. Session storage is restored immediately on the matching origin or before its first matching navigation. It is not seeded into unrelated origins. The initial seed is then removed so later application changes and reloads are preserved.
88
+ Sessions capture cookies, local storage, session storage and the current HTTP(S) storage origin. Local storage and session storage are restored immediately on the matching origin or before its first matching navigation. It is not seeded into unrelated origins. The initial seed is then removed so later application changes and reloads are preserved.
89
89
 
90
- Existing cookie/local-storage snapshots remain compatible. Older snapshots with session storage but no origin use the current application origin or configured `baseUrl`; open the application first or set `baseUrl` if neither is available. Restoration merges saved keys into storage. This is a single-origin storage snapshot.
90
+ Existing cookie/local-storage snapshots remain compatible. Older snapshots with Web Storage but no origin prefer the configured application `baseUrl` (including `BASE_URL`), then an available current HTTP(S) application page. Configure the intended base URL before restoring a legacy record. Restoration merges saved keys into storage. This is a single-origin storage snapshot.
91
91
 
92
92
  ## Interactive history and debugging
93
93
 
@@ -96,3 +96,15 @@ Each completed flow tab restores its own last captured screen. Selecting an earl
96
96
  The local cURL preview remains `[REDACTED]`. Copy uses the original captured request held in interactive debugger memory. Already-redacted historical data cannot be recovered. CI and Cloud reports retain redaction.
97
97
 
98
98
  Review artifact access and test data: credential redaction does not remove every possible application secret, and screenshots or videos can show sensitive information.
99
+
100
+ ## Runnable core workflow examples
101
+
102
+ [Core Flowstride workflows](NATIVE_WORKFLOWS.md) covers API + UI, email OTP, iFrames, audio/transcripts, tables/cells, variables and manual testing. The [shared setup](examples/native-workflows/README.md) explains the local demo application's lifecycle and CI configuration.
103
+
104
+ After `npm ci` and matching browser installation, a CI job can run a named local example with:
105
+
106
+ ```sh
107
+ npx --no-install flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow --ci
108
+ ```
109
+
110
+ In CI, `--no-install` makes a missing local Flowstride installation fail rather than fetching a package. Ordinary interactive commands use `npx flowstride`. Configure the local app through `webServer`, and exclude `negative/` examples and unconfigured service templates from your passing CI target.
@@ -0,0 +1,44 @@
1
+ # Core Flowstride workflows
2
+
3
+ Flowstride can call an API, extract data from its response, use that data in a browser, and verify the application outcome in the same `.flow` test. It also supports table/cell targeting, browser sessions, microphone input and visual manual testing.
4
+
5
+ The examples below use the normal Flowstride CLI. Their small HTTP server is a local demonstration application; it does not replace the test runner.
6
+
7
+ ## Choose a workflow
8
+
9
+ | Workflow | What you will do | Availability |
10
+ | --- | --- | --- |
11
+ | [API + UI in one test](examples/native-workflows/api-and-ui/README.md) | Create a user through an API, sign in through the browser, then verify backend state | OS and Cloud; Chromium, Firefox, WebKit |
12
+ | [API + UI + email OTP](examples/native-workflows/api-ui-otp/README.md) | Use FlowMail to retrieve a delivered code and complete application authentication | Cloud; a supported inbox and an application that sends OTP email |
13
+ | [iFrames](examples/native-workflows/iframes/README.md) | Find a readable child-frame target, assert inside an explicit frame, and extend an action with a focused plugin | OS and Cloud; Chromium, Firefox, WebKit |
14
+ | [Audio-to-text workflows](examples/native-workflows/audio-to-text/README.md) | Inject microphone audio, drive recording controls and check the resulting transcript | OS and Cloud; audio injection requires Chromium |
15
+ | [Tables and cells](examples/native-workflows/tables-and-cells/README.md) | Search a table, click a specific row action, extract a cell and reuse its value on another page | OS and Cloud; Chromium, Firefox, WebKit |
16
+ | [Variables, reuse and saved sessions](examples/native-workflows/variables-and-reuse/README.md) | Reuse generated/extracted values, understand scope, and restore saved browser state | OS and Cloud; Chromium, Firefox, WebKit |
17
+ | [Manual testing](examples/native-workflows/manual-testing/README.md) | Load a design, execute its steps, record defects/results and reopen saved history | OS and Cloud; local Manual Workspace |
18
+
19
+ Start with the [shared setup](examples/native-workflows/README.md). It installs Flowstride with `npm i flowstride`, copies the examples already included in the package, and starts a loopback-only demo application. No separate starter download is required.
20
+
21
+ ## OS and Cloud
22
+
23
+ OS works locally without an account. To connect Cloud, run:
24
+
25
+ ```sh
26
+ npx flowstride login
27
+ ```
28
+
29
+ Use your Cloud developer token when prompted and accept the authenticated engine installation when offered. Cloud has free accounts; availability and usage allowances depend on the workspace feature and plan. Email OTP retrieval uses the Cloud FlowMail service. The other local examples do not need a Cloud service connection.
30
+
31
+ The public CLI selects a compatible, authorized Cloud engine when available and retains OS fallback. A Cloud-only command still requires its Cloud service prerequisites.
32
+
33
+ ## Reading and running the examples
34
+
35
+ - `flows/` contains runnable examples for the shared demo application.
36
+ - `negative/` contains deliberate failures. Run them individually and expect a nonzero exit code.
37
+ - `templates/` contains application-specific workflows. Configure the documented application, credentials and selectors before using them.
38
+ - Manual designs are JSON files; a human performs their actions and records the result.
39
+
40
+ Run the named file from its chapter. Running the whole example tree also discovers intentional failures and service-dependent templates.
41
+
42
+ Each chapter explains its prerequisites, expected result, negative check and limitations. For browser profiles, native assertions, network mocking, downloads, traces/HAR, accessibility and component testing, use [NATIVE_BROWSER.md](NATIVE_BROWSER.md). For CI, reports and games, use [NATIVE_CI.md](NATIVE_CI.md).
43
+
44
+ The maintained full reference is at [docs.flowstride.io](https://docs.flowstride.io/).
package/README.md CHANGED
@@ -7,7 +7,7 @@ Flowstride OS is an open source framework for browser, API and manual testing. W
7
7
  ## Requirements
8
8
 
9
9
  - Node.js 22.12 or newer.
10
- - Chromium installed through Flowstride for browser tests.
10
+ - A matching Chromium, Firefox or WebKit installation for browser tests. Chromium is the default; audio-file microphone injection requires Chromium.
11
11
 
12
12
  ## Get started
13
13
 
@@ -41,6 +41,13 @@ Then "The sign-in button is visible"
41
41
 
42
42
  Replace the URL and assertion with values from your own application.
43
43
 
44
+ ## Workflow guides
45
+
46
+ - [Core workflows](./NATIVE_WORKFLOWS.md): API + UI, email OTP, iFrames, audio-to-text, tables/cells, variables and manual testing.
47
+ - [Runnable examples and local setup](./examples/native-workflows/README.md).
48
+ - [Native browser workflows](./NATIVE_BROWSER.md): profiles, assertions, network control, downloads, traces/HAR, accessibility and components.
49
+ - [Native CI and games](./NATIVE_CI.md).
50
+
44
51
  ## Native CI
45
52
 
46
53
  ```sh
@@ -67,15 +74,15 @@ For visual manual test design and execution:
67
74
  npx flowstride manual
68
75
  ```
69
76
 
70
- ## Flowstride Pro
77
+ ## Flowstride Cloud
71
78
 
72
- Eligible Flowstride Cloud users install Pro through the authenticated private channel:
79
+ Flowstride Cloud users authenticate through the private installation channel:
73
80
 
74
81
  ```sh
75
82
  npx flowstride login
76
83
  ```
77
84
 
78
- The OS CLI selects a compatible, authorized Pro engine when available and falls back to OS when Pro cannot be used. Pro is distributed privately.
85
+ The OS CLI selects a compatible, authorized Cloud engine when available and falls back to OS when the Cloud engine cannot be used. The Cloud engine is distributed privately.
79
86
 
80
87
  ## Test data and artifacts
81
88
 
@@ -9,14 +9,24 @@ export declare class ApiAdapter {
9
9
  private config;
10
10
  private globalHeaders;
11
11
  private onUnauthorized?;
12
+ private unauthorizedStatus?;
13
+ private refreshing;
12
14
  private lastStatus;
13
15
  private lastBody;
14
16
  private lastHeaders;
15
17
  private lastResponseTime;
16
18
  constructor(config: FlowstrideConfig);
17
19
  setGlobalHeader(name: string, value: string): void;
18
- setUnauthorizedHandler(handler: () => Promise<void>): void;
20
+ setUnauthorizedHandler(handler: () => Promise<void>, status?: number): void;
19
21
  getState(): ApiState;
22
+ checkpointState(): {
23
+ globalHeaders: Record<string, string>;
24
+ status: number | null;
25
+ body: any;
26
+ headers: Record<string, string | string[] | undefined> | null;
27
+ responseTime: number;
28
+ };
29
+ restoreState(snapshot: ReturnType<ApiAdapter['checkpointState']>): void;
20
30
  resetState(): void;
21
31
  get(path: string, headers?: {
22
32
  key: string;
@@ -6,6 +6,8 @@ class ApiAdapter {
6
6
  config;
7
7
  globalHeaders = {};
8
8
  onUnauthorized;
9
+ unauthorizedStatus;
10
+ refreshing = false;
9
11
  lastStatus = null;
10
12
  lastBody = null;
11
13
  lastHeaders = null;
@@ -16,8 +18,12 @@ class ApiAdapter {
16
18
  setGlobalHeader(name, value) {
17
19
  this.globalHeaders[name] = value;
18
20
  }
19
- setUnauthorizedHandler(handler) {
21
+ setUnauthorizedHandler(handler, status) {
22
+ if (status !== undefined && status !== 401 && status !== 403) {
23
+ throw new Error('Authentication refresh status must be 401 or 403.');
24
+ }
20
25
  this.onUnauthorized = handler;
26
+ this.unauthorizedStatus = status;
21
27
  }
22
28
  getState() {
23
29
  if (this.lastStatus === null) {
@@ -30,6 +36,17 @@ class ApiAdapter {
30
36
  responseTime: this.lastResponseTime,
31
37
  };
32
38
  }
39
+ checkpointState() {
40
+ return structuredClone({ globalHeaders: this.globalHeaders, status: this.lastStatus, body: this.lastBody, headers: this.lastHeaders, responseTime: this.lastResponseTime });
41
+ }
42
+ restoreState(snapshot) {
43
+ const data = structuredClone(snapshot);
44
+ this.globalHeaders = data.globalHeaders;
45
+ this.lastStatus = data.status;
46
+ this.lastBody = data.body;
47
+ this.lastHeaders = data.headers;
48
+ this.lastResponseTime = data.responseTime;
49
+ }
33
50
  resetState() {
34
51
  this.lastStatus = null;
35
52
  this.lastBody = null;
@@ -104,8 +121,16 @@ class ApiAdapter {
104
121
  };
105
122
  };
106
123
  let res = await makeRequest();
107
- if ((res.status === 401 || res.status === 403) && this.onUnauthorized) {
108
- await this.onUnauthorized();
124
+ if ((res.status === 401 || res.status === 403) && this.onUnauthorized &&
125
+ !this.refreshing && (this.unauthorizedStatus === undefined || res.status === this.unauthorizedStatus)) {
126
+ // A refresh can use this adapter too. Never recursively refresh its own rejection.
127
+ this.refreshing = true;
128
+ try {
129
+ await this.onUnauthorized();
130
+ }
131
+ finally {
132
+ this.refreshing = false;
133
+ }
109
134
  Object.assign(requestHeaders, this.globalHeaders);
110
135
  res = await makeRequest();
111
136
  }
@@ -1,17 +1,37 @@
1
- import { Page } from "playwright";
1
+ import { BrowserContext, Page } from "playwright";
2
2
  import { FlowstrideConfig } from "../../types";
3
3
  export declare class WebAdapter {
4
4
  private config;
5
+ activeProfile: string;
6
+ private profiles;
7
+ private contextOptions;
8
+ onPageChanged?: () => Promise<void>;
9
+ profileNames(): string[];
10
+ getContext(): BrowserContext;
11
+ createProfile(profileName: string, options?: unknown): Promise<void>;
12
+ useProfile(profileName: string): Promise<void>;
13
+ closeProfile(profileName: string): Promise<void>;
14
+ beginTrace(profileName?: string, file?: string): Promise<void>;
15
+ endTrace(profileName?: string): Promise<string>;
16
+ private finishProfile;
17
+ checkpointProfiles(): Promise<any>;
18
+ restoreProfiles(snapshot: any): Promise<void>;
5
19
  pageErrors: string[];
6
20
  private browser;
7
21
  private context;
8
22
  private page;
9
23
  private discoveredPages;
10
24
  private dialogHandler;
11
- private pendingSessionStorageRestore;
25
+ private sessionStorageRestores;
26
+ private get pendingSessionStorageRestore();
12
27
  private clearSessionStorageRestore;
13
28
  constructor(config: FlowstrideConfig);
14
29
  getPage(): Page;
30
+ getBrowserInfo(): {
31
+ engine: string;
32
+ version: string;
33
+ headless: boolean;
34
+ };
15
35
  initialise(audioInjectionPath?: string): Promise<void>;
16
36
  acceptDialog(): Promise<void>;
17
37
  rejectDialog(): Promise<void>;