flowstride 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/adapters/api/undici.d.ts +46 -0
  4. package/dist/adapters/api/undici.js +118 -0
  5. package/dist/adapters/web/playwright.d.ts +51 -0
  6. package/dist/adapters/web/playwright.js +1108 -0
  7. package/dist/cli/commands/init.d.ts +1 -0
  8. package/dist/cli/commands/init.js +77 -0
  9. package/dist/cli/commands/report.d.ts +1 -0
  10. package/dist/cli/commands/report.js +78 -0
  11. package/dist/cli/commands/run.d.ts +1 -0
  12. package/dist/cli/commands/run.js +158 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +39 -0
  15. package/dist/parser/lexer.d.ts +29 -0
  16. package/dist/parser/lexer.js +425 -0
  17. package/dist/parser/parser.d.ts +41 -0
  18. package/dist/parser/parser.js +573 -0
  19. package/dist/parser/resolver.d.ts +5 -0
  20. package/dist/parser/resolver.js +19 -0
  21. package/dist/parser/validator.d.ts +4 -0
  22. package/dist/parser/validator.js +37 -0
  23. package/dist/parser/variable_resolver.d.ts +7 -0
  24. package/dist/parser/variable_resolver.js +33 -0
  25. package/dist/runner/data_generators.d.ts +55 -0
  26. package/dist/runner/data_generators.js +233 -0
  27. package/dist/runner/dom_stripper.d.ts +4 -0
  28. package/dist/runner/dom_stripper.js +61 -0
  29. package/dist/runner/events.d.ts +15 -0
  30. package/dist/runner/events.js +20 -0
  31. package/dist/runner/flow_orchestrator.d.ts +15 -0
  32. package/dist/runner/flow_orchestrator.js +216 -0
  33. package/dist/runner/flow_worker.d.ts +53 -0
  34. package/dist/runner/flow_worker.js +1112 -0
  35. package/dist/runner/persistent_vault.d.ts +12 -0
  36. package/dist/runner/persistent_vault.js +84 -0
  37. package/dist/runner/plugin_manager.d.ts +13 -0
  38. package/dist/runner/plugin_manager.js +164 -0
  39. package/dist/runner/temporary_store.d.ts +8 -0
  40. package/dist/runner/temporary_store.js +22 -0
  41. package/dist/runner/worker_entry.d.ts +1 -0
  42. package/dist/runner/worker_entry.js +63 -0
  43. package/dist/sdk/api_proxy.d.ts +8 -0
  44. package/dist/sdk/api_proxy.js +28 -0
  45. package/dist/sdk/db_proxy.d.ts +3 -0
  46. package/dist/sdk/db_proxy.js +15 -0
  47. package/dist/sessions/manager.d.ts +17 -0
  48. package/dist/sessions/manager.js +74 -0
  49. package/dist/sessions/types.d.ts +39 -0
  50. package/dist/sessions/types.js +2 -0
  51. package/dist/sessions/vault.d.ts +8 -0
  52. package/dist/sessions/vault.js +69 -0
  53. package/dist/test_data/commerce.json +341 -0
  54. package/dist/test_data/geography.json +1298 -0
  55. package/dist/test_data/identities.json +1420 -0
  56. package/dist/test_data/phone_data.json +27 -0
  57. package/dist/test_data/utility.json +246 -0
  58. package/dist/types/index.d.ts +104 -0
  59. package/dist/types/index.js +2 -0
  60. package/dist/utils/redactor.d.ts +5 -0
  61. package/dist/utils/redactor.js +53 -0
  62. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flowstride.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # Flowstride OS
