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.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/adapters/api/undici.d.ts +46 -0
- package/dist/adapters/api/undici.js +118 -0
- package/dist/adapters/web/playwright.d.ts +51 -0
- package/dist/adapters/web/playwright.js +1108 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +77 -0
- package/dist/cli/commands/report.d.ts +1 -0
- package/dist/cli/commands/report.js +78 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +158 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +39 -0
- package/dist/parser/lexer.d.ts +29 -0
- package/dist/parser/lexer.js +425 -0
- package/dist/parser/parser.d.ts +41 -0
- package/dist/parser/parser.js +573 -0
- package/dist/parser/resolver.d.ts +5 -0
- package/dist/parser/resolver.js +19 -0
- package/dist/parser/validator.d.ts +4 -0
- package/dist/parser/validator.js +37 -0
- package/dist/parser/variable_resolver.d.ts +7 -0
- package/dist/parser/variable_resolver.js +33 -0
- package/dist/runner/data_generators.d.ts +55 -0
- package/dist/runner/data_generators.js +233 -0
- package/dist/runner/dom_stripper.d.ts +4 -0
- package/dist/runner/dom_stripper.js +61 -0
- package/dist/runner/events.d.ts +15 -0
- package/dist/runner/events.js +20 -0
- package/dist/runner/flow_orchestrator.d.ts +15 -0
- package/dist/runner/flow_orchestrator.js +216 -0
- package/dist/runner/flow_worker.d.ts +53 -0
- package/dist/runner/flow_worker.js +1112 -0
- package/dist/runner/persistent_vault.d.ts +12 -0
- package/dist/runner/persistent_vault.js +84 -0
- package/dist/runner/plugin_manager.d.ts +13 -0
- package/dist/runner/plugin_manager.js +164 -0
- package/dist/runner/temporary_store.d.ts +8 -0
- package/dist/runner/temporary_store.js +22 -0
- package/dist/runner/worker_entry.d.ts +1 -0
- package/dist/runner/worker_entry.js +63 -0
- package/dist/sdk/api_proxy.d.ts +8 -0
- package/dist/sdk/api_proxy.js +28 -0
- package/dist/sdk/db_proxy.d.ts +3 -0
- package/dist/sdk/db_proxy.js +15 -0
- package/dist/sessions/manager.d.ts +17 -0
- package/dist/sessions/manager.js +74 -0
- package/dist/sessions/types.d.ts +39 -0
- package/dist/sessions/types.js +2 -0
- package/dist/sessions/vault.d.ts +8 -0
- package/dist/sessions/vault.js +69 -0
- package/dist/test_data/commerce.json +341 -0
- package/dist/test_data/geography.json +1298 -0
- package/dist/test_data/identities.json +1420 -0
- package/dist/test_data/phone_data.json +27 -0
- package/dist/test_data/utility.json +246 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/redactor.d.ts +5 -0
- package/dist/utils/redactor.js +53 -0
- package/package.json +54 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class PersistentVault {
|
|
2
|
+
vaultPath: string;
|
|
3
|
+
data: Record<string, any>;
|
|
4
|
+
constructor(projectRoot?: string);
|
|
5
|
+
initialiseVault(): void;
|
|
6
|
+
save(key: string, value: any): void;
|
|
7
|
+
set(key: string, value: any): void;
|
|
8
|
+
get(key: string): any;
|
|
9
|
+
syncToDisk(): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
getAll(): Record<string, any>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PersistentVault = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class PersistentVault {
|
|
40
|
+
vaultPath;
|
|
41
|
+
data = {};
|
|
42
|
+
constructor(projectRoot = process.cwd()) {
|
|
43
|
+
this.vaultPath = path.join(projectRoot, ".flowstride", "vault.json");
|
|
44
|
+
this.initialiseVault();
|
|
45
|
+
}
|
|
46
|
+
initialiseVault() {
|
|
47
|
+
const dir = path.dirname(this.vaultPath);
|
|
48
|
+
if (!fs.existsSync(dir)) {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
if (fs.existsSync(this.vaultPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const rawData = fs.readFileSync(this.vaultPath, "utf8");
|
|
54
|
+
this.data = JSON.parse(rawData);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
this.data = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
save(key, value) {
|
|
62
|
+
this.data[key] = value;
|
|
63
|
+
this.syncToDisk();
|
|
64
|
+
}
|
|
65
|
+
set(key, value) {
|
|
66
|
+
this.save(key, value);
|
|
67
|
+
}
|
|
68
|
+
get(key) {
|
|
69
|
+
return this.data[key];
|
|
70
|
+
}
|
|
71
|
+
syncToDisk() {
|
|
72
|
+
fs.writeFileSync(this.vaultPath, JSON.stringify(this.data, null, 2));
|
|
73
|
+
}
|
|
74
|
+
clear() {
|
|
75
|
+
this.data = {};
|
|
76
|
+
if (fs.existsSync(this.vaultPath)) {
|
|
77
|
+
fs.unlinkSync(this.vaultPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
getAll() {
|
|
81
|
+
return { ...this.data };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.PersistentVault = PersistentVault;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FlowstrideConfig } from "../types";
|
|
2
|
+
import { ApiProxy } from "../sdk/api_proxy";
|
|
3
|
+
import { TemporaryStore } from "./temporary_store";
|
|
4
|
+
import { WebAdapter } from "../adapters/web/playwright";
|
|
5
|
+
export declare class PluginManager {
|
|
6
|
+
private config;
|
|
7
|
+
private web;
|
|
8
|
+
private store;
|
|
9
|
+
private apiProxy;
|
|
10
|
+
private dbProxy;
|
|
11
|
+
constructor(config: FlowstrideConfig, web: WebAdapter, store: TemporaryStore, apiProxy: ApiProxy);
|
|
12
|
+
execute(actionName: string, args: any, saveAs?: string): Promise<any>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PluginManager = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const db_proxy_1 = require("../sdk/db_proxy");
|
|
39
|
+
class PluginManager {
|
|
40
|
+
config;
|
|
41
|
+
web;
|
|
42
|
+
store;
|
|
43
|
+
apiProxy;
|
|
44
|
+
dbProxy;
|
|
45
|
+
constructor(config, web, store, apiProxy) {
|
|
46
|
+
this.config = config;
|
|
47
|
+
this.web = web;
|
|
48
|
+
this.store = store;
|
|
49
|
+
this.apiProxy = apiProxy;
|
|
50
|
+
this.dbProxy = new db_proxy_1.DbProxy();
|
|
51
|
+
}
|
|
52
|
+
async execute(actionName, args, saveAs) {
|
|
53
|
+
const lastDotIndex = actionName.lastIndexOf(".");
|
|
54
|
+
if (lastDotIndex === -1) {
|
|
55
|
+
throw new Error(`Invalid plugin action format: "${actionName}". Expected "path.method"`);
|
|
56
|
+
}
|
|
57
|
+
const pluginFile = actionName.substring(0, lastDotIndex);
|
|
58
|
+
const functionName = actionName.substring(lastDotIndex + 1);
|
|
59
|
+
let fullPath;
|
|
60
|
+
if (pluginFile.includes("/") || pluginFile.includes("\\")) {
|
|
61
|
+
fullPath = path.resolve(process.cwd(), `${pluginFile}.ts`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const pluginsPath = this.config.pluginsDir || path.join(process.cwd(), "plugins");
|
|
65
|
+
fullPath = path.resolve(pluginsPath, `${pluginFile}.ts`);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
let module;
|
|
69
|
+
if (fullPath.endsWith(".ts")) {
|
|
70
|
+
require("ts-node").register({
|
|
71
|
+
transpileOnly: true,
|
|
72
|
+
compilerOptions: {
|
|
73
|
+
module: "commonjs",
|
|
74
|
+
esModuleInterop: true,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
module = require(fullPath);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
module = await import(fullPath);
|
|
81
|
+
}
|
|
82
|
+
const action = module[functionName];
|
|
83
|
+
if (!action) {
|
|
84
|
+
throw new Error(`Plugin function "${functionName}" not found in ${fullPath}`);
|
|
85
|
+
}
|
|
86
|
+
const sdk = {
|
|
87
|
+
api: {
|
|
88
|
+
get: (url, headers) => this.apiProxy.get(url, headers),
|
|
89
|
+
post: (url, body, headers) => this.apiProxy.post(url, body, headers),
|
|
90
|
+
put: (url, body, headers) => this.apiProxy.put(url, body, headers),
|
|
91
|
+
delete: (url, headers) => this.apiProxy.delete(url, headers),
|
|
92
|
+
},
|
|
93
|
+
ui: {
|
|
94
|
+
click: (s) => this.web.click(s),
|
|
95
|
+
type: (s, t) => this.web.type(s, t),
|
|
96
|
+
isVisible: async (s) => {
|
|
97
|
+
try {
|
|
98
|
+
await this.web.expectVisible(s);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
getText: async (s) => {
|
|
106
|
+
const page = this.web.getPage();
|
|
107
|
+
const text = await page.locator(s).first().textContent();
|
|
108
|
+
return text ? text.trim() : "";
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
state: {
|
|
112
|
+
get: (k) => this.store.get(k),
|
|
113
|
+
set: (k, v) => this.store.set(k, v),
|
|
114
|
+
},
|
|
115
|
+
env: {
|
|
116
|
+
get: (k) => process.env[k],
|
|
117
|
+
},
|
|
118
|
+
db: this.dbProxy,
|
|
119
|
+
otp: {
|
|
120
|
+
generate: (secret) => `OTP_FOR_${secret}`,
|
|
121
|
+
},
|
|
122
|
+
log: (m) => console.log(`[Plugin Log]: ${m}`),
|
|
123
|
+
expect: {
|
|
124
|
+
visible: async (s) => {
|
|
125
|
+
try {
|
|
126
|
+
await this.web.expectVisible(s);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error(`Assertion Failed: Expected element "${s}" to be visible.`);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
equals: (actual, expected, message) => {
|
|
134
|
+
if (actual !== expected) {
|
|
135
|
+
throw new Error(message ||
|
|
136
|
+
`Assertion Failed: Expected "${expected}", but got "${actual}".`);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
contains: (actual, expected, message) => {
|
|
140
|
+
if (!actual.includes(expected)) {
|
|
141
|
+
throw new Error(message ||
|
|
142
|
+
`Assertion Failed: Expected "${actual}" to contain "${expected}".`);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
utils: {
|
|
147
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
148
|
+
generateId: (length = 8) => Math.random()
|
|
149
|
+
.toString(36)
|
|
150
|
+
.substring(2, 2 + length),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const result = await action(sdk, args);
|
|
154
|
+
if (saveAs) {
|
|
155
|
+
this.store.set(saveAs, result);
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new Error(`Plugin Execution Failed (${actionName}): ${error.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.PluginManager = PluginManager;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TemporaryStore = void 0;
|
|
4
|
+
class TemporaryStore {
|
|
5
|
+
store = new Map();
|
|
6
|
+
set(key, value) {
|
|
7
|
+
this.store.set(key, value);
|
|
8
|
+
}
|
|
9
|
+
get(key) {
|
|
10
|
+
return this.store.get(key);
|
|
11
|
+
}
|
|
12
|
+
has(key) {
|
|
13
|
+
return this.store.has(key);
|
|
14
|
+
}
|
|
15
|
+
clear() {
|
|
16
|
+
this.store.clear();
|
|
17
|
+
}
|
|
18
|
+
getAll() {
|
|
19
|
+
return Object.fromEntries(this.store);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.TemporaryStore = TemporaryStore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const flow_worker_1 = require("./flow_worker");
|
|
4
|
+
const playwright_1 = require("../adapters/web/playwright");
|
|
5
|
+
const undici_1 = require("../adapters/api/undici");
|
|
6
|
+
const temporary_store_1 = require("./temporary_store");
|
|
7
|
+
const manager_1 = require("../sessions/manager");
|
|
8
|
+
const persistent_vault_1 = require("./persistent_vault");
|
|
9
|
+
const plugin_manager_1 = require("./plugin_manager");
|
|
10
|
+
const api_proxy_1 = require("../sdk/api_proxy");
|
|
11
|
+
process.on("message", async (message) => {
|
|
12
|
+
if (message.type === "START_RUN") {
|
|
13
|
+
const { files, config } = message.payload;
|
|
14
|
+
try {
|
|
15
|
+
const web = new playwright_1.WebAdapter(config);
|
|
16
|
+
const api = new undici_1.ApiAdapter(config);
|
|
17
|
+
const store = new temporary_store_1.TemporaryStore();
|
|
18
|
+
const apiProxy = new api_proxy_1.ApiProxy(api);
|
|
19
|
+
const pluginManager = new plugin_manager_1.PluginManager(config, web, store, apiProxy);
|
|
20
|
+
const vault = new persistent_vault_1.PersistentVault(process.cwd());
|
|
21
|
+
const sessions = new manager_1.SessionManager(web, api, vault);
|
|
22
|
+
const worker = new flow_worker_1.FlowWorker(config, web, api, store, sessions, pluginManager);
|
|
23
|
+
const workerResults = [];
|
|
24
|
+
let currentFileName = "Unknown";
|
|
25
|
+
let currentScenarioName = "Unknown";
|
|
26
|
+
worker.on("worker_event", (data) => {
|
|
27
|
+
if (data.type === "RUN_START") {
|
|
28
|
+
currentFileName = data.payload.fileName;
|
|
29
|
+
currentScenarioName = data.payload.scenarioName;
|
|
30
|
+
}
|
|
31
|
+
if (data.type === "STEP_UPDATE") {
|
|
32
|
+
let fileGroup = workerResults.find((r) => r.fileName === currentFileName);
|
|
33
|
+
if (!fileGroup) {
|
|
34
|
+
fileGroup = { fileName: currentFileName, steps: [] };
|
|
35
|
+
workerResults.push(fileGroup);
|
|
36
|
+
}
|
|
37
|
+
fileGroup.steps.push({
|
|
38
|
+
id: data.payload.id,
|
|
39
|
+
status: data.payload.status,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (process.send) {
|
|
43
|
+
process.send(data);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
await worker.runBatch(files);
|
|
47
|
+
if (process.send) {
|
|
48
|
+
process.send({
|
|
49
|
+
type: "WORKER_COMPLETE",
|
|
50
|
+
payload: { status: "success", results: workerResults },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (process.send) {
|
|
56
|
+
process.send({
|
|
57
|
+
type: "WORKER_COMPLETE",
|
|
58
|
+
payload: { status: "error", error: error.message },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ApiAdapter } from "../adapters/api/undici";
|
|
2
|
+
export declare class ApiProxy {
|
|
3
|
+
private adapter;
|
|
4
|
+
constructor(adapter: ApiAdapter);
|
|
5
|
+
private formatHeaders;
|
|
6
|
+
get(url: string, headers?: Record<string, string>): Promise<any>;
|
|
7
|
+
post(url: string, body: any, headers?: Record<string, string>): Promise<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiProxy = void 0;
|
|
4
|
+
class ApiProxy {
|
|
5
|
+
adapter;
|
|
6
|
+
constructor(adapter) {
|
|
7
|
+
this.adapter = adapter;
|
|
8
|
+
}
|
|
9
|
+
formatHeaders(headers) {
|
|
10
|
+
if (!headers)
|
|
11
|
+
return [];
|
|
12
|
+
return Object.entries(headers).map(([key, value]) => ({ key, value }));
|
|
13
|
+
}
|
|
14
|
+
async get(url, headers) {
|
|
15
|
+
const formattedHeaders = this.formatHeaders(headers);
|
|
16
|
+
await this.adapter.get(url, formattedHeaders);
|
|
17
|
+
const state = this.adapter.getState();
|
|
18
|
+
return state.body;
|
|
19
|
+
}
|
|
20
|
+
async post(url, body, headers) {
|
|
21
|
+
const formattedHeaders = this.formatHeaders(headers);
|
|
22
|
+
const stringifiedBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
23
|
+
await this.adapter.post(url, formattedHeaders, stringifiedBody);
|
|
24
|
+
const state = this.adapter.getState();
|
|
25
|
+
return state.body;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ApiProxy = ApiProxy;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbProxy = void 0;
|
|
4
|
+
class DbProxy {
|
|
5
|
+
connect(connectionString) {
|
|
6
|
+
console.log(`Plugin establishing secure connection to: ${connectionString}`);
|
|
7
|
+
return {
|
|
8
|
+
queryOne: async (sql, params) => {
|
|
9
|
+
console.log(`Executing safe query: ${sql}`);
|
|
10
|
+
return {};
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.DbProxy = DbProxy;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { WebAdapter } from '../adapters/web/playwright';
|
|
2
|
+
import { ApiAdapter } from '../adapters/api/undici';
|
|
3
|
+
import { PersistentVault } from '../runner/persistent_vault';
|
|
4
|
+
import { SessionDefinition } from '../types';
|
|
5
|
+
export declare class SessionManager {
|
|
6
|
+
private web;
|
|
7
|
+
private api;
|
|
8
|
+
private vault;
|
|
9
|
+
private activeSession;
|
|
10
|
+
private definitions;
|
|
11
|
+
constructor(web: WebAdapter, api: ApiAdapter, vault: PersistentVault);
|
|
12
|
+
register(definition: SessionDefinition): void;
|
|
13
|
+
private handleUnauthorized;
|
|
14
|
+
private updateSessionData;
|
|
15
|
+
useSession(name: string): Promise<void>;
|
|
16
|
+
saveCurrentState(name: string, persist: boolean): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionManager = void 0;
|
|
4
|
+
class SessionManager {
|
|
5
|
+
web;
|
|
6
|
+
api;
|
|
7
|
+
vault;
|
|
8
|
+
activeSession = null;
|
|
9
|
+
definitions = new Map();
|
|
10
|
+
constructor(web, api, vault) {
|
|
11
|
+
this.web = web;
|
|
12
|
+
this.api = api;
|
|
13
|
+
this.vault = vault;
|
|
14
|
+
this.api.setUnauthorizedHandler(() => this.handleUnauthorized());
|
|
15
|
+
}
|
|
16
|
+
register(definition) {
|
|
17
|
+
this.definitions.set(definition.name, definition);
|
|
18
|
+
}
|
|
19
|
+
async handleUnauthorized() {
|
|
20
|
+
if (!this.activeSession)
|
|
21
|
+
return;
|
|
22
|
+
const definition = this.definitions.get(this.activeSession.name);
|
|
23
|
+
if (definition?.refresh) {
|
|
24
|
+
const newTokens = await definition.refresh.action(this.activeSession.tokens);
|
|
25
|
+
await this.updateSessionData(this.activeSession.name, newTokens);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async updateSessionData(name, tokens) {
|
|
29
|
+
const data = this.vault.get(`session_${name}`) || {
|
|
30
|
+
name,
|
|
31
|
+
createdAt: Date.now(),
|
|
32
|
+
persist: true,
|
|
33
|
+
};
|
|
34
|
+
data.tokens = tokens;
|
|
35
|
+
data.lastUsedAt = Date.now();
|
|
36
|
+
this.vault.save(`session_${name}`, data);
|
|
37
|
+
this.activeSession = data;
|
|
38
|
+
await this.web.injectStorage(data.cookies, data.localStorage);
|
|
39
|
+
const definition = this.definitions.get(name);
|
|
40
|
+
if (definition?.inject?.api) {
|
|
41
|
+
const { headerName, template } = definition.inject.api;
|
|
42
|
+
const token = tokens.accessToken || tokens.idToken;
|
|
43
|
+
this.api.setGlobalHeader(headerName, template.replace('{{token}}', token));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async useSession(name) {
|
|
47
|
+
const definition = this.definitions.get(name);
|
|
48
|
+
if (!definition)
|
|
49
|
+
throw new Error(`Session "${name}" not defined.`);
|
|
50
|
+
let data = this.vault.get(`session_${name}`);
|
|
51
|
+
if (!data) {
|
|
52
|
+
const tokens = await definition.acquire();
|
|
53
|
+
await this.updateSessionData(name, tokens);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.activeSession = data;
|
|
57
|
+
await this.updateSessionData(name, data.tokens);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async saveCurrentState(name, persist) {
|
|
61
|
+
const state = await this.web.getState();
|
|
62
|
+
const data = {
|
|
63
|
+
name,
|
|
64
|
+
tokens: this.activeSession?.tokens || {},
|
|
65
|
+
cookies: state.cookies,
|
|
66
|
+
localStorage: state.localStorage,
|
|
67
|
+
createdAt: Date.now(),
|
|
68
|
+
lastUsedAt: Date.now(),
|
|
69
|
+
persist,
|
|
70
|
+
};
|
|
71
|
+
this.vault.save(`session_${name}`, data);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface FlowstrideConfig {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
timeout: number;
|
|
4
|
+
headless: boolean;
|
|
5
|
+
network?: {
|
|
6
|
+
include?: string[];
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
backgroundPatterns?: string[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface SessionDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
acquire: () => Promise<Record<string, any>>;
|
|
14
|
+
inject?: {
|
|
15
|
+
api?: Record<string, string>;
|
|
16
|
+
ui?: {
|
|
17
|
+
localStorage?: Record<string, string>;
|
|
18
|
+
cookies?: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
value: string;
|
|
21
|
+
domain: string;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
refresh?: {
|
|
26
|
+
onStatus: number[];
|
|
27
|
+
action: () => Promise<Record<string, any>>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface Scenario {
|
|
31
|
+
name: string;
|
|
32
|
+
steps: any[];
|
|
33
|
+
}
|
|
34
|
+
export interface ScenarioResult {
|
|
35
|
+
name: string;
|
|
36
|
+
status: "PASS" | "FAIL" | "ERROR" | "SKIPPED" | "NOT TESTED";
|
|
37
|
+
duration: number;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PersistentVault = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class PersistentVault {
|
|
40
|
+
vaultPath;
|
|
41
|
+
constructor() {
|
|
42
|
+
this.vaultPath = path.join(process.cwd(), ".flowstride", "vault");
|
|
43
|
+
this.ensureVaultExists();
|
|
44
|
+
}
|
|
45
|
+
ensureVaultExists() {
|
|
46
|
+
if (!fs.existsSync(this.vaultPath)) {
|
|
47
|
+
fs.mkdirSync(this.vaultPath, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
save(name, data) {
|
|
51
|
+
const filePath = path.join(this.vaultPath, `${name}.json`);
|
|
52
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
53
|
+
}
|
|
54
|
+
load(name) {
|
|
55
|
+
const filePath = path.join(this.vaultPath, `${name}.json`);
|
|
56
|
+
if (!fs.existsSync(filePath))
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exists(name) {
|
|
66
|
+
return fs.existsSync(path.join(this.vaultPath, `${name}.json`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.PersistentVault = PersistentVault;
|