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,398 @@
|
|
|
1
|
+
import { chromium, firefox, webkit, Browser, BrowserContext, Page } from "playwright";
|
|
2
|
+
import { ActionResponse, TestCaseConfig } from "../types/types";
|
|
3
|
+
import { pass, fail } from "../utils/response";
|
|
4
|
+
import { ScreenshotManager } from "./ScreenshotManager";
|
|
5
|
+
import { Logger } from "../utils/logger";
|
|
6
|
+
|
|
7
|
+
export class BrowserManager {
|
|
8
|
+
private browser?: Browser;
|
|
9
|
+
private context?: BrowserContext;
|
|
10
|
+
private page?: Page;
|
|
11
|
+
private shots: ScreenshotManager;
|
|
12
|
+
private log: Logger;
|
|
13
|
+
private headless: boolean;
|
|
14
|
+
private timeout: number;
|
|
15
|
+
private slowMo: number;
|
|
16
|
+
private recordVideo: boolean;
|
|
17
|
+
private videoDir: string;
|
|
18
|
+
|
|
19
|
+
constructor(tConfig: TestCaseConfig) {
|
|
20
|
+
this.headless = tConfig.headless ?? false;
|
|
21
|
+
this.timeout = tConfig.timeout ?? 30000;
|
|
22
|
+
this.slowMo = tConfig.slowMo ?? 0;
|
|
23
|
+
this.recordVideo = tConfig.record_video ?? false;
|
|
24
|
+
this.videoDir = `videos/${tConfig.executionId}`;
|
|
25
|
+
// this.videoDir = require("path").join(process.cwd(), "videos", tConfig.executionId);
|
|
26
|
+
this.shots = new ScreenshotManager(tConfig.screenshot_mode);
|
|
27
|
+
this.log = Logger.create("BrowserManager", tConfig.executionId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Open ──────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
async openBrowser(type: string = "chromium"): Promise<ActionResponse> {
|
|
33
|
+
try {
|
|
34
|
+
const launcher = this.getLauncher(type);
|
|
35
|
+
this.browser = await launcher.launch({
|
|
36
|
+
headless: this.headless,
|
|
37
|
+
slowMo: this.slowMo,
|
|
38
|
+
args: ["--no-sandbox",
|
|
39
|
+
"--disable-dev-shm-usage",
|
|
40
|
+
"--disable-blink-features=AutomationControlled",
|
|
41
|
+
"--start-maximized"
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const ctxOpts: Parameters<Browser["newContext"]>[0] = {
|
|
46
|
+
viewport: { width: 1280, height: 720 },
|
|
47
|
+
// Avoid waiting for networkidle by default — use domcontentloaded instead
|
|
48
|
+
// Individual nav calls can override this
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (this.recordVideo) {
|
|
52
|
+
ctxOpts.recordVideo = { dir: this.videoDir, size: { width: 1280, height: 720 } };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.context = await this.browser!.newContext(ctxOpts);
|
|
56
|
+
// ✅ FIX: Use domcontentloaded globally — networkidle was the dropdown delay root cause
|
|
57
|
+
// Each navigation still passes waitUntil but falls back faster
|
|
58
|
+
this.context.setDefaultTimeout(this.timeout);
|
|
59
|
+
this.context.setDefaultNavigationTimeout(this.timeout);
|
|
60
|
+
this.page = await this.context.newPage();
|
|
61
|
+
|
|
62
|
+
this.log.info(`${type} browser launched successfully`);
|
|
63
|
+
const sc = await this.shots.onPass(this.page);
|
|
64
|
+
return pass(`Browser opened: ${type}`, sc);
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
this.log.error(`openBrowser failed`, { error: error.message });
|
|
67
|
+
return fail("Failed to open browser", error.message);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private getLauncher(type: string) {
|
|
72
|
+
const map: Record<string, any> = {
|
|
73
|
+
chromium, chrome: chromium, edge: chromium,
|
|
74
|
+
firefox,
|
|
75
|
+
webkit, safari: webkit,
|
|
76
|
+
};
|
|
77
|
+
const l = map[type.toLowerCase()];
|
|
78
|
+
if (!l) throw new Error(`Unsupported browser: "${type}". Use: chromium|chrome|firefox|webkit|safari|edge`);
|
|
79
|
+
return l;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Close ─────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async closeBrowser(): Promise<ActionResponse> {
|
|
85
|
+
try {
|
|
86
|
+
if (this.page && !this.page.isClosed()) await this.page.close().catch(() => {});
|
|
87
|
+
if (this.context) await this.context.close().catch(() => {});
|
|
88
|
+
if (this.browser) await this.browser.close().catch(() => {});
|
|
89
|
+
if (this.recordVideo) this.log.info(`Video saved to: ${this.videoDir}`);
|
|
90
|
+
return pass("Browser closed successfully");
|
|
91
|
+
} catch (error: any) {
|
|
92
|
+
return fail("Failed to close browser", error.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Navigation ────────────────────────────────────────────────────────────
|
|
97
|
+
// ✅ ROOT CAUSE FIX: switched from "networkidle" to "domcontentloaded"
|
|
98
|
+
// networkidle waits for ALL network requests to stop (can be 5-30s on SPAs)
|
|
99
|
+
// domcontentloaded fires as soon as HTML is parsed — much faster
|
|
100
|
+
|
|
101
|
+
async navigateToURL(url: string): Promise<ActionResponse> {
|
|
102
|
+
try {
|
|
103
|
+
await this.getPage().goto(url, {
|
|
104
|
+
waitUntil: "domcontentloaded", // ✅ was networkidle — that caused dropdown delay
|
|
105
|
+
timeout: this.timeout,
|
|
106
|
+
});
|
|
107
|
+
const sc = await this.shots.onPass(this.page);
|
|
108
|
+
return pass(`Navigated to ${url}`, sc);
|
|
109
|
+
} catch (error: any) {
|
|
110
|
+
const sc = await this.shots.onFail(this.page);
|
|
111
|
+
return fail(`Failed to navigate to: ${url}`, error.message, sc);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async navigateBack(): Promise<ActionResponse> {
|
|
116
|
+
try {
|
|
117
|
+
await this.getPage().goBack({ waitUntil: "domcontentloaded" });
|
|
118
|
+
const sc = await this.shots.onPass(this.page);
|
|
119
|
+
return pass("Navigated back", sc);
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
const sc = await this.shots.onFail(this.page);
|
|
122
|
+
return fail("Failed to navigate back", error.message, sc);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async navigateForward(): Promise<ActionResponse> {
|
|
127
|
+
try {
|
|
128
|
+
await this.getPage().goForward({ waitUntil: "domcontentloaded" });
|
|
129
|
+
const sc = await this.shots.onPass(this.page);
|
|
130
|
+
return pass("Navigated forward", sc);
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
const sc = await this.shots.onFail(this.page);
|
|
133
|
+
return fail("Failed to navigate forward", error.message, sc);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async refreshPage(): Promise<ActionResponse> {
|
|
138
|
+
try {
|
|
139
|
+
await this.getPage().reload({ waitUntil: "domcontentloaded" });
|
|
140
|
+
const sc = await this.shots.onPass(this.page);
|
|
141
|
+
return pass("Page refreshed", sc);
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
const sc = await this.shots.onFail(this.page);
|
|
144
|
+
return fail("Failed to refresh page", error.message, sc);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Window / Viewport ─────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async maximizeWindow(): Promise<ActionResponse> {
|
|
151
|
+
try {
|
|
152
|
+
await this.getPage().setViewportSize({ width: 1920, height: 1080 });
|
|
153
|
+
return pass("Window maximized to 1920×1080");
|
|
154
|
+
} catch (error: any) {
|
|
155
|
+
return fail("Failed to maximize window", error.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async setWindowSize(width: string, height: string): Promise<ActionResponse> {
|
|
160
|
+
try {
|
|
161
|
+
await this.getPage().setViewportSize({ width: parseInt(width, 10), height: parseInt(height, 10) });
|
|
162
|
+
return pass(`Window set to ${width}×${height}`);
|
|
163
|
+
} catch (error: any) {
|
|
164
|
+
return fail("Failed to set window size", error.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Tabs ──────────────────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
async switchToTab(indexStr: string): Promise<ActionResponse> {
|
|
171
|
+
try {
|
|
172
|
+
const pages = this.context!.pages();
|
|
173
|
+
const index = parseInt(indexStr, 10);
|
|
174
|
+
if (index >= pages.length) return fail(`Tab index ${index} out of range (${pages.length} tabs)`);
|
|
175
|
+
this.page = pages[index];
|
|
176
|
+
await this.page.bringToFront();
|
|
177
|
+
const sc = await this.shots.onPass(this.page);
|
|
178
|
+
return pass(`Switched to tab ${index}`, sc);
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
return fail("Failed to switch tab", error.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async openNewTab(url?: string): Promise<ActionResponse> {
|
|
185
|
+
try {
|
|
186
|
+
this.page = await this.context!.newPage();
|
|
187
|
+
if (url) await this.page.goto(url, { waitUntil: "domcontentloaded" });
|
|
188
|
+
const sc = await this.shots.onPass(this.page);
|
|
189
|
+
return pass(`Opened new tab${url ? ": " + url : ""}`, sc);
|
|
190
|
+
} catch (error: any) {
|
|
191
|
+
return fail("Failed to open new tab", error.message);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async closeCurrentTab(): Promise<ActionResponse> {
|
|
196
|
+
try {
|
|
197
|
+
await this.getPage().close();
|
|
198
|
+
const pages = this.context!.pages();
|
|
199
|
+
if (pages.length > 0) this.page = pages[pages.length - 1];
|
|
200
|
+
return pass("Current tab closed");
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
return fail("Failed to close tab", error.message);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async waitForNewTab(triggerAction: () => Promise<void>): Promise<ActionResponse> {
|
|
207
|
+
try {
|
|
208
|
+
const [newPage] = await Promise.all([
|
|
209
|
+
this.context!.waitForEvent("page"),
|
|
210
|
+
triggerAction(),
|
|
211
|
+
]);
|
|
212
|
+
await newPage.waitForLoadState("domcontentloaded");
|
|
213
|
+
this.page = newPage;
|
|
214
|
+
const sc = await this.shots.onPass(this.page);
|
|
215
|
+
return pass("New tab opened and switched to", sc);
|
|
216
|
+
} catch (error: any) {
|
|
217
|
+
return fail("Failed waiting for new tab", error.message);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── Title / URL ───────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
async getTitle(storeKey?: string): Promise<ActionResponse> {
|
|
224
|
+
try {
|
|
225
|
+
const title = await this.getPage().title();
|
|
226
|
+
const sc = await this.shots.onPass(this.page);
|
|
227
|
+
return pass(`Page title: "${title}"`, sc, { title });
|
|
228
|
+
} catch (error: any) {
|
|
229
|
+
const sc = await this.shots.onFail(this.page);
|
|
230
|
+
return fail("Failed to get title", error.message, sc);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async verifyTitle(expected: string): Promise<ActionResponse> {
|
|
235
|
+
try {
|
|
236
|
+
const title = await this.getPage().title();
|
|
237
|
+
const ok = title === expected;
|
|
238
|
+
const msg = ok ? `Title matches: "${expected}"` : `Title mismatch. Expected: "${expected}", Actual: "${title}"`;
|
|
239
|
+
const sc = ok ? await this.shots.onPass(this.page) : await this.shots.onFail(this.page);
|
|
240
|
+
return ok ? pass(msg, sc) : fail(msg, undefined, sc);
|
|
241
|
+
} catch (error: any) {
|
|
242
|
+
const sc = await this.shots.onFail(this.page);
|
|
243
|
+
return fail("Failed to verify title", error.message, sc);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async verifyPartialTitle(partial: string): Promise<ActionResponse> {
|
|
248
|
+
try {
|
|
249
|
+
const title = await this.getPage().title();
|
|
250
|
+
const ok = title.includes(partial);
|
|
251
|
+
const msg = ok ? `Title contains: "${partial}"` : `Title does not contain "${partial}". Actual: "${title}"`;
|
|
252
|
+
const sc = ok ? await this.shots.onPass(this.page) : await this.shots.onFail(this.page);
|
|
253
|
+
return ok ? pass(msg, sc) : fail(msg, undefined, sc);
|
|
254
|
+
} catch (error: any) {
|
|
255
|
+
const sc = await this.shots.onFail(this.page);
|
|
256
|
+
return fail("Failed to verify partial title", error.message, sc);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async getCurrentURL(): Promise<ActionResponse> {
|
|
261
|
+
const url = this.getPage().url();
|
|
262
|
+
return pass(`Current URL: "${url}"`, undefined, { url });
|
|
263
|
+
}
|
|
264
|
+
async getPageSource(): Promise<ActionResponse> {
|
|
265
|
+
try {
|
|
266
|
+
const pageSource = this.getPage().content();
|
|
267
|
+
return pass("Page source captured", undefined, { pagesource: pageSource });
|
|
268
|
+
} catch (error: any) {
|
|
269
|
+
return fail(`Failed to get page source — ${error.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Dialogs ───────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
async acceptAlert(promptText?: string): Promise<ActionResponse> {
|
|
276
|
+
try {
|
|
277
|
+
this.getPage().once("dialog", (d) => d.accept(promptText));
|
|
278
|
+
return pass("Dialog handler set: will accept next dialog");
|
|
279
|
+
} catch (error: any) {
|
|
280
|
+
return fail("Failed to set accept handler", error.message);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async dismissAlert(): Promise<ActionResponse> {
|
|
285
|
+
try {
|
|
286
|
+
this.getPage().once("dialog", (d) => d.dismiss());
|
|
287
|
+
return pass("Dialog handler set: will dismiss next dialog");
|
|
288
|
+
} catch (error: any) {
|
|
289
|
+
return fail("Failed to set dismiss handler", error.message);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async getAlertText(storeKey?: string): Promise<ActionResponse> {
|
|
294
|
+
return new Promise((resolve) => {
|
|
295
|
+
const timer = setTimeout(() => resolve(fail("No dialog appeared within timeout")), this.timeout);
|
|
296
|
+
this.getPage().once("dialog", async (d) => {
|
|
297
|
+
clearTimeout(timer);
|
|
298
|
+
const text = d.message();
|
|
299
|
+
await d.dismiss();
|
|
300
|
+
if (storeKey) {/* testData would need to be injected here if needed */}
|
|
301
|
+
resolve(pass(`Alert text: "${text}"`, undefined, { text }));
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Frames ────────────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
async switchToFrame(nameOrSelector: string): Promise<ActionResponse> {
|
|
309
|
+
try {
|
|
310
|
+
const frame = this.getPage().frame({ name: nameOrSelector });
|
|
311
|
+
if (!frame) {
|
|
312
|
+
// try as locator-based frame
|
|
313
|
+
this.getPage().frameLocator(nameOrSelector);
|
|
314
|
+
return pass(`FrameLocator set for: "${nameOrSelector}"`);
|
|
315
|
+
}
|
|
316
|
+
return pass(`Switched to frame: "${nameOrSelector}"`);
|
|
317
|
+
} catch (error: any) {
|
|
318
|
+
return fail("Failed to switch to frame", error.message);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ─── JavaScript ────────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
async executeScript(script: string): Promise<ActionResponse> {
|
|
325
|
+
try {
|
|
326
|
+
const result = await this.getPage().evaluate(script);
|
|
327
|
+
const sc = await this.shots.onPass(this.page);
|
|
328
|
+
return pass("Script executed", sc, { result });
|
|
329
|
+
} catch (error: any) {
|
|
330
|
+
const sc = await this.shots.onFail(this.page);
|
|
331
|
+
return fail("Failed to execute script", error.message, sc);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async scrollToBottom(): Promise<ActionResponse> {
|
|
336
|
+
try {
|
|
337
|
+
await this.getPage().evaluate("window.scrollTo(0, document.body.scrollHeight)");
|
|
338
|
+
return pass("Scrolled to bottom");
|
|
339
|
+
} catch (error: any) {
|
|
340
|
+
return fail("Failed to scroll to bottom", error.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async scrollToTop(): Promise<ActionResponse> {
|
|
345
|
+
try {
|
|
346
|
+
await this.getPage().evaluate("window.scrollTo(0, 0)");
|
|
347
|
+
return pass("Scrolled to top");
|
|
348
|
+
} catch (error: any) {
|
|
349
|
+
return fail("Failed to scroll to top", error.message);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─── Cookies / Storage ─────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
async deleteAllCookies(): Promise<ActionResponse> {
|
|
356
|
+
try {
|
|
357
|
+
await this.context!.clearCookies();
|
|
358
|
+
return pass("All cookies deleted");
|
|
359
|
+
} catch (error: any) {
|
|
360
|
+
return fail("Failed to delete cookies", error.message);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async clearLocalStorage(): Promise<ActionResponse> {
|
|
365
|
+
try {
|
|
366
|
+
await this.getPage().evaluate("localStorage.clear()");
|
|
367
|
+
return pass("LocalStorage cleared");
|
|
368
|
+
} catch (error: any) {
|
|
369
|
+
return fail("Failed to clear localStorage", error.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async clearSessionStorage(): Promise<ActionResponse> {
|
|
374
|
+
try {
|
|
375
|
+
await this.getPage().evaluate("sessionStorage.clear()");
|
|
376
|
+
return pass("SessionStorage cleared");
|
|
377
|
+
} catch (error: any) {
|
|
378
|
+
return fail("Failed to clear sessionStorage", error.message);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Screenshot ────────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
async takeScreenshot(): Promise<ActionResponse> {
|
|
385
|
+
const sc = await this.shots.force(this.page);
|
|
386
|
+
return pass("Screenshot captured", sc);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ─── Getters ───────────────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
getPage(): Page {
|
|
392
|
+
if (!this.page || this.page.isClosed())
|
|
393
|
+
throw new Error("Browser not initialized. Call openBrowser() first.");
|
|
394
|
+
return this.page;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
hasPage(): boolean { return !!this.page && !this.page.isClosed(); }
|
|
398
|
+
}
|