flowstride 1.0.21 → 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 (81) hide show
  1. package/NATIVE_BROWSER.md +40 -0
  2. package/NATIVE_CI.md +14 -2
  3. package/NATIVE_WORKFLOWS.md +44 -0
  4. package/README.md +8 -1
  5. package/dist/adapters/api/undici.d.ts +3 -1
  6. package/dist/adapters/api/undici.js +17 -3
  7. package/dist/adapters/web/playwright.js +23 -73
  8. package/dist/ci/runtime.js +9 -1
  9. package/dist/cli/commands/run.js +14 -4
  10. package/dist/cli/manual_server/workspace_server.js +90 -74
  11. package/dist/manual/security.d.ts +4 -4
  12. package/dist/native/accessibility.d.ts +2 -1
  13. package/dist/native/accessibility.js +22 -6
  14. package/dist/native/assertions.d.ts +2 -1
  15. package/dist/native/assertions.js +174 -91
  16. package/dist/native/deadline.d.ts +2 -0
  17. package/dist/native/deadline.js +13 -0
  18. package/dist/native/events.d.ts +3 -2
  19. package/dist/native/events.js +35 -8
  20. package/dist/native/functions.d.ts +2 -1
  21. package/dist/native/functions.js +34 -6
  22. package/dist/native/runtime.d.ts +3 -0
  23. package/dist/native/runtime.js +12 -6
  24. package/dist/parser/assertion_contract.d.ts +12 -0
  25. package/dist/parser/assertion_contract.js +56 -0
  26. package/dist/parser/parser.js +14 -6
  27. package/dist/reporting/assertion_result.d.ts +36 -0
  28. package/dist/reporting/assertion_result.js +104 -0
  29. package/dist/reporting/pdf_generator.js +25 -20
  30. package/dist/reporting/runtime_failure.d.ts +10 -0
  31. package/dist/reporting/runtime_failure.js +64 -0
  32. package/dist/reporting/step_status.d.ts +10 -0
  33. package/dist/reporting/step_status.js +44 -0
  34. package/dist/runner/flow_orchestrator.d.ts +1 -0
  35. package/dist/runner/flow_orchestrator.js +8 -3
  36. package/dist/runner/flow_worker.d.ts +8 -1
  37. package/dist/runner/flow_worker.js +119 -41
  38. package/dist/runner/plugin_manager.js +3 -0
  39. package/dist/runner/temporary_store.d.ts +1 -0
  40. package/dist/runner/temporary_store.js +3 -0
  41. package/dist/sdk/api_proxy.d.ts +3 -0
  42. package/dist/sdk/api_proxy.js +16 -0
  43. package/dist/security/localWorkspace.d.ts +2 -0
  44. package/dist/security/localWorkspace.js +17 -0
  45. package/dist/security/redaction.js +1 -1
  46. package/dist/types/index.d.ts +3 -0
  47. package/examples/native-workflows/README.md +144 -0
  48. package/examples/native-workflows/api-and-ui/README.md +80 -0
  49. package/examples/native-workflows/api-and-ui/flows/api-and-ui.flow +27 -0
  50. package/examples/native-workflows/api-and-ui/negative/wrong-backend-state.flow +27 -0
  51. package/examples/native-workflows/api-ui-otp/README.md +140 -0
  52. package/examples/native-workflows/api-ui-otp/templates/api-ui-otp.flow +31 -0
  53. package/examples/native-workflows/api-ui-otp/templates/existing-account-otp.flow +20 -0
  54. package/examples/native-workflows/audio-to-text/README.md +131 -0
  55. package/examples/native-workflows/audio-to-text/fixtures/microphone-tone.wav +0 -0
  56. package/examples/native-workflows/audio-to-text/flows/microphone-demo.flow +23 -0
  57. package/examples/native-workflows/audio-to-text/negative/wrong-transcript.flow +23 -0
  58. package/examples/native-workflows/audio-to-text/templates/real-transcription.flow +25 -0
  59. package/examples/native-workflows/iframes/README.md +116 -0
  60. package/examples/native-workflows/iframes/flows/explicit-frame-plugin.flow +20 -0
  61. package/examples/native-workflows/iframes/flows/readable-frame.flow +13 -0
  62. package/examples/native-workflows/iframes/negative/wrong-frame-result.flow +13 -0
  63. package/examples/native-workflows/iframes/plugins/frame-actions.ts +7 -0
  64. package/examples/native-workflows/manual-testing/README.md +85 -0
  65. package/examples/native-workflows/manual-testing/fixtures/profile-review-mismatch.json +93 -0
  66. package/examples/native-workflows/manual-testing/fixtures/profile-review.json +93 -0
  67. package/examples/native-workflows/shared/server.mjs +80 -0
  68. package/examples/native-workflows/tables-and-cells/README.md +118 -0
  69. package/examples/native-workflows/tables-and-cells/flows/coordinates.flow +17 -0
  70. package/examples/native-workflows/tables-and-cells/flows/search-extract-reuse.flow +27 -0
  71. package/examples/native-workflows/tables-and-cells/negative/wrong-cell-value.flow +27 -0
  72. package/examples/native-workflows/variables-and-reuse/README.md +140 -0
  73. package/examples/native-workflows/variables-and-reuse/flows/reuse.flow +9 -0
  74. package/examples/native-workflows/variables-and-reuse/flows/setup.first.flow +27 -0
  75. package/examples/native-workflows/variables-and-reuse/negative/wrong-reused-value.flow +27 -0
  76. package/examples/native-workflows/variables-and-reuse/sessions/restore.flow +10 -0
  77. package/examples/native-workflows/variables-and-reuse/sessions/save.flow +29 -0
  78. package/package.json +11 -8
  79. package/src/ui/dist/assets/index-s0WYkGPt.js +636 -0
  80. package/src/ui/dist/index.html +1 -1
  81. package/src/ui/dist/assets/index-DglsL_J2.js +0 -636
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLocalWorkspaceRequest = isLocalWorkspaceRequest;
4
+ const localDashboard_1 = require("./localDashboard");
5
+ /** Bind the manual workspace browser boundary to the actual loopback listener. */
6
+ function isLocalWorkspaceRequest(host, origin, localPort, fetchSite) {
7
+ if (!Number.isInteger(localPort) || !localPort || localPort < 1 || localPort > 65535)
8
+ return false;
9
+ if (!(0, localDashboard_1.isLocalDashboardRequest)(host, origin) || fetchSite === 'cross-site')
10
+ return false;
11
+ try {
12
+ return Number(new URL(`http://${host}`).port) === localPort;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
@@ -33,7 +33,7 @@ function redactPayload(value, secrets = [], depth = 0) {
33
33
  return value.map(item => redactPayload(item, secrets, depth + 1));
34
34
  if (!value || typeof value !== 'object')
35
35
  return value;
36
- if (Buffer.isBuffer(value))
36
+ if (globalThis.Buffer?.isBuffer(value))
37
37
  return '[binary]';
38
38
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, sensitive.test(key) ? '[REDACTED]' : redactPayload(item, secrets, depth + 1)]));
39
39
  }