2
+
3
+ **The next-generation, BDD-flavored End-to-End Testing Framework.**
4
+
5
+ Flowstride OS combines the human-readable structure of Behavior-Driven Development (BDD) with the raw execution power of Playwright. It allows QA engineers and developers to write robust, maintainable UI and API tests using an intuitive domain-specific language (DSL), while providing a rich, interactive React dashboard to monitor, debug, and replay test runs in real time.
6
+
7
+ ---
8
+
9
+ ## Why Flowstride?
10
+
11
+ Traditional E2E frameworks force you to choose between highly technical code (which non-developers struggle to read) or rigid low-code tools (which lack flexibility). Flowstride bridges the gap:
12
+
13
+ - **Plain-English Selectors**: Target elements spatially (`"Submit" near "Cancel"`) or by text, eliminating brittle CSS/XPath dependencies.
14
+ - **Unified UI & API Testing**: Execute browser actions and REST/GraphQL API calls within the exact same scenario.
15
+ - **Live Visual Dashboard**: Watch your tests run step-by-step, inspect DOM snapshots, trace network activity, and manually replay API requests directly from the UI.
16
+ - **Built-in State Management**: Save and inject browser sessions (cookies, local storage) with a single command to bypass repetitive login steps.
17
+
18
+ ---
19
+
20
+ ## Installation & Quick Start
21
+
22
+ Flowstride requires Node.js 18 or higher.
23
+
24
+ **1. Install globally (or as a dev dependency in your project):**
25
+ npm install -g flowstride
26
+
27
+ **2. Initialize a new Flowstride workspace:**
28
+ flowstride init
29
+
30
+ _(This creates the default directory structure (`/flows`, `/reports`, `/plugins`) and generates a `welcome.flow` sample file.)_
31
+
32
+ **3. Run your first test:**
33
+ flowstride run flows/welcome.flow
34
+
35
+ _(This command executes your test while instantly booting up the local Flowstride Dashboard on `http://localhost:5173`.)_
36
+
37
+ ---
38
+
39
+ ## The `.flow` Syntax
40
+
41
+ Flowstride utilizes a custom `.flow` file extension. It seamlessly pairs descriptive BDD steps (Given, When, And, Then) with direct, executable `flow.` commands.
42
+
43
+ Here is an example of a standard UI and API flow:
44
+
45
+ ```bash
46
+ // Signing up a new user via API call.
47
+ Given "User signup via API call"
48
+ flow.post "/api/signup" with reqBody
49
+ """
50
+ {
51
+ "firstName": "Mike",
52
+ "lastName": "Doe",
53
+ "dob": "1995-05-10",
54
+ "sex": "male",
55
+ "email": "user@flowstridemail.com",
56
+ "password": "$randomPassword"
57
+ }
58
+ """
59
+ flow.extract resBody "userId" as "newUserId"
60
+ flow.expect status "201"
61
+
62
+ // Approving the new user from admin via API call
63
+ When "A new signup user is approved by the Admin"
64
+ flow.post "/api/approve" with reqBody
65
+ """
66
+ {
67
+ "userId": "@newUserId"
68
+ }
69
+ """
70
+
71
+ And "Confirm Status Code is 200"
72
+ flow.expect status "200"
73
+
74
+ And "Confirm resBody message is successful"
75
+ flow.expect resBody "message" equals "User approved successfully"
76
+
77
+ And "Confirm user status to be active"
78
+ flow.expect resBody "user.status" equals "active"
79
+
80
+ // Using the newly registered user credentials to login via the UI
81
+ When "User lands on home page"
82
+ flow.open "/login"
83
+
84
+ And "User clicks on Log in, near Sign Up"
85
+ flow.click "Log in" near "Sign up"
86
+
87
+ And "User inputs newly created credentials"
88
+ flow.type "Email" "user@flowstridemail.com"
89
+ flow.type "Password" "@randomPassword" // Reusing the random password from API call
90
+
91
+ Then "Click on Login button"
92
+ flow.click button "Login"
93
+
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Command Reference
99
+
100
+ ### Browser Navigation & Context
101
+
102
+ | Command | Example | Description |
103
+ | :--------- | :----------------------------- | :--------------------------------------------------------------- |
104
+ | `open` | `flow.open "https://site.com"` | Navigates the browser to the specified URL. |
105
+ | `switchTo` | `flow.switchTo "next"` | Switches to a new tab or window by title/URL or relation. |
106
+ | `close` | `flow.close "Modal Title"` | Geometrically calculates and clicks the close button of a modal. |
107
+
108
+ ### UI Actions
109
+
110
+ | Command | Example | Description |
111
+ | :----------- | :---------------------------------------- | :-------------------------------------------------------------- |
112
+ | `click` | `flow.click "Submit"` | Clicks an element based on text, CSS, or spatial relation. |
113
+ | `forceClick` | `flow.forceClick "#hidden-btn"` | Bypasses Playwright's actionability checks to force a click. |
114
+ | `type` | `flow.type "Email" "test@test.com"` | Types value into an input field matching the target. |
115
+ | `passcode` | `flow.passcode "OTP" "123456"` | Specifically handles split OTP/Passcode input arrays. |
116
+ | `select` | `flow.select "Option 1" from "Dropdown"` | Selects an option from a native select or custom portal. |
117
+ | `check` | `flow.check "Terms and Conditions"` | Checks a checkbox or radio button. |
118
+ | `upload` | `flow.upload "Avatar" "path/to/file.jpg"` | Uploads a local file to a file input element. |
119
+ | `drag` | `flow.drag "Item A" into "Zone B"` | Drags a source element and drops it into a destination element. |
120
+
121
+ ### Assertions
122
+
123
+ | Command | Example | Description |
124
+ | :--------------- | :------------------------------------- | :----------------------------------------------------- |
125
+ | `expect visible` | `flow.expect visible "Success"` | Asserts an element is attached to the DOM and visible. |
126
+ | `expect text` | `flow.expect text "Error"` | Asserts specific text is present in the UI. |
127
+ | `expect value` | `flow.expect value "Username" "admin"` | Asserts the input value of a targeted field. |
128
+
129
+ ### Session Management
130
+
131
+ | Command | Example | Description |
132
+ | :------------- | :------------------------- | :---------------------------------------------------------------------------- |
133
+ | `save session` | `flow.save session "Auth"` | Captures current cookies/storage into memory (add `persist` to save to disk). |
134
+ | `use session` | `flow.use session "Auth"` | Injects previously saved cookies/storage directly into the browser. |
135
+
136
+ ### API Testing
137
+
138
+ | Command | Example | Description |
139
+ | :----------------------------- | :-------------------------------------------- | :----------------------------------------------------------------- |
140
+ | `get`, `post`, `put`, `delete` | `flow.post "/api/users" with reqBody "{...}"` | Executes HTTP requests natively via Undici. |
141
+ | `extract` | `flow.extract resBody "token" as "jwt"` | Parses API responses and saves data to a local or global variable. |
142
+ | `expect status` | `flow.expect status "200"` | Asserts the HTTP status code of the last API call. |
143
+
144
+ _(For a complete list of commands, API assertions, and plugin integration rules, please see the official documentation)._
145
+
146
+ ---
147
+
148
+ ## Interactive Dashboard
149
+
150
+ Running a `.flow` file automatically boots the Flowstride Dashboard.
151
+
152
+ - **Live Execution:** Watch tests execute in real-time with an integrated screencast.
153
+ - **Step Diagnostics:** Click on any step (Pass or Fail) to view exact execution context, error stack traces, and a DOM snapshot with the target element highlighted.
154
+ - **Network Interception:** View all intercepted XHR/Fetch requests. You can click **Replay** on any API log to independently re-execute the request from the Node.js backend natively.
155
+ - **Soft Kill & Rerun:** Use the top navigation bar to seamlessly abort running tests, clear state, and restart execution without touching the terminal.
156
+
157
+ ---
158
+
159
+ ## Flowstride OS vs. Enterprise
160
+
161
+ **Flowstride OS** is free, fully featured, and highly capable for individual developers and standard QA teams.
162
+
163
+ **Flowstride Enterprise** (`flowstride.io`) unlocks advanced capabilities for massive-scale automation:
164
+
165
+ - **Distributed Parallel Execution Grid**: Run thousands of tests simultaneously across dynamically spun-up workers.
166
+ - **AI Auto-Healing**: Let AI automatically fix broken or outdated selectors during test execution based on DOM analysis.
167
+ - **Automated FlowMail (OTP/2FA)**: Fully native email extraction (`flow.getOtp`) to bypass complex 2FA flows.
168
+ - **High-Fidelity PDF Exports**: Programmatic, C-suite ready PDF report generation.
169
+ - **Cloud Storage & History**: Store all your test runs in the cloud to access and reference them at any time.
170
+ - **Native Integrations**: Seamlessly connect your testing workflows directly with Jira and Slack.
171
+ - **Massive Team Collaboration**: Scale your operations by connecting with 70+ team members in a shared workspace.
172
+
173
+ ---
174
+
175
+ ## Contributing
176
+
177
+ We welcome contributions from the community! Whether you are fixing bugs, proposing new features, or improving documentation, please feel free to open an issue or submit a Pull Request.
178
+
179
+ When submitting a PR, please ensure you test your changes against the core parser (`src/parser/`) and the Playwright web adapter (`src/adapters/web/`).
180
+
181
+ ## License
182
+
183
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,46 @@
1
+ import { FlowstrideConfig } from "../../types";
2
+ export interface ApiState {
3
+ status: number | null;
4
+ body: any | null;
5
+ headers: Record<string, string | string[] | undefined> | null;
6
+ responseTime: number;
7
+ }
8
+ export declare class ApiAdapter {
9
+ private config;
10
+ private globalHeaders;
11
+ private onUnauthorized?;
12
+ private lastStatus;
13
+ private lastBody;
14
+ private lastHeaders;
15
+ private lastResponseTime;
16
+ constructor(config: FlowstrideConfig);
17
+ setGlobalHeader(name: string, value: string): void;
18
+ setUnauthorizedHandler(handler: () => Promise<void>): void;
19
+ getState(): ApiState;
20
+ resetState(): void;
21
+ get(path: string, headers?: {
22
+ key: string;
23
+ value: string;
24
+ }[]): Promise<void>;
25
+ delete(path: string, headers?: {
26
+ key: string;
27
+ value: string;
28
+ }[]): Promise<void>;
29
+ post(path: string, headers: {
30
+ key: string;
31
+ value: string;
32
+ }[], body?: string): Promise<void>;
33
+ put(path: string, headers: {
34
+ key: string;
35
+ value: string;
36
+ }[], body?: string): Promise<void>;
37
+ patch(path: string, headers: {
38
+ key: string;
39
+ value: string;
40
+ }[], body?: string): Promise<void>;
41
+ graphql(path: string, headers: {
42
+ key: string;
43
+ value: string;
44
+ }[], bodyContent: string): Promise<void>;
45
+ private execute;
46
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiAdapter = void 0;
4
+ const undici_1 = require("undici");
5
+ class ApiAdapter {
6
+ config;
7
+ globalHeaders = {};
8
+ onUnauthorized;
9
+ lastStatus = null;
10
+ lastBody = null;
11
+ lastHeaders = null;
12
+ lastResponseTime = 0;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ setGlobalHeader(name, value) {
17
+ this.globalHeaders[name] = value;
18
+ }
19
+ setUnauthorizedHandler(handler) {
20
+ this.onUnauthorized = handler;
21
+ }
22
+ getState() {
23
+ if (this.lastStatus === null) {
24
+ throw new Error("No API request has been executed yet to extract state from.");
25
+ }
26
+ return {
27
+ status: this.lastStatus,
28
+ body: this.lastBody,
29
+ headers: this.lastHeaders,
30
+ responseTime: this.lastResponseTime,
31
+ };
32
+ }
33
+ resetState() {
34
+ this.lastStatus = null;
35
+ this.lastBody = null;
36
+ this.lastHeaders = null;
37
+ this.lastResponseTime = 0;
38
+ }
39
+ async get(path, headers = []) {
40
+ return this.execute("GET", path, headers);
41
+ }
42
+ async delete(path, headers = []) {
43
+ return this.execute("DELETE", path, headers);
44
+ }
45
+ async post(path, headers, body) {
46
+ return this.execute("POST", path, headers, body);
47
+ }
48
+ async put(path, headers, body) {
49
+ return this.execute("PUT", path, headers, body);
50
+ }
51
+ async patch(path, headers, body) {
52
+ return this.execute("PATCH", path, headers, body);
53
+ }
54
+ async graphql(path, headers, bodyContent) {
55
+ let finalPayload = bodyContent;
56
+ try {
57
+ JSON.parse(bodyContent);
58
+ }
59
+ catch {
60
+ finalPayload = JSON.stringify({ query: bodyContent.trim() });
61
+ }
62
+ const hasContentType = headers.some((h) => h.key.toLowerCase() === "content-type");
63
+ if (!hasContentType)
64
+ headers.push({ key: "Content-Type", value: "application/json" });
65
+ return this.execute("POST", path, headers, finalPayload);
66
+ }
67
+ async execute(method, path, headersArray = [], body) {
68
+ const url = this.config.baseUrl && !path.startsWith("http")
69
+ ? `${this.config.baseUrl.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`
70
+ : path;
71
+ const requestHeaders = {
72
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Flowstride/1.0",
73
+ Accept: "application/json, text/plain, */*",
74
+ ...this.globalHeaders,
75
+ };
76
+ let hasContentType = false;
77
+ headersArray.forEach((h) => {
78
+ requestHeaders[h.key] = h.value;
79
+ if (h.key.toLowerCase() === "content-type")
80
+ hasContentType = true;
81
+ });
82
+ if (body && !hasContentType) {
83
+ requestHeaders["Content-Type"] = "application/json";
84
+ }
85
+ const makeRequest = async () => {
86
+ const startTime = Date.now();
87
+ const response = await (0, undici_1.request)(url, {
88
+ method,
89
+ headers: requestHeaders,
90
+ body: body ? body : undefined,
91
+ });
92
+ const responseTime = Date.now() - startTime;
93
+ const text = await response.body.text();
94
+ let parsedBody = text;
95
+ try {
96
+ parsedBody = text ? JSON.parse(text) : null;
97
+ }
98
+ catch { }
99
+ return {
100
+ status: response.statusCode,
101
+ headers: response.headers,
102
+ body: parsedBody,
103
+ responseTime,
104
+ };
105
+ };
106
+ let res = await makeRequest();
107
+ if ((res.status === 401 || res.status === 403) && this.onUnauthorized) {
108
+ await this.onUnauthorized();
109
+ Object.assign(requestHeaders, this.globalHeaders);
110
+ res = await makeRequest();
111
+ }
112
+ this.lastStatus = res.status;
113
+ this.lastHeaders = res.headers;
114
+ this.lastBody = res.body;
115
+ this.lastResponseTime = res.responseTime;
116
+ }
117
+ }
118
+ exports.ApiAdapter = ApiAdapter;
@@ -0,0 +1,51 @@
1
+ import { Page } from "playwright";
2
+ import { FlowstrideConfig } from "../../types";
3
+ export declare class WebAdapter {
4
+ private config;
5
+ private browser;
6
+ private context;
7
+ private page;
8
+ private discoveredPages;
9
+ private dialogHandler;
10
+ constructor(config: FlowstrideConfig);
11
+ getPage(): Page;
12
+ initialise(audioInjectionPath?: string): Promise<void>;
13
+ acceptDialog(): Promise<void>;
14
+ rejectDialog(): Promise<void>;
15
+ injectAudio(filePath: string): Promise<void>;
16
+ switchTo(target: string): Promise<void>;
17
+ injectStorage(cookies?: any[], localStorage?: Record<string, string>, fallbackDomain?: string): Promise<void>;
18
+ getState(): Promise<{
19
+ cookies: any[];
20
+ localStorage: Record<string, string>;
21
+ sessionStorage: Record<string, string>;
22
+ }>;
23
+ getCurrentUrl(): string;
24
+ navigate(url: string): Promise<void>;
25
+ synthesizeCurl(method: string, url: string, headers: Record<string, string>, postData: string | null): string;
26
+ private checkAmbiguity;
27
+ close(targetText: string): Promise<void>;
28
+ click(selector: string, elementType?: string): Promise<void>;
29
+ type(selector: string, value: string, elementType?: string): Promise<void>;
30
+ passcode(selector: string, value: string, elementType?: string): Promise<void>;
31
+ check(selector: string, elementType?: string): Promise<void>;
32
+ uncheck(selector: string, elementType?: string): Promise<void>;
33
+ upload(selector: string, filePath: string, elementType?: string): Promise<void>;
34
+ set(selector: string, value: string, elementType?: string): Promise<void>;
35
+ private fallbackCalendarSelect;
36
+ select(option: string, selector: string, elementType?: string): Promise<void>;
37
+ drag(sourceSelector: string, destinationSelector: string): Promise<void>;
38
+ forceClick(selector: string, elementType?: string): Promise<void>;
39
+ forceType(selector: string, value: string, elementType?: string): Promise<void>;
40
+ expectVisible(selector: string, elementType?: string): Promise<void>;
41
+ expectText(text: string, fuzzy?: boolean, elementType?: string): Promise<void>;
42
+ expectValue(selector: string, val: string, elementType?: string): Promise<void>;
43
+ expectTranscript(expectedText: string): Promise<void>;
44
+ getAttribute(selector: string, attr: string, elementType?: string): Promise<string | null>;
45
+ private isRawCssSelector;
46
+ private buildHeuristicSelector;
47
+ private getLocator;
48
+ clearHUD(): Promise<void>;
49
+ updateHUD(id: string, text: string, status: string, error?: string): Promise<void>;
50
+ shutdown(videoTargetPath?: string): Promise<void>;
51
+ }