pw-automation-framework 2.0.1
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/README.md +93 -0
- package/bin/lexxit-automation-framework.js +427 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +26 -0
- package/dist/app.js.map +1 -0
- package/dist/controllers/controller.d.ts +57 -0
- package/dist/controllers/controller.js +263 -0
- package/dist/controllers/controller.js.map +1 -0
- package/dist/core/BrowserManager.d.ts +46 -0
- package/dist/core/BrowserManager.js +377 -0
- package/dist/core/BrowserManager.js.map +1 -0
- package/dist/core/PlaywrightEngine.d.ts +16 -0
- package/dist/core/PlaywrightEngine.js +246 -0
- package/dist/core/PlaywrightEngine.js.map +1 -0
- package/dist/core/ScreenshotManager.d.ts +10 -0
- package/dist/core/ScreenshotManager.js +28 -0
- package/dist/core/ScreenshotManager.js.map +1 -0
- package/dist/core/TestData.d.ts +12 -0
- package/dist/core/TestData.js +29 -0
- package/dist/core/TestData.js.map +1 -0
- package/dist/core/TestExecutor.d.ts +16 -0
- package/dist/core/TestExecutor.js +355 -0
- package/dist/core/TestExecutor.js.map +1 -0
- package/dist/core/handlers/AllHandlers.d.ts +116 -0
- package/dist/core/handlers/AllHandlers.js +648 -0
- package/dist/core/handlers/AllHandlers.js.map +1 -0
- package/dist/core/handlers/BaseHandler.d.ts +16 -0
- package/dist/core/handlers/BaseHandler.js +27 -0
- package/dist/core/handlers/BaseHandler.js.map +1 -0
- package/dist/core/handlers/ClickHandler.d.ts +34 -0
- package/dist/core/handlers/ClickHandler.js +359 -0
- package/dist/core/handlers/ClickHandler.js.map +1 -0
- package/dist/core/handlers/CustomCodeHandler.d.ts +35 -0
- package/dist/core/handlers/CustomCodeHandler.js +102 -0
- package/dist/core/handlers/CustomCodeHandler.js.map +1 -0
- package/dist/core/handlers/DropdownHandler.d.ts +43 -0
- package/dist/core/handlers/DropdownHandler.js +304 -0
- package/dist/core/handlers/DropdownHandler.js.map +1 -0
- package/dist/core/handlers/InputHandler.d.ts +24 -0
- package/dist/core/handlers/InputHandler.js +197 -0
- package/dist/core/handlers/InputHandler.js.map +1 -0
- package/dist/core/registry/ActionRegistry.d.ts +8 -0
- package/dist/core/registry/ActionRegistry.js +35 -0
- package/dist/core/registry/ActionRegistry.js.map +1 -0
- package/dist/installer/frameworkLauncher.d.ts +31 -0
- package/dist/installer/frameworkLauncher.js +198 -0
- package/dist/installer/frameworkLauncher.js.map +1 -0
- package/dist/queue/ExecutionQueue.d.ts +52 -0
- package/dist/queue/ExecutionQueue.js +175 -0
- package/dist/queue/ExecutionQueue.js.map +1 -0
- package/dist/routes/api.routes.d.ts +2 -0
- package/dist/routes/api.routes.js +16 -0
- package/dist/routes/api.routes.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +30 -0
- package/dist/server.js.map +1 -0
- package/dist/types/types.d.ts +135 -0
- package/dist/types/types.js +4 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/elementHighlight.d.ts +35 -0
- package/dist/utils/elementHighlight.js +136 -0
- package/dist/utils/elementHighlight.js.map +1 -0
- package/dist/utils/locatorHelper.d.ts +7 -0
- package/dist/utils/locatorHelper.js +53 -0
- package/dist/utils/locatorHelper.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +4 -0
- package/dist/utils/response.js +25 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/responseFormatter.d.ts +78 -0
- package/dist/utils/responseFormatter.js +123 -0
- package/dist/utils/responseFormatter.js.map +1 -0
- package/dist/utils/sseManager.d.ts +32 -0
- package/dist/utils/sseManager.js +122 -0
- package/dist/utils/sseManager.js.map +1 -0
- package/lexxit-automation-framework-2.0.0.tgz +0 -0
- package/npmignore +5 -0
- package/package.json +36 -0
- package/scripts/postinstall.js +52 -0
- package/src/app.ts +27 -0
- package/src/controllers/controller.ts +282 -0
- package/src/core/BrowserManager.ts +398 -0
- package/src/core/PlaywrightEngine.ts +371 -0
- package/src/core/ScreenshotManager.ts +25 -0
- package/src/core/TestData.ts +25 -0
- package/src/core/TestExecutor.ts +436 -0
- package/src/core/handlers/AllHandlers.ts +626 -0
- package/src/core/handlers/BaseHandler.ts +41 -0
- package/src/core/handlers/ClickHandler.ts +482 -0
- package/src/core/handlers/CustomCodeHandler.ts +123 -0
- package/src/core/handlers/DropdownHandler.ts +438 -0
- package/src/core/handlers/InputHandler.ts +192 -0
- package/src/core/registry/ActionRegistry.ts +31 -0
- package/src/installer/frameworkLauncher.ts +242 -0
- package/src/installer/install.sh +107 -0
- package/src/public/dashboard.html +540 -0
- package/src/public/queue-monitor.html +190 -0
- package/src/queue/ExecutionQueue.ts +200 -0
- package/src/routes/api.routes.ts +16 -0
- package/src/server.ts +29 -0
- package/src/types/types.ts +169 -0
- package/src/utils/elementHighlight.ts +174 -0
- package/src/utils/locatorHelper.ts +49 -0
- package/src/utils/logger.ts +40 -0
- package/src/utils/response.ts +27 -0
- package/src/utils/responseFormatter.ts +167 -0
- package/src/utils/sseManager.ts +127 -0
- package/tsconfig.json +18 -0
- package/videos/fb1b94b6-6639-4c9a-82bb-63572606f403/page@5bd5c6c8b62baa700e9810cdd64f5c49.webm +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// import { ActionResponse, ParsedStep, TestCaseConfig } from "../types/types";
|
|
2
|
+
// import { ActionRegistry } from "./registry/ActionRegistry";
|
|
3
|
+
// import { BrowserManager } from "./BrowserManager";
|
|
4
|
+
// import { InputHandler } from "./handlers/InputHandler";
|
|
5
|
+
// import { ClickHandler } from "./handlers/ClickHandler";
|
|
6
|
+
// import { DropdownHandler } from "./handlers/DropdownHandler";
|
|
7
|
+
// import { CustomCodeHandler } from "./handlers/CustomCodeHandler";
|
|
8
|
+
// import { AssertionHandler, WaitHandler, KeyboardHandler, CheckboxHandler, TableHandler } from "./handlers/AllHandlers";
|
|
9
|
+
// import { Logger } from "../utils/logger";
|
|
10
|
+
|
|
11
|
+
// export class PlaywrightEngine {
|
|
12
|
+
// private browserManager: BrowserManager;
|
|
13
|
+
// private registry: ActionRegistry;
|
|
14
|
+
// private tConfig: TestCaseConfig;
|
|
15
|
+
// private log: Logger;
|
|
16
|
+
|
|
17
|
+
// constructor(tConfig: TestCaseConfig) {
|
|
18
|
+
// this.tConfig = tConfig;
|
|
19
|
+
// this.browserManager = new BrowserManager(tConfig);
|
|
20
|
+
// this.registry = new ActionRegistry();
|
|
21
|
+
// this.log = Logger.create("PlaywrightEngine", tConfig.executionId);
|
|
22
|
+
// this.registerBrowserActions();
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
// // ─── Script Parser ─────────────────────────────────────────────────────────
|
|
26
|
+
// parsedStepScript(script: string | undefined | null): ParsedStep | null {
|
|
27
|
+
// // ✅ Guard: must be a non-empty string — no crash on undefined
|
|
28
|
+
// if (!script || typeof script !== "string" || !script.trim()) return null;
|
|
29
|
+
|
|
30
|
+
// const match = script.match(/tSetup\.(\w+)\(([\s\S]*)\)/i);
|
|
31
|
+
// if (!match) return null;
|
|
32
|
+
|
|
33
|
+
// const action = match[1];
|
|
34
|
+
// const args: string[] = [];
|
|
35
|
+
// const rx = /'((?:[^'\\]|\\.)*)'/g;
|
|
36
|
+
// let m: RegExpExecArray | null;
|
|
37
|
+
// while ((m = rx.exec(match[2])) !== null) {
|
|
38
|
+
// args.push(m[1].replace(/\\'/g, "'"));
|
|
39
|
+
// }
|
|
40
|
+
// return { action, args };
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
// async execute(script: string | undefined | null,label:string | undefined | null): Promise<ActionResponse | undefined> {
|
|
44
|
+
// // ✅ Guard: missing step_script — actionable error instead of crash
|
|
45
|
+
// if (!script || typeof script !== "string" || !script.trim()) {
|
|
46
|
+
// const msg =
|
|
47
|
+
// "step_script is missing or empty. " +
|
|
48
|
+
// "Your steps contain only step_name and value — they are RAW database objects. " +
|
|
49
|
+
// "You must call prepareSteps(scriptUid) in your main app BEFORE sending to the framework. " +
|
|
50
|
+
// "prepareSteps() resolves obj_uid → locator → step_script for each step.";
|
|
51
|
+
// this.log.error(msg);
|
|
52
|
+
// return { status: "Fail", comments: msg };
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// const parsed = this.parsedStepScript(script);
|
|
56
|
+
// if (!parsed) {
|
|
57
|
+
// this.log.error(`Cannot parse: "${script}"`);
|
|
58
|
+
// return {
|
|
59
|
+
// status: "Fail",
|
|
60
|
+
// comments: `Invalid script format: "${script}". Expected: tSetup.actionName('arg1', ...)`,
|
|
61
|
+
// };
|
|
62
|
+
// }
|
|
63
|
+
|
|
64
|
+
// this.log.info(`→ ${parsed.action}(${parsed.args.join(", ")})`);
|
|
65
|
+
|
|
66
|
+
// const result = await this.registry.execute(parsed.action, parsed.args, label ?? undefined);
|
|
67
|
+
|
|
68
|
+
// if (!result) {
|
|
69
|
+
// return {
|
|
70
|
+
// status: "Fail",
|
|
71
|
+
// comments: `Unknown action: "${parsed.action}". Call GET /api/actions to list all available actions.`,
|
|
72
|
+
// };
|
|
73
|
+
// }
|
|
74
|
+
// return result;
|
|
75
|
+
// }
|
|
76
|
+
|
|
77
|
+
// private registerBrowserActions(): void {
|
|
78
|
+
// const bm = this.browserManager;
|
|
79
|
+
// this.registry.register({
|
|
80
|
+
// getActions: () => ({
|
|
81
|
+
// openbrowser: async (a) => {
|
|
82
|
+
// const result = await bm.openBrowser(a[0] || "chromium");
|
|
83
|
+
// if (result.status === "Pass") this.registerPageHandlers();
|
|
84
|
+
// return result;
|
|
85
|
+
// },
|
|
86
|
+
// closebrowser: () => bm.closeBrowser(),
|
|
87
|
+
// navigatetourl: (a) => bm.navigateToURL(a[0]),
|
|
88
|
+
// navigateback: () => bm.navigateBack(),
|
|
89
|
+
// navigateforward: () => bm.navigateForward(),
|
|
90
|
+
// refreshpage: () => bm.refreshPage(),
|
|
91
|
+
// gettitle: (a) => bm.getTitle(a[0]),
|
|
92
|
+
// verifytitle: (a) => bm.verifyTitle(a[0]),
|
|
93
|
+
// verifypartialtitle: (a) => bm.verifyPartialTitle(a[0]),
|
|
94
|
+
// getcurrenturl: () => bm.getCurrentURL(),
|
|
95
|
+
// maximizewindow: () => bm.maximizeWindow(),
|
|
96
|
+
// setwindowsize: (a) => bm.setWindowSize(a[0], a[1]),
|
|
97
|
+
// switchtotab: (a) => bm.switchToTab(a[0]),
|
|
98
|
+
// opennewtab: (a) => bm.openNewTab(a[0]),
|
|
99
|
+
// closecurrenttab: () => bm.closeCurrentTab(),
|
|
100
|
+
// acceptalert: (a) => bm.acceptAlert(a[0]),
|
|
101
|
+
// dismissalert: () => bm.dismissAlert(),
|
|
102
|
+
// getalerttext: (a) => bm.getAlertText(a[0]),
|
|
103
|
+
// switchtoframe: (a) => bm.switchToFrame(a[0]),
|
|
104
|
+
// executescript: (a) => bm.executeScript(a[0]),
|
|
105
|
+
// scrolltobottom: () => bm.scrollToBottom(),
|
|
106
|
+
// scrolltotop: () => bm.scrollToTop(),
|
|
107
|
+
// deleteallcookies: () => bm.deleteAllCookies(),
|
|
108
|
+
// clearlocalstorage: () => bm.clearLocalStorage(),
|
|
109
|
+
// clearsessionstorage: () => bm.clearSessionStorage(),
|
|
110
|
+
// takescreenshot: () => bm.takeScreenshot(),
|
|
111
|
+
// }),
|
|
112
|
+
// });
|
|
113
|
+
// }
|
|
114
|
+
|
|
115
|
+
// private registerPageHandlers(): void {
|
|
116
|
+
// const page = this.browserManager.getPage();
|
|
117
|
+
// const cfg = this.tConfig;
|
|
118
|
+
// this.registry.register(new InputHandler(cfg, page));
|
|
119
|
+
// this.registry.register(new ClickHandler(cfg, page));
|
|
120
|
+
// this.registry.register(new DropdownHandler(cfg, page));
|
|
121
|
+
// this.registry.register(new AssertionHandler(cfg, page));
|
|
122
|
+
// this.registry.register(new WaitHandler(cfg, page));
|
|
123
|
+
// this.registry.register(new KeyboardHandler(cfg, page));
|
|
124
|
+
// this.registry.register(new CheckboxHandler(cfg, page));
|
|
125
|
+
// this.registry.register(new TableHandler(cfg, page));
|
|
126
|
+
|
|
127
|
+
// this.registry.register(new CustomCodeHandler(cfg, page));
|
|
128
|
+
// this.log.info(`Registered ${this.registry.listActions().length} actions`);
|
|
129
|
+
// }
|
|
130
|
+
|
|
131
|
+
// async forceClose(): Promise<void> {
|
|
132
|
+
// try { await this.browserManager.closeBrowser(); } catch { /* ignore */ }
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
// getRegisteredActions(): string[] { return this.registry.listActions(); }
|
|
136
|
+
// }
|
|
137
|
+
import { ActionResponse, ParsedStep, TestCaseConfig } from "../types/types";
|
|
138
|
+
import { ActionRegistry } from "./registry/ActionRegistry";
|
|
139
|
+
import { BrowserManager } from "./BrowserManager";
|
|
140
|
+
import { InputHandler } from "./handlers/InputHandler";
|
|
141
|
+
import { ClickHandler } from "./handlers/ClickHandler";
|
|
142
|
+
import { DropdownHandler } from "./handlers/DropdownHandler";
|
|
143
|
+
import { CustomCodeHandler } from "./handlers/CustomCodeHandler";
|
|
144
|
+
import { AssertionHandler, WaitHandler, KeyboardHandler, CheckboxHandler, TableHandler } from "./handlers/AllHandlers";
|
|
145
|
+
import { Logger } from "../utils/logger";
|
|
146
|
+
|
|
147
|
+
export class PlaywrightEngine {
|
|
148
|
+
private browserManager: BrowserManager;
|
|
149
|
+
private registry: ActionRegistry;
|
|
150
|
+
private tConfig: TestCaseConfig;
|
|
151
|
+
private log: Logger;
|
|
152
|
+
|
|
153
|
+
constructor(tConfig: TestCaseConfig) {
|
|
154
|
+
this.tConfig = tConfig;
|
|
155
|
+
this.browserManager = new BrowserManager(tConfig);
|
|
156
|
+
this.registry = new ActionRegistry();
|
|
157
|
+
this.log = Logger.create("PlaywrightEngine", tConfig.executionId);
|
|
158
|
+
this.registerBrowserActions();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Script Parser ─────────────────────────────────────────────────────────
|
|
162
|
+
parsedStepScript(script: string | undefined | null): ParsedStep | null {
|
|
163
|
+
// ✅ Guard: must be a non-empty string — no crash on undefined
|
|
164
|
+
if (!script || typeof script !== "string" || !script.trim()) return null;
|
|
165
|
+
|
|
166
|
+
const match = script.match(/tSetup\.(\w+)\(([\s\S]*)\)/i);
|
|
167
|
+
if (!match) return null;
|
|
168
|
+
|
|
169
|
+
const action = match[1];
|
|
170
|
+
const rawArgs = match[2];
|
|
171
|
+
const args: string[] = [];
|
|
172
|
+
|
|
173
|
+
// ── Robust tokenizer ────────────────────────────────────────────────────
|
|
174
|
+
// Handles: triple-double-quoted strings """...""" (e.g. runCustomCode blocks)
|
|
175
|
+
// double-quoted strings "..."
|
|
176
|
+
// single-quoted strings '...'
|
|
177
|
+
// tSetup.testData['KEY'] → stored as __testData__:KEY marker
|
|
178
|
+
// and resolved in execute() at runtime
|
|
179
|
+
let i = 0;
|
|
180
|
+
while (i < rawArgs.length) {
|
|
181
|
+
const ch = rawArgs[i];
|
|
182
|
+
|
|
183
|
+
// Skip whitespace and commas between arguments
|
|
184
|
+
if (ch === " " || ch === "," || ch === "\n" || ch === "\r" || ch === "\t") {
|
|
185
|
+
i++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// tSetup.testData['KEY'] or tSetup.testData["KEY"]
|
|
190
|
+
// Captures the variable key and defers resolution to execute()
|
|
191
|
+
if (rawArgs.startsWith("tSetup.testData[", i)) {
|
|
192
|
+
const closeIdx = rawArgs.indexOf("]", i);
|
|
193
|
+
if (closeIdx !== -1) {
|
|
194
|
+
const inner = rawArgs.slice(i + "tSetup.testData[".length, closeIdx).trim();
|
|
195
|
+
const key = inner.replace(/^['"]|['"]$/g, ""); // strip surrounding quotes
|
|
196
|
+
args.push(`__testData__:${key}`);
|
|
197
|
+
i = closeIdx + 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Triple double-quote string: """...""" (used in runCustomCode JS blocks)
|
|
203
|
+
if (ch === '"' && rawArgs[i + 1] === '"' && rawArgs[i + 2] === '"') {
|
|
204
|
+
i += 3;
|
|
205
|
+
let str = "";
|
|
206
|
+
while (i < rawArgs.length) {
|
|
207
|
+
if (rawArgs[i] === '"' && rawArgs[i + 1] === '"' && rawArgs[i + 2] === '"') {
|
|
208
|
+
i += 3;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
// Unescape \n and \t written as two-character literals inside JSON strings
|
|
212
|
+
if (rawArgs[i] === "\\" && rawArgs[i + 1] === "n") { str += "\n"; i += 2; }
|
|
213
|
+
else if (rawArgs[i] === "\\" && rawArgs[i + 1] === "t") { str += "\t"; i += 2; }
|
|
214
|
+
else if (rawArgs[i] === "\\" && rawArgs[i + 1] === "\\") { str += "\\"; i += 2; }
|
|
215
|
+
else { str += rawArgs[i++]; }
|
|
216
|
+
}
|
|
217
|
+
args.push(str);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Double-quoted string: "..."
|
|
222
|
+
if (ch === '"') {
|
|
223
|
+
i++;
|
|
224
|
+
let str = "";
|
|
225
|
+
while (i < rawArgs.length && rawArgs[i] !== '"') {
|
|
226
|
+
if (rawArgs[i] === "\\") { i++; str += rawArgs[i++]; }
|
|
227
|
+
else { str += rawArgs[i++]; }
|
|
228
|
+
}
|
|
229
|
+
i++; // consume closing "
|
|
230
|
+
args.push(str);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Single-quoted string: '...'
|
|
235
|
+
if (ch === "'") {
|
|
236
|
+
i++;
|
|
237
|
+
let str = "";
|
|
238
|
+
while (i < rawArgs.length && rawArgs[i] !== "'") {
|
|
239
|
+
if (rawArgs[i] === "\\") { i++; str += rawArgs[i++]; }
|
|
240
|
+
else { str += rawArgs[i++]; }
|
|
241
|
+
}
|
|
242
|
+
i++; // consume closing '
|
|
243
|
+
args.push(str.replace(/\\'/g, "'"));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Any other token (numeric literal, identifier, etc.) — skip
|
|
248
|
+
i++;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { action, args };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async execute(script: string | undefined | null, label: string | undefined | null): Promise<ActionResponse | undefined> {
|
|
255
|
+
// ✅ Guard: missing step_script — actionable error instead of crash
|
|
256
|
+
if (!script || typeof script !== "string" || !script.trim()) {
|
|
257
|
+
const msg =
|
|
258
|
+
"step_script is missing or empty. " +
|
|
259
|
+
"Your steps contain only step_name and value — they are RAW database objects. " +
|
|
260
|
+
"You must call prepareSteps(scriptUid) in your main app BEFORE sending to the framework. " +
|
|
261
|
+
"prepareSteps() resolves obj_uid → locator → step_script for each step.";
|
|
262
|
+
this.log.error(msg);
|
|
263
|
+
return { status: "Fail", comments: msg };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const parsed = this.parsedStepScript(script);
|
|
267
|
+
if (!parsed) {
|
|
268
|
+
this.log.error(`Cannot parse: "${script}"`);
|
|
269
|
+
return {
|
|
270
|
+
status: "Fail",
|
|
271
|
+
comments: `Invalid script format: "${script}". Expected: tSetup.actionName('arg1', ...)`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Resolve __testData__:KEY markers produced by the parser for
|
|
276
|
+
// tSetup.testData['KEY'] arguments — looks up the live value from the store
|
|
277
|
+
const resolvedArgs = parsed.args.map((arg) => {
|
|
278
|
+
if (typeof arg === "string" && arg.startsWith("__testData__:")) {
|
|
279
|
+
const key = arg.slice("__testData__:".length);
|
|
280
|
+
try {
|
|
281
|
+
return String(this.tConfig.testData.get(key));
|
|
282
|
+
} catch {
|
|
283
|
+
this.log.warn(`testData key "${key}" not found — passing empty string`);
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return arg;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
this.log.info(`→ ${parsed.action}(${resolvedArgs.join(", ")})`);
|
|
291
|
+
|
|
292
|
+
const result = await this.registry.execute(parsed.action, resolvedArgs, label ?? undefined);
|
|
293
|
+
|
|
294
|
+
if (!result) {
|
|
295
|
+
return {
|
|
296
|
+
status: "Fail",
|
|
297
|
+
comments: `Unknown action: "${parsed.action}". Call GET /api/actions to list all available actions.`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private registerBrowserActions(): void {
|
|
304
|
+
const bm = this.browserManager;
|
|
305
|
+
this.registry.register({
|
|
306
|
+
getActions: () => ({
|
|
307
|
+
openbrowser: async (a) => {
|
|
308
|
+
const result = await bm.openBrowser(a[0] || "chromium");
|
|
309
|
+
if (result.status === "Pass") this.registerPageHandlers();
|
|
310
|
+
return result;
|
|
311
|
+
},
|
|
312
|
+
closebrowser: () => bm.closeBrowser(),
|
|
313
|
+
navigatetourl: (a) => bm.navigateToURL(a[0]),
|
|
314
|
+
navigateback: () => bm.navigateBack(),
|
|
315
|
+
navigateforward: () => bm.navigateForward(),
|
|
316
|
+
refreshpage: () => bm.refreshPage(),
|
|
317
|
+
gettitle: (a) => bm.getTitle(a[0]),
|
|
318
|
+
verifytitle: (a) => bm.verifyTitle(a[0]),
|
|
319
|
+
verifypartialtitle: (a) => bm.verifyPartialTitle(a[0]),
|
|
320
|
+
getcurrenturl: () => bm.getCurrentURL(),
|
|
321
|
+
maximizewindow: () => bm.maximizeWindow(),
|
|
322
|
+
setwindowsize: (a) => bm.setWindowSize(a[0], a[1]),
|
|
323
|
+
switchtotab: (a) => bm.switchToTab(a[0]),
|
|
324
|
+
opennewtab: (a) => bm.openNewTab(a[0]),
|
|
325
|
+
closecurrenttab: () => bm.closeCurrentTab(),
|
|
326
|
+
acceptalert: (a) => bm.acceptAlert(a[0]),
|
|
327
|
+
dismissalert: () => bm.dismissAlert(),
|
|
328
|
+
getalerttext: (a) => bm.getAlertText(a[0]),
|
|
329
|
+
switchtoframe: (a) => bm.switchToFrame(a[0]),
|
|
330
|
+
executescript: (a) => bm.executeScript(a[0]),
|
|
331
|
+
scrolltobottom: () => bm.scrollToBottom(),
|
|
332
|
+
scrolltotop: () => bm.scrollToTop(),
|
|
333
|
+
deleteallcookies: () => bm.deleteAllCookies(),
|
|
334
|
+
clearlocalstorage: () => bm.clearLocalStorage(),
|
|
335
|
+
clearsessionstorage: () => bm.clearSessionStorage(),
|
|
336
|
+
takescreenshot: () => bm.takeScreenshot(),
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private registerPageHandlers(): void {
|
|
342
|
+
const page = this.browserManager.getPage();
|
|
343
|
+
const cfg = this.tConfig;
|
|
344
|
+
this.registry.register(new InputHandler(cfg, page));
|
|
345
|
+
this.registry.register(new ClickHandler(cfg, page));
|
|
346
|
+
this.registry.register(new DropdownHandler(cfg, page));
|
|
347
|
+
this.registry.register(new AssertionHandler(cfg, page));
|
|
348
|
+
this.registry.register(new WaitHandler(cfg, page));
|
|
349
|
+
this.registry.register(new KeyboardHandler(cfg, page));
|
|
350
|
+
this.registry.register(new CheckboxHandler(cfg, page));
|
|
351
|
+
this.registry.register(new TableHandler(cfg, page));
|
|
352
|
+
|
|
353
|
+
this.registry.register(new CustomCodeHandler(cfg, page));
|
|
354
|
+
this.log.info(`Registered ${this.registry.listActions().length} actions`);
|
|
355
|
+
}
|
|
356
|
+
async getPageSource(): Promise<string> {
|
|
357
|
+
const resp = await this.browserManager.getPageSource();
|
|
358
|
+
return resp?.data?.pagesource || "";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async getCurrentURL(): Promise<string> {
|
|
362
|
+
const resp = await this.browserManager.getCurrentURL();
|
|
363
|
+
return resp?.data?.url || "";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async forceClose(): Promise<void> {
|
|
367
|
+
try { await this.browserManager.closeBrowser(); } catch { /* ignore */ }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getRegisteredActions(): string[] { return this.registry.listActions(); }
|
|
371
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Page } from "playwright";
|
|
2
|
+
import { ScreenshotMode } from "../types/types";
|
|
3
|
+
|
|
4
|
+
export class ScreenshotManager {
|
|
5
|
+
private readonly mode: ScreenshotMode;
|
|
6
|
+
constructor(mode: ScreenshotMode = "on_failure") { this.mode = mode; }
|
|
7
|
+
|
|
8
|
+
async onPass(page?: Page | null): Promise<string | undefined> {
|
|
9
|
+
return this.mode === "always" ? this.capture(page) : undefined;
|
|
10
|
+
}
|
|
11
|
+
async onFail(page?: Page | null): Promise<string | undefined> {
|
|
12
|
+
return this.mode !== "never" ? this.capture(page) : undefined;
|
|
13
|
+
}
|
|
14
|
+
async force(page?: Page | null): Promise<string | undefined> {
|
|
15
|
+
return this.capture(page);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private async capture(page?: Page | null): Promise<string | undefined> {
|
|
19
|
+
if (!page || page.isClosed()) return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const buf = await page.screenshot({ type: "png", fullPage: false });
|
|
22
|
+
return buf.toString("base64");
|
|
23
|
+
} catch { return undefined; }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ITestData } from "../types/types";
|
|
2
|
+
|
|
3
|
+
export class TestData implements ITestData {
|
|
4
|
+
private store: Record<string, unknown> = {};
|
|
5
|
+
|
|
6
|
+
set(key: string, value: unknown): void { this.store[key] = value; }
|
|
7
|
+
has(key: string): boolean { return key in this.store; }
|
|
8
|
+
getAll(): Record<string, unknown> { return { ...this.store }; }
|
|
9
|
+
delete(key: string): void { delete this.store[key]; }
|
|
10
|
+
clear(): void { this.store = {}; }
|
|
11
|
+
|
|
12
|
+
get(key: string): unknown {
|
|
13
|
+
if (!(key in this.store))
|
|
14
|
+
throw new Error(`TestData: key "${key}" not found. Available: [${Object.keys(this.store).join(", ")}]`);
|
|
15
|
+
return this.store[key];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Resolve testdata("key") references in arg strings */
|
|
19
|
+
resolve(value: unknown): unknown {
|
|
20
|
+
if (typeof value !== "string") return value;
|
|
21
|
+
const m = value.match(/^testdata\("(.+?)"\)$/i);
|
|
22
|
+
if (m) return this.get(m[1]);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
}
|