@@ -105,6 +105,7 @@ export interface FlowstrideSDK {
105
105
  get: (url: string, headers?: Record<string, string>) => Promise<any>;
106
106
  post: (url: string, body: any, headers?: Record<string, string>) => Promise<any>;
107
107
  put: (url: string, body: any, headers?: Record<string, string>) => Promise<any>;
108
+ patch: (url: string, body: any, headers?: Record<string, string>) => Promise<any>;
108
109
  delete: (url: string, headers?: Record<string, string>) => Promise<any>;
109
110
  };
110
111
  ui: {
@@ -116,6 +117,8 @@ export interface FlowstrideSDK {
116
117
  state: {
117
118
  get: (key: string) => any;
118
119
  set: (key: string, value: any) => void;
120
+ delete: (key: string) => void;
121
+ clear: () => void;
119
122
  };
120
123
  env: {
121
124
  get: (key: string) => string | undefined;
@@ -0,0 +1,144 @@
1
+ # Run the core workflow examples
2
+
3
+ These examples accompany [Core Flowstride workflows](../../NATIVE_WORKFLOWS.md). The shared application runs on your computer and stores disposable data in memory. It sends no email and performs no speech recognition. The email-OTP and real-transcription templates explain how to use your own authorized application and services.
4
+
5
+ ## 1. Install through npm
6
+
7
+ Use Node.js 22.12 or newer. In a new project:
8
+
9
+ ```sh
10
+ mkdir flowstride-workflows
11
+ cd flowstride-workflows
12
+ npm init -y
13
+ npm i flowstride
14
+ npx flowstride init
15
+ npx flowstride install chromium
16
+ ```
17
+
18
+ `init` creates the workspace. The explicit browser-install command installs the Chromium build matching the selected engine. The chapters below run their own named example files; any starter file generated under `flows/` is separate.
19
+
20
+ Copy the examples shipped by the installed package into your project. This keeps fixture edits, sessions and reports in your workspace:
21
+
22
+ ```sh
23
+ cp -R node_modules/flowstride/examples/native-workflows workflow-examples
24
+ ```
25
+
26
+ On PowerShell, the equivalent copy is:
27
+
28
+ ```powershell
29
+ Copy-Item -Recurse node_modules/flowstride/examples/native-workflows workflow-examples
30
+ ```
31
+
32
+ Use a fresh `workflow-examples` destination. In an existing project, preserve your configuration and fixtures instead of overwriting them.
33
+
34
+ ## 2. Configure the local application URL
35
+
36
+ Create a project-root `.env` file, or add these values to the existing file:
37
+
38
+ ```dotenv
39
+ BASE_URL=http://127.0.0.1:4107
40
+ EXAMPLE_LABEL="Workflow fixture"
41
+ ```
42
+
43
+ OS and Cloud load `.env` automatically for a run. `BASE_URL` supplies the base for relative browser and API URLs. You can alternatively set `baseUrl` in `flowstride.config.json`; `BASE_URL` takes precedence.
44
+
45
+ For example, `flow.open "/orders";` and `flow.post "/api/users" ...` use the same configured application. String values inside `.flow` commands can use `{{env.NAME}}`. General environment-template substitution is not performed throughout the JSON configuration file.
46
+
47
+ Ensure your project's `.gitignore` includes:
48
+
49
+ ```gitignore
50
+ node_modules/
51
+ .env
52
+ .env.*
53
+ !.env.example
54
+ .flowstride/
55
+ flowstride-artifacts/
56
+ ```
57
+
58
+ Share placeholder values in `.env.example`. Keep real credentials in local configuration or CI secrets.
59
+
60
+ ## 3. Start the demo application
61
+
62
+ In a separate terminal, from the project root:
63
+
64
+ ```sh
65
+ node workflow-examples/shared/server.mjs
66
+ ```
67
+
68
+ Leave it running. It prints `http://127.0.0.1:4107`; open that address to see the example routes. It listens only on loopback and is a demonstration fixture, not a production authentication service.
69
+
70
+ If the port is occupied, select another port and update `BASE_URL` to match:
71
+
72
+ ```sh
73
+ FLOWSTRIDE_EXAMPLE_PORT=4108 node workflow-examples/shared/server.mjs
74
+ ```
75
+
76
+ PowerShell:
77
+
78
+ ```powershell
79
+ $env:FLOWSTRIDE_EXAMPLE_PORT = "4108"
80
+ node workflow-examples/shared/server.mjs
81
+ ```
82
+
83
+ Restarting the app clears its demo users and profile changes. A saved browser session does not restore the app's in-memory database.
84
+
85
+ ## 4. Run a named example
86
+
87
+ In your other terminal:
88
+
89
+ ```sh
90
+ npx flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow
91
+ ```
92
+
93
+ Interactive execution opens the local dashboard. For headless execution, a process exit code and retained evidence:
94
+
95
+ ```sh
96
+ npx flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow --ci
97
+ ```
98
+
99
+ A successful run exits with code `0`. Deliberate failures exit nonzero. Read `flowstride-artifacts/summary.json` and `junit.xml`; the summary identifies its unique `run-*` evidence directory. Failed assertions retain their screenshot and video when recording is enabled.
100
+
101
+ Read an individual step's `error` as well as the suite-level `errors`: an assertion failure can appear on the failed step while the suite-error array is empty.
102
+
103
+ ## 5. Choose browsers or Cloud
104
+
105
+ ```sh
106
+ npx flowstride install chromium firefox webkit
107
+ npx flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow --browser firefox --ci
108
+ npx flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow --browser webkit --ci
109
+ ```
110
+
111
+ Chromium is the default. Firefox and WebKit run the API/UI, iframe, table and variable examples. Audio-file microphone injection requires Chromium. Use `--headed` to see the test application's browser window during an interactive run.
112
+
113
+ For Cloud, connect with `npx flowstride login`, accept the authenticated installation when offered, then use the same commands. Private engine hosting and installation details are handled by the authenticated channel.
114
+
115
+ ## CI application startup
116
+
117
+ Flowstride can own the local application's lifecycle. Stop the manually started demo server first, then use a project configuration such as:
118
+
119
+ ```json
120
+ {
121
+ "testDir": "workflow-examples/api-and-ui/flows",
122
+ "timeout": 10000,
123
+ "cloudSync": false,
124
+ "webServer": {
125
+ "command": "node workflow-examples/shared/server.mjs",
126
+ "url": "http://127.0.0.1:4107",
127
+ "timeout": 10000
128
+ }
129
+ }
130
+ ```
131
+
132
+ Keep `BASE_URL` set in the environment or `.env`. Flowstride starts the configured app, checks readiness, runs the tests and stops the app afterward. It refuses to adopt a readiness URL that is already serving.
133
+
134
+ Use `npm ci` and matching browser installation on your CI runner. [NATIVE_CI.md](../../NATIVE_CI.md) covers workflow generation and artifact retention. Keep intentional failures and service-dependent templates out of the passing CI target.
135
+
136
+ ## Chapters
137
+
138
+ - [API + UI](api-and-ui/README.md)
139
+ - [API + UI + email OTP](api-ui-otp/README.md)
140
+ - [iFrames](iframes/README.md)
141
+ - [Audio-to-text](audio-to-text/README.md)
142
+ - [Tables and cells](tables-and-cells/README.md)
143
+ - [Variables and saved sessions](variables-and-reuse/README.md)
144
+ - [Manual testing](manual-testing/README.md)
@@ -0,0 +1,80 @@
1
+ # API + UI in the same test
2
+
3
+ One `.flow` scenario can create data through an API, extract the response, interact with the browser and make another API request to verify the outcome. No custom runner is needed.
4
+
5
+ | Requirement | Value |
6
+ | --- | --- |
7
+ | Engine | OS or Cloud |
8
+ | Browser | Chromium, Firefox or WebKit |
9
+ | Application | Shared local demo, running in another terminal |
10
+ | Cloud account | Optional for this example |
11
+
12
+ Complete the [shared setup](../README.md), including `.env` and the running application.
13
+
14
+ ## Complete example
15
+
16
+ [Read the runnable file](flows/api-and-ui.flow). It creates a disposable demo user, reuses its returned email in the UI and checks the recorded login count:
17
+
18
+ ```flow
19
+ Feature: API and browser in one test
20
+ Scenario: Create through API and sign in through UI
21
+ Given "Create a disposable user"
22
+ flow.post "/api/users" with reqBody;
23
+ """
24
+ {
25
+ "email": "$randomEmail@example.test",
26
+ "password": "fixture-password",
27
+ "name": "Workflow Reader"
28
+ }
29
+ """
30
+ flow.extract resBody "id" as "createdId";
31
+ flow.extract resBody "email" as "createdEmail";
32
+ And "Creation succeeds"
33
+ flow.expect status "201";
34
+ When "Sign in through the browser"
35
+ flow.open "/login";
36
+ flow.type input "Email" "@createdEmail";
37
+ flow.type input "Password" "fixture-password";
38
+ flow.click button "Sign in";
39
+ And "The UI shows the created account"
40
+ flow.expect visible "Welcome, Workflow Reader";
41
+ When "Verify backend state from the same test"
42
+ flow.get "/api/users/@createdId";
43
+ Then "The backend recorded the browser login"
44
+ flow.expect status "200";
45
+ flow.expect resBody "loginCount" equals "1";
46
+ ```
47
+
48
+ `@createdId` and `@createdEmail` are extracted values available later in this file. The browser's UI login changes the backend; the final API assertion verifies that change.
49
+
50
+ Use `And` for an intermediate assertion block. `Then` closes the scenario, so put the final outcome there rather than adding more steps after it.
51
+
52
+ ## Run and inspect
53
+
54
+ From the project root:
55
+
56
+ ```sh
57
+ npx flowstride run workflow-examples/api-and-ui/flows/api-and-ui.flow --ci
58
+ ```
59
+
60
+ Expected: HTTP registration status `201`, visible `Welcome, Workflow Reader`, backend `loginCount` equal to `1`, and a passed summary with process exit code `0`.
61
+
62
+ Try another browser with `--browser firefox` or `--browser webkit`. The same test commands work after connecting Cloud.
63
+
64
+ ## Deliberate failure
65
+
66
+ ```sh
67
+ npx flowstride run workflow-examples/api-and-ui/negative/wrong-backend-state.flow --ci
68
+ ```
69
+
70
+ That file expects `loginCount` to equal `99`. The browser login still occurs, but the final assertion must fail and the process must exit nonzero. Inspect the failed step, screenshot and video. A completed HTTP request is not proof of HTTP success: use an explicit status/body assertion.
71
+
72
+ ## Adapt to your application
73
+
74
+ Replace the API routes, request fields, response extraction paths and UI labels with your application's contract. Keep expected status codes and business outcomes explicit. Use disposable test accounts and provide an application-appropriate cleanup path when the backend stores data persistently.
75
+
76
+ The demo intentionally uses minimal local test authentication. Your application may require CSRF handling, application credentials or a privileged setup account. A Flowstride Cloud developer token authorizes Cloud services; application API authorization is configured separately.
77
+
78
+ Variables are shared through Flowstride's variable mechanisms; browser cookies and direct API authentication are separate state. Use the [saved-session guide](../variables-and-reuse/README.md#save-and-restore-browser-state) when you need a browser snapshot, and configure API request headers explicitly for your application's protected endpoints.
79
+
80
+ For an email challenge after login, continue with [API + UI + OTP](../api-ui-otp/README.md).
@@ -0,0 +1,27 @@
1
+ Feature: API and browser in one test
2
+ Scenario: Create through API and sign in through UI
3
+ Given "Create a disposable user"
4
+ flow.post "/api/users" with reqBody;
5
+ """
6
+ {
7
+ "email": "$randomEmail@example.test",
8
+ "password": "fixture-password",
9
+ "name": "Workflow Reader"
10
+ }
11
+ """
12
+ flow.extract resBody "id" as "createdId";
13
+ flow.extract resBody "email" as "createdEmail";
14
+ And "Creation succeeds"
15
+ flow.expect status "201";
16
+ When "Sign in through the browser"
17
+ flow.open "/login";
18
+ flow.type input "Email" "@createdEmail";
19
+ flow.type input "Password" "fixture-password";
20
+ flow.click button "Sign in";
21
+ And "The UI shows the created account"
22
+ flow.expect visible "Welcome, Workflow Reader";
23
+ When "Verify backend state from the same test"
24
+ flow.get "/api/users/@createdId";
25
+ Then "The backend recorded the browser login"
26
+ flow.expect status "200";
27
+ flow.expect resBody "loginCount" equals "1";
@@ -0,0 +1,27 @@
1
+ Feature: API and browser in one test
2
+ Scenario: Create through API and sign in through UI
3
+ Given "Create a disposable user"
4
+ flow.post "/api/users" with reqBody;
5
+ """
6
+ {
7
+ "email": "$randomEmail@example.test",
8
+ "password": "fixture-password",
9
+ "name": "Workflow Reader"
10
+ }
11
+ """
12
+ flow.extract resBody "id" as "createdId";
13
+ flow.extract resBody "email" as "createdEmail";
14
+ And "Creation succeeds"
15
+ flow.expect status "201";
16
+ When "Sign in through the browser"
17
+ flow.open "/login";
18
+ flow.type input "Email" "@createdEmail";
19
+ flow.type input "Password" "fixture-password";
20
+ flow.click button "Sign in";
21
+ And "The UI shows the created account"
22
+ flow.expect visible "Welcome, Workflow Reader";
23
+ When "Verify backend state from the same test"
24
+ flow.get "/api/users/@createdId";
25
+ Then "The backend recorded the browser login"
26
+ flow.expect status "200";
27
+ flow.expect resBody "loginCount" equals "99";
@@ -0,0 +1,140 @@
1
+ # API + UI + email OTP
2
+
3
+ FlowMail retrieves an OTP delivered to a supported test inbox and stores it in a flow variable. The same test can register through your application's API, sign in through its UI, enter the code and assert successful authentication.
4
+
5
+ | Requirement | Value |
6
+ | --- | --- |
7
+ | Engine | Cloud; `flow.mail.getOtp` is a Cloud service command |
8
+ | Browser | Chromium, Firefox or WebKit for ordinary browser interactions |
9
+ | Cloud access | Authenticated developer token and available workspace FlowMail allowance |
10
+ | Application | An authorized test application that actually sends email OTP |
11
+ | Inbox | An address supported by FlowMail, using `@flowstridemail.com` |
12
+
13
+ Cloud includes free accounts. FlowMail allowances depend on the workspace plan and current usage. This chapter's templates require your application's real routes and selectors; the shared local demo does not send email.
14
+
15
+ ## Connect Cloud
16
+
17
+ Follow the normal [npm setup](../README.md), then:
18
+
19
+ ```sh
20
+ npx flowstride login
21
+ ```
22
+
23
+ Use your developer token at the prompt and accept authenticated engine installation when offered. In CI, use the `FLOWSTRIDE_TOKEN` secret and the normal Cloud setup described in [NATIVE_CI.md](../../../NATIVE_CI.md).
24
+
25
+ ## Register, sign in and verify
26
+
27
+ [api-ui-otp.flow](templates/api-ui-otp.flow) is a complete application template. Its contract is:
28
+
29
+ - Registration accepts the shown fields, returns `201` and a `userId`, and leaves the account active for sign-in.
30
+ - The login form has Email, Password and Login targets.
31
+ - Login triggers an email to the generated address.
32
+ - The challenge has an Enter 6-digit OTP input and Verify OTP button.
33
+ - `Logout` is visible only after authentication completes.
34
+
35
+ Adapt these fields, selectors and expected results to your application. In your private `.env`, configure:
36
+
37
+ ```dotenv
38
+ SIGNUP_URL=https://your-test-application.example/api/register
39
+ LOGIN_URL=https://your-test-application.example/login
40
+ ```
41
+
42
+ The template contains the full API-to-UI journey:
43
+
44
+ ```flow
45
+ Feature: Register through an application API and verify an email OTP in the UI
46
+
47
+ Scenario: Complete an email-verified sign-in with a disposable account
48
+ Given "The application creates an active test account"
49
+ flow.post "{{env.SIGNUP_URL}}" with reqBody;
50
+ """
51
+ {
52
+ "firstName": "Example",
53
+ "lastName": "Reader",
54
+ "email": "$randomEmail@flowstridemail.com",
55
+ "password": "$randomPassword"
56
+ }
57
+ """
58
+ flow.extract resBody "userId" as "newUserId";
59
+ flow.expect status "201";
60
+
61
+ When "Open the application's sign-in form"
62
+ flow.open "{{env.LOGIN_URL}}";
63
+ flow.type input "Email" "@randomEmail@flowstridemail.com";
64
+ flow.type input "Password" "@randomPassword";
65
+ flow.click button "Login";
66
+
67
+ When "Retrieve the delivered code through FlowMail"
68
+ flow.mail.getOtp "@randomEmail@flowstridemail.com" into "loginOtp";
69
+
70
+ When "Submit the retrieved code"
71
+ flow.type input "Enter 6-digit OTP" "@loginOtp";
72
+ flow.click button "Verify OTP";
73
+
74
+ Then "The application confirms successful authentication"
75
+ flow.expect visible "Logout";
76
+ ```
77
+
78
+ `$randomEmail` and `$randomPassword` generate values; `@randomEmail` and `@randomPassword` reuse their remembered values in this file. The address suffix must stay consistent between registration, UI sign-in and inbox polling.
79
+
80
+ Run the adapted template explicitly:
81
+
82
+ ```sh
83
+ npx flowstride run workflow-examples/api-ui-otp/templates/api-ui-otp.flow --ci
84
+ ```
85
+
86
+ Expected: registration succeeds, the code is retrieved and entered, the application confirms sign-in, and the process exits `0`.
87
+
88
+ ### Applications requiring approval
89
+
90
+ If registration creates a pending account, add your application's authorized approval step before browser sign-in. For an application whose approval API uses a bearer token, an example block is:
91
+
92
+ ```flow
93
+ When "Approve the test account using application authorization"
94
+ flow.post "{{env.APPROVAL_URL}}"
95
+ with reqHeader "Authorization" "Bearer {{env.APPROVAL_TOKEN}}"
96
+ with reqBody;
97
+ """
98
+ {
99
+ "userId": "@newUserId"
100
+ }
101
+ """
102
+
103
+ And "The application accepted approval"
104
+ flow.expect status "200";
105
+ ```
106
+
107
+ Use your application's actual authorization scheme and verify the resulting active state. A cookie-based approver session needs its own application setup. A `401` or `403` at this step prevents the test from reaching OTP and must be resolved as an application prerequisite.
108
+
109
+ ## Use an existing account
110
+
111
+ For a preapproved account, adapt [existing-account-otp.flow](templates/existing-account-otp.flow) instead. Configure:
112
+
113
+ ```dotenv
114
+ LOGIN_URL=https://your-test-application.example/login
115
+ OTP_TEST_EMAIL=your-supported-test-address@flowstridemail.com
116
+ OTP_TEST_PASSWORD=replace-with-the-application-test-password
117
+ ```
118
+
119
+ ```sh
120
+ npx flowstride run workflow-examples/api-ui-otp/templates/existing-account-otp.flow --ci
121
+ ```
122
+
123
+ The chosen account must require an email OTP challenge. An account that signs in directly does not exercise email delivery or retrieval.
124
+
125
+ ## Negative checks and diagnosis
126
+
127
+ For a deterministic assertion failure, change the final expected login marker to a value your application never displays. The run must fail even after code retrieval succeeds. To test invalid-code handling, use a code known to be invalid for that request and assert the application's rejection message; a fixed six-digit value is not guaranteed to be wrong.
128
+
129
+ FlowMail polls up to ten times with delays. Authentication or permission rejection stops the lookup; an inbox with no matching code eventually times out. Set a suitable test timeout for delivery and your application's processing time.
130
+
131
+ | Result | Check |
132
+ | --- | --- |
133
+ | No Cloud authentication | Run `npx flowstride login` or configure the CI secret |
134
+ | Permission/allowance rejection | Check workspace access, token validity and FlowMail usage |
135
+ | Approval returns `401`/`403` | Supply authorized application setup before sign-in |
136
+ | No email arrives | Confirm the application actually sends to the same supported address |
137
+ | Login succeeds without challenge | Use an account that requires OTP |
138
+ | Code retrieved, login still fails | Inspect application expiry, validation and the final UI outcome |
139
+
140
+ Code retrieval alone is not a passed authentication test. Keep the final application assertion, and isolate account creation/mail side effects when designing retries.
@@ -0,0 +1,31 @@
1
+ Feature: Register through an application API and verify an email OTP in the UI
2
+
3
+ Scenario: Complete an email-verified sign-in with a disposable account
4
+ Given "The application creates an active test account"
5
+ flow.post "{{env.SIGNUP_URL}}" with reqBody;
6
+ """
7
+ {
8
+ "firstName": "Example",
9
+ "lastName": "Reader",
10
+ "email": "$randomEmail@flowstridemail.com",
11
+ "password": "$randomPassword"
12
+ }
13
+ """
14
+ flow.extract resBody "userId" as "newUserId";
15
+ flow.expect status "201";
16
+
17
+ When "Open the application's sign-in form"
18
+ flow.open "{{env.LOGIN_URL}}";
19
+ flow.type input "Email" "@randomEmail@flowstridemail.com";
20
+ flow.type input "Password" "@randomPassword";
21
+ flow.click button "Login";
22
+
23
+ When "Retrieve the delivered code through FlowMail"
24
+ flow.mail.getOtp "@randomEmail@flowstridemail.com" into "loginOtp";
25
+
26
+ When "Submit the retrieved code"
27
+ flow.type input "Enter 6-digit OTP" "@loginOtp";
28
+ flow.click button "Verify OTP";
29
+
30
+ Then "The application confirms successful authentication"
31
+ flow.expect visible "Logout";
@@ -0,0 +1,20 @@
1
+ Feature: Email OTP for an existing application test account
2
+
3
+ Scenario: Authenticate an account that requires email OTP
4
+ Given "An active OTP-enabled test account"
5
+ flow.open "{{env.LOGIN_URL}}";
6
+
7
+ When "Sign in with the application credentials"
8
+ flow.type input "Email" "{{env.OTP_TEST_EMAIL}}";
9
+ flow.type input "Password" "{{env.OTP_TEST_PASSWORD}}";
10
+ flow.click button "Login";
11
+
12
+ When "Read the code delivered to the supported test inbox"
13
+ flow.mail.getOtp "{{env.OTP_TEST_EMAIL}}" into "loginOtp";
14
+
15
+ When "Complete verification"
16
+ flow.type input "Enter 6-digit OTP" "@loginOtp";
17
+ flow.click button "Verify OTP";
18
+
19
+ Then "The application confirms the login"
20
+ flow.expect visible "Logout";
@@ -0,0 +1,131 @@
1
+ # Test audio-to-text workflows
2
+
3
+ Flowstride supplies prerecorded audio to the browser's microphone, drives recording controls and checks the transcript displayed by your application. Your application's speech-to-text service performs recognition.
4
+
5
+ | Requirement | Value |
6
+ | --- | --- |
7
+ | Engine | OS or Cloud |
8
+ | Browser | Chromium for audio-file microphone injection |
9
+ | Local demo | Included PCM WAV, real microphone capture and a simulated transcript response |
10
+ | Real transcription | Your own authorized recording application, service and spoken WAV fixture |
11
+
12
+ Complete the [shared setup](../README.md). Install the matching Chromium explicitly if needed:
13
+
14
+ ```sh
15
+ npx flowstride install chromium
16
+ ```
17
+
18
+ ## 1. Run the self-contained microphone demo
19
+
20
+ The included [microphone-tone.wav](fixtures/microphone-tone.wav) is a short generated tone. The demo requests `getUserMedia`, measures actual microphone input and produces a deterministic transcript-like response when it detects a signal. This checks input delivery, recording controls, waiting and assertions; it is not a speech-recognition accuracy test.
21
+
22
+ [microphone-demo.flow](flows/microphone-demo.flow):
23
+
24
+ ```flow
25
+ Feature: Microphone and transcript UI fixture
26
+ Scenario: Capture a known microphone signal
27
+ Given "Configure a PCM WAV before browser startup"
28
+ flow.injectAudio "workflow-examples/audio-to-text/fixtures/microphone-tone.wav";
29
+ flow.browser.permissions {
30
+ "permissions": [
31
+ "microphone"
32
+ ]
33
+ };
34
+ flow.open "/audio";
35
+ When "Start recording"
36
+ flow.click button "Start recording";
37
+ flow.expect visible "Recording active";
38
+ When "Allow the fixture audio to play and stop"
39
+ flow.waitForPipeline;
40
+ flow.click button "Stop recording";
41
+ Then "The simulated transcription result is displayed"
42
+ flow.expect transcript contains "Fixture transcript: audio received.";
43
+ flow.assert.that {
44
+ "selector": "#transcript",
45
+ "condition": "text",
46
+ "value": "Fixture transcript: audio received."
47
+ };
48
+ ```
49
+
50
+ ```sh
51
+ npx flowstride run workflow-examples/audio-to-text/flows/microphone-demo.flow --browser chromium --ci
52
+ ```
53
+
54
+ Expected: recording becomes active, non-silent microphone input is captured, recording stops, and the displayed result is `Fixture transcript: audio received.`. The run exits `0`.
55
+
56
+ The fixture path is literal and relative to the project root after the shared copy step. The engine reads the first literal audio fixture before browser startup. It does not resolve flow variables in that pre-launch path or hot-swap recordings during an active browser session. Run different audio fixtures in separate invocations.
57
+
58
+ ## 2. Confirm a wrong transcript fails
59
+
60
+ ```sh
61
+ npx flowstride run workflow-examples/audio-to-text/negative/wrong-transcript.flow --browser chromium --ci
62
+ ```
63
+
64
+ This captures audio but expects a phrase the demo never produces. It must fail and exit nonzero. Inspect its failed assertion and media.
65
+
66
+ Firefox and WebKit reject `flow.injectAudio` explicitly. Keep audio cases in a Chromium job instead of adding them indiscriminately to a three-browser matrix.
67
+
68
+ ## 3. Adapt to a real transcription service
69
+
70
+ Use [real-transcription.flow](templates/real-transcription.flow) with your recording application. Put a known spoken recording at the literal project path `fixtures/voice-note.wav`. Use an uncompressed PCM WAV with a conventional 44-byte header; changing a file extension does not convert its audio format.
71
+
72
+ Configure private application values:
73
+
74
+ ```dotenv
75
+ TRANSCRIPTION_URL=https://your-test-application.example/record
76
+ EXPECTED_TRANSCRIPT="the known words spoken in the recording"
77
+ ```
78
+
79
+ The template is:
80
+
81
+ ```flow
82
+ Feature: Verify a real application's speech transcription
83
+
84
+ Scenario: Record known spoken audio and check the resulting transcript
85
+ Given "A spoken PCM WAV and an authorized recording page"
86
+ flow.injectAudio "fixtures/voice-note.wav";
87
+ flow.browser.permissions {
88
+ "permissions": ["microphone"]
89
+ };
90
+ flow.open "{{env.TRANSCRIPTION_URL}}";
91
+
92
+ When "Start the application's recording"
93
+ flow.click button "Start recording";
94
+ flow.expect visible "Recording active";
95
+
96
+ When "Allow the audio clip to play and stop recording"
97
+ flow.waitForPipeline;
98
+ flow.click button "Stop recording";
99
+
100
+ Then "The new transcript contains the expected spoken words"
101
+ flow.assert.that {
102
+ "selector": "#transcript",
103
+ "condition": "containsText",
104
+ "value": "{{env.EXPECTED_TRANSCRIPT}}",
105
+ "timeout": 60000
106
+ };
107
+ ```
108
+
109
+ Adapt the recording buttons, recording-state label and transcript selector to your application. If authentication is required, add the normal application login or restore a saved browser session before opening the recording page. The application must have a working transcription service and suitable access to it.
110
+
111
+ ```sh
112
+ npx flowstride run workflow-examples/audio-to-text/templates/real-transcription.flow --browser chromium --ci
113
+ ```
114
+
115
+ Expected: the application receives the spoken audio and the **newly produced** transcript contains the known phrase. This is the service-backed check; the local tone demo cannot establish that result.
116
+
117
+ ## Timing and comparison
118
+
119
+ | Command | Behavior |
120
+ | --- | --- |
121
+ | `flow.injectAudio` | Configures the file used as Chromium microphone input |
122
+ | `flow.browser.permissions` | Grants microphone permission to the current browser profile |
123
+ | `flow.waitForPipeline` | Estimates a wait from the WAV header, approximately the clip duration plus a half second |
124
+ | `flow.expect transcript contains` | Polls rendered page-body text after lowercasing, removing punctuation and collapsing whitespace |
125
+ | `flow.assert.that` with `selector` | Checks the specific result element and supports an explicit timeout |
126
+
127
+ `flow.waitForPipeline` does not observe an upload, server job or actual playback completion. Assert the recording state and final application result separately. Browser video alone does not prove microphone input or transcription succeeded.
128
+
129
+ The transcript helper normalizes ASCII word characters and searches the page body. Static example text or old transcripts can satisfy it. Clear old results and prefer a scoped assertion for the new transcript, especially for accented/non-Latin text. If the result is in an input/textarea, use the native `value` condition rather than `text`.
130
+
131
+ Use a browser-trusted origin such as localhost or HTTPS. Check microphone permission, fixture format, capture start/stop timing and service latency when diagnosing failures. Cloud can run these same browser commands; recording or transcription service credentials remain application prerequisites.