@ddwang/magnitude-core 0.3.1-ddwang.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/dist/actions/desktopActions.d.ts +58 -0
- package/dist/actions/desktopActions.js +154 -0
- package/dist/actions/index.d.ts +26 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/taskActions.d.ts +8 -0
- package/dist/actions/taskActions.js +27 -0
- package/dist/actions/types.d.ts +31 -0
- package/dist/actions/types.js +5 -0
- package/dist/actions/util.d.ts +20 -0
- package/dist/actions/util.js +297 -0
- package/dist/actions/webActions.d.ts +79 -0
- package/dist/actions/webActions.js +190 -0
- package/dist/agent/browserAgent.d.ts +29 -0
- package/dist/agent/browserAgent.js +122 -0
- package/dist/agent/errors.d.ts +8 -0
- package/dist/agent/errors.js +68 -0
- package/dist/agent/index.d.ts +53 -0
- package/dist/agent/index.js +377 -0
- package/dist/agent/narrator.d.ts +4 -0
- package/dist/agent/narrator.js +63 -0
- package/dist/agent/pause.test.d.ts +1 -0
- package/dist/agent/pause.test.js +194 -0
- package/dist/ai/baml_client/async_client.d.ts +73 -0
- package/dist/ai/baml_client/async_client.js +152 -0
- package/dist/ai/baml_client/async_request.d.ts +35 -0
- package/dist/ai/baml_client/async_request.js +98 -0
- package/dist/ai/baml_client/config.d.ts +11 -0
- package/dist/ai/baml_client/config.js +20 -0
- package/dist/ai/baml_client/globals.d.ts +16 -0
- package/dist/ai/baml_client/globals.js +37 -0
- package/dist/ai/baml_client/index.d.ts +16 -0
- package/dist/ai/baml_client/index.js +41 -0
- package/dist/ai/baml_client/inlinedbaml.d.ts +19 -0
- package/dist/ai/baml_client/inlinedbaml.js +31 -0
- package/dist/ai/baml_client/parser.d.ts +53 -0
- package/dist/ai/baml_client/parser.js +86 -0
- package/dist/ai/baml_client/partial_types.d.ts +61 -0
- package/dist/ai/baml_client/partial_types.js +10 -0
- package/dist/ai/baml_client/sync_client.d.ts +50 -0
- package/dist/ai/baml_client/sync_client.js +100 -0
- package/dist/ai/baml_client/sync_request.d.ts +35 -0
- package/dist/ai/baml_client/sync_request.js +98 -0
- package/dist/ai/baml_client/tracing.d.ts +16 -0
- package/dist/ai/baml_client/tracing.js +18 -0
- package/dist/ai/baml_client/type_builder.d.ts +38 -0
- package/dist/ai/baml_client/type_builder.js +96 -0
- package/dist/ai/baml_client/types.d.ts +69 -0
- package/dist/ai/baml_client/types.js +15 -0
- package/dist/ai/claudeCode.d.ts +1 -0
- package/dist/ai/claudeCode.js +186 -0
- package/dist/ai/modelHarness.d.ts +39 -0
- package/dist/ai/modelHarness.js +189 -0
- package/dist/ai/multiModelHarness.d.ts +27 -0
- package/dist/ai/multiModelHarness.js +52 -0
- package/dist/ai/types.d.ts +90 -0
- package/dist/ai/types.js +5 -0
- package/dist/ai/util.d.ts +13 -0
- package/dist/ai/util.js +165 -0
- package/dist/cli.d.ts +0 -0
- package/dist/cli.js +3 -0
- package/dist/common/actions.d.ts +0 -0
- package/dist/common/actions.js +14 -0
- package/dist/common/events.d.ts +15 -0
- package/dist/common/events.js +3 -0
- package/dist/common/failure.d.ts +68 -0
- package/dist/common/failure.js +1 -0
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.js +3 -0
- package/dist/common/retry.d.ts +16 -0
- package/dist/common/retry.js +30 -0
- package/dist/common/util.d.ts +23 -0
- package/dist/common/util.js +132 -0
- package/dist/connectors/browserConnector.d.ts +40 -0
- package/dist/connectors/browserConnector.js +127 -0
- package/dist/connectors/desktopConnector.d.ts +58 -0
- package/dist/connectors/desktopConnector.js +68 -0
- package/dist/connectors/index.d.ts +10 -0
- package/dist/connectors/index.js +2 -0
- package/dist/index.cjs +6143 -0
- package/dist/index.d.cts +825 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/index.mjs +6097 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +15 -0
- package/dist/memory/agentMemory.d.ts +37 -0
- package/dist/memory/agentMemory.js +121 -0
- package/dist/memory/image.d.ts +26 -0
- package/dist/memory/image.js +106 -0
- package/dist/memory/index.d.ts +3 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/masking.d.ts +7 -0
- package/dist/memory/masking.js +141 -0
- package/dist/memory/masking.test.d.ts +1 -0
- package/dist/memory/masking.test.js +117 -0
- package/dist/memory/observation.d.ts +38 -0
- package/dist/memory/observation.js +79 -0
- package/dist/memory/rendering/index.d.ts +10 -0
- package/dist/memory/rendering/index.js +13 -0
- package/dist/memory/rendering/renderJsonParts.d.ts +3 -0
- package/dist/memory/rendering/renderJsonParts.js +115 -0
- package/dist/memory/rendering/renderJsonParts.test.d.ts +1 -0
- package/dist/memory/rendering/renderJsonParts.test.js +217 -0
- package/dist/memory/rendering/renderXmlParts.d.ts +7 -0
- package/dist/memory/rendering/renderXmlParts.js +127 -0
- package/dist/memory/serde.d.ts +20 -0
- package/dist/memory/serde.js +81 -0
- package/dist/memory/util.d.ts +2 -0
- package/dist/memory/util.js +17 -0
- package/dist/telemetry/events.d.ts +9 -0
- package/dist/telemetry/events.js +80 -0
- package/dist/telemetry/index.d.ts +6 -0
- package/dist/telemetry/index.js +91 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +1 -0
- package/dist/util.js +12 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/web/browserProvider.d.ts +23 -0
- package/dist/web/browserProvider.js +153 -0
- package/dist/web/harness.d.ts +102 -0
- package/dist/web/harness.js +340 -0
- package/dist/web/stability.d.ts +57 -0
- package/dist/web/stability.js +316 -0
- package/dist/web/tabs.d.ts +58 -0
- package/dist/web/tabs.js +293 -0
- package/dist/web/transformer.d.ts +7 -0
- package/dist/web/transformer.js +41 -0
- package/dist/web/types.d.ts +32 -0
- package/dist/web/types.js +2 -0
- package/dist/web/util.d.ts +3 -0
- package/dist/web/util.js +81 -0
- package/dist/web/visualizer/cursor.d.ts +16 -0
- package/dist/web/visualizer/cursor.js +152 -0
- package/dist/web/visualizer/index.d.ts +22 -0
- package/dist/web/visualizer/index.js +47 -0
- package/dist/web/visualizer/mouseEffects.d.ts +18 -0
- package/dist/web/visualizer/mouseEffects.js +287 -0
- package/dist/web/visualizer/typeEffects.d.ts +10 -0
- package/dist/web/visualizer/typeEffects.js +190 -0
- package/package.json +71 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { PageStabilityAnalyzer } from "./stability";
|
|
2
|
+
import { parseTypeContent } from "./util";
|
|
3
|
+
import { ActionVisualizer } from "./visualizer";
|
|
4
|
+
import logger from "@/logger";
|
|
5
|
+
import { TabManager } from "./tabs";
|
|
6
|
+
import { DOMTransformer } from "./transformer";
|
|
7
|
+
import { Image } from '@/memory/image';
|
|
8
|
+
import EventEmitter from "eventemitter3";
|
|
9
|
+
export class WebHarness {
|
|
10
|
+
/**
|
|
11
|
+
* Executes web actions on a page
|
|
12
|
+
* Not responsible for browser lifecycle
|
|
13
|
+
*/
|
|
14
|
+
context;
|
|
15
|
+
options;
|
|
16
|
+
stability;
|
|
17
|
+
visualizer;
|
|
18
|
+
transformer;
|
|
19
|
+
tabs;
|
|
20
|
+
events = new EventEmitter();
|
|
21
|
+
constructor(context, options = {}) {
|
|
22
|
+
//this.page = page;
|
|
23
|
+
this.context = context;
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.stability = new PageStabilityAnalyzer({ disableVisualStability: true });
|
|
26
|
+
this.visualizer = new ActionVisualizer(this.context, this.options.visuals ?? {});
|
|
27
|
+
this.transformer = new DOMTransformer();
|
|
28
|
+
this.tabs = new TabManager(context, {
|
|
29
|
+
switchOnActivity: options.switchTabsOnActivity ?? true
|
|
30
|
+
});
|
|
31
|
+
// this.context.on('page', (page: Page) => {
|
|
32
|
+
// this.setActivePage(page);
|
|
33
|
+
// //logger.info('ayo we got a new page');
|
|
34
|
+
// });
|
|
35
|
+
this.tabs.events.on('tabChanged', async (page) => {
|
|
36
|
+
await this.setActivePage(page);
|
|
37
|
+
// need to wait for page to load before evaluating a script
|
|
38
|
+
//page.on('load', () => { this.transformer.setActivePage(page); });
|
|
39
|
+
//console.log('tabs:', await this.tabs.getState())
|
|
40
|
+
}, this);
|
|
41
|
+
}
|
|
42
|
+
async setActivePage(page) {
|
|
43
|
+
logger.trace(`WebHarness active page: ${page.url()}`);
|
|
44
|
+
this.stability.setActivePage(page);
|
|
45
|
+
await this.visualizer.setActivePage(page);
|
|
46
|
+
this.transformer.setActivePage(page);
|
|
47
|
+
this.events.emit('activePageChanged', page);
|
|
48
|
+
}
|
|
49
|
+
async retrieveTabState() {
|
|
50
|
+
return this.tabs.retrieveState();
|
|
51
|
+
}
|
|
52
|
+
// setActivePage(page: Page) {
|
|
53
|
+
// this.page = page;
|
|
54
|
+
// this.stability.setActivePage(this.page);
|
|
55
|
+
// this.visualizer.setActivePage(this.page);
|
|
56
|
+
// }
|
|
57
|
+
async start() {
|
|
58
|
+
// Initialize tab manager first
|
|
59
|
+
await this.tabs.initialize();
|
|
60
|
+
if (this.context.pages().length > 0) {
|
|
61
|
+
// If context already contains a page, set it as active
|
|
62
|
+
this.tabs.setActivePage(this.context.pages()[0]);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const page = await this.context.newPage();
|
|
66
|
+
// Force the initial page to be set as active and emit tabChanged
|
|
67
|
+
this.tabs.setActivePage(page);
|
|
68
|
+
}
|
|
69
|
+
await this.visualizer.setup();
|
|
70
|
+
}
|
|
71
|
+
async stop() {
|
|
72
|
+
// Clean up tab manager resources
|
|
73
|
+
this.tabs.destroy();
|
|
74
|
+
}
|
|
75
|
+
get page() {
|
|
76
|
+
return this.tabs.getActivePage();
|
|
77
|
+
}
|
|
78
|
+
async screenshot(options = {}) {
|
|
79
|
+
/**
|
|
80
|
+
* Get b64 encoded string of screenshot (PNG) with screen dimensions
|
|
81
|
+
*/
|
|
82
|
+
// Target page, context or browser has been closed
|
|
83
|
+
let dpr;
|
|
84
|
+
let buffer;
|
|
85
|
+
const retries = 3;
|
|
86
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
87
|
+
try {
|
|
88
|
+
dpr = await this.page.evaluate(() => window.devicePixelRatio);
|
|
89
|
+
buffer = await this.page.screenshot({ type: 'png', ...options });
|
|
90
|
+
break; // Success! Exit the retry loop
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
// A few possibilities:
|
|
94
|
+
// 1. Target page, context or browser has been closed
|
|
95
|
+
// 2. Page navigation in progress
|
|
96
|
+
// In theory 2 shouldn't shouldn't happen during typical execution as we wait for page load - unless screenshot is triggered at an usual time.
|
|
97
|
+
const error = err;
|
|
98
|
+
if (error.message.includes('Target page, context or browser has been closed')) {
|
|
99
|
+
// Irrecoverable, no point in retrying
|
|
100
|
+
throw new Error("Attempted to take screenshot but page, context or browser is closed");
|
|
101
|
+
}
|
|
102
|
+
if (attempt >= retries) {
|
|
103
|
+
throw new Error(`Unable to capture screenshot after retries, error: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const base64data = buffer.toString('base64');
|
|
108
|
+
const image = Image.fromBase64(base64data);
|
|
109
|
+
// Now, need to rescale the image based on DPR. This is so that:
|
|
110
|
+
// (1) Save on tokens, dont need huge high res images
|
|
111
|
+
// (2) More importantly, clicks happen in the standard resolution space, so need to do this for coordinates to be correct
|
|
112
|
+
// for any agent not using a virtual screen space (e.g. those that aren't Claude)
|
|
113
|
+
const { width, height } = await image.getDimensions();
|
|
114
|
+
const rescaledImage = await image.resize(width / dpr, height / dpr);
|
|
115
|
+
return rescaledImage;
|
|
116
|
+
}
|
|
117
|
+
// async goto(url: string) {
|
|
118
|
+
// // No need to redraw here anymore, the 'load' event listener handles it
|
|
119
|
+
// await this.page.goto(url);
|
|
120
|
+
// }
|
|
121
|
+
async _type(content) {
|
|
122
|
+
/** Util for typing + keypresses */
|
|
123
|
+
const chunks = parseTypeContent(content);
|
|
124
|
+
// Total typing period to make typing more natural, in ms
|
|
125
|
+
const totalTextDelay = 500;
|
|
126
|
+
let totalTextLength = 0;
|
|
127
|
+
for (const chunk of chunks) {
|
|
128
|
+
if (chunk != '<enter>' && chunk != '<tab>') {
|
|
129
|
+
totalTextLength += chunk.length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const chunk of chunks) {
|
|
133
|
+
if (chunk == '<enter>') {
|
|
134
|
+
await this.page.keyboard.press('Enter');
|
|
135
|
+
}
|
|
136
|
+
else if (chunk == '<tab>') {
|
|
137
|
+
await this.page.keyboard.press('Tab');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const chunkProportion = chunk.length / totalTextLength;
|
|
141
|
+
const chunkDelay = totalTextDelay * chunkProportion;
|
|
142
|
+
const chunkCharDelay = chunkDelay / chunk.length;
|
|
143
|
+
await this.page.keyboard.type(chunk, { delay: chunkCharDelay });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// safer might be Coordinate interface/obj tied to certain screen space dims
|
|
148
|
+
async transformCoordinates({ x, y }) {
|
|
149
|
+
const virtual = this.options.virtualScreenDimensions;
|
|
150
|
+
if (!virtual) {
|
|
151
|
+
return { x, y };
|
|
152
|
+
}
|
|
153
|
+
let vp = this.page.viewportSize();
|
|
154
|
+
if (!vp) {
|
|
155
|
+
vp = await this.page.evaluate(() => ({
|
|
156
|
+
width: window.innerWidth,
|
|
157
|
+
height: window.innerHeight
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
if (!vp)
|
|
161
|
+
throw new Error("Could not get viewport dimensions to transform coordinates");
|
|
162
|
+
return {
|
|
163
|
+
x: x * (vp.width / virtual.width),
|
|
164
|
+
y: y * (vp.height / virtual.height),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async click({ x, y }, options) {
|
|
168
|
+
if (options?.transform ?? true)
|
|
169
|
+
({ x, y } = await this.transformCoordinates({ x, y }));
|
|
170
|
+
// console.log("x:", x);
|
|
171
|
+
// console.log("y:", y);
|
|
172
|
+
//await this.visualizer.visualizeAction(x, y);
|
|
173
|
+
//await this.page.mouse.click(x, y);
|
|
174
|
+
//await this.page.mouse.move(x, y, { steps: 20 });
|
|
175
|
+
//console.log('clicking:', x, y);
|
|
176
|
+
// const loc = this.page.getByText('Where are you going?');
|
|
177
|
+
// console.log('found:', loc);
|
|
178
|
+
// await loc.click();
|
|
179
|
+
await this._click(x, y);
|
|
180
|
+
// await this.page.waitForTimeout(1000);
|
|
181
|
+
// await this.page.mouse.click(x, y);
|
|
182
|
+
// await this.page.waitForTimeout(1000);
|
|
183
|
+
// await this.page.mouse.click(x, y);
|
|
184
|
+
// await this.page.waitForTimeout(1000);
|
|
185
|
+
// await this.page.mouse.click(x, y);
|
|
186
|
+
// await this.page.waitForTimeout(1000);
|
|
187
|
+
// await this.page.mouse.click(x, y);
|
|
188
|
+
// await this.page.waitForTimeout(1000);
|
|
189
|
+
// await Promise.all([
|
|
190
|
+
// this.page.mouse.move(x, y, { steps: 20 }),
|
|
191
|
+
// this.visualizer.visualizeAction(x, y),
|
|
192
|
+
// ]);
|
|
193
|
+
// await this.page.mouse.down();
|
|
194
|
+
// await this.page.waitForTimeout(200);
|
|
195
|
+
// await this.page.mouse.up();
|
|
196
|
+
// await this.page.evaluate(({ x, y }) => {
|
|
197
|
+
// // Find the topmost element at the given coordinates
|
|
198
|
+
// const targetElement = document.elementFromPoint(x, y);
|
|
199
|
+
// if (!targetElement) {
|
|
200
|
+
// console.error('No element found at coordinates:', x, y);
|
|
201
|
+
// return;
|
|
202
|
+
// }
|
|
203
|
+
// // Create and dispatch the events with properties that mimic a real click
|
|
204
|
+
// const options = {
|
|
205
|
+
// bubbles: true,
|
|
206
|
+
// cancelable: true,
|
|
207
|
+
// composed: true,
|
|
208
|
+
// // We can't set isTrusted, the browser forces it to false
|
|
209
|
+
// };
|
|
210
|
+
// targetElement.dispatchEvent(new MouseEvent('mouseover', options));
|
|
211
|
+
// targetElement.dispatchEvent(new MouseEvent('mousedown', options));
|
|
212
|
+
// targetElement.dispatchEvent(new MouseEvent('mouseup', options));
|
|
213
|
+
// targetElement.dispatchEvent(new MouseEvent('click', options));
|
|
214
|
+
// }, { x, y });
|
|
215
|
+
await this.waitForStability();
|
|
216
|
+
//await this.visualizer.removeActionVisuals();
|
|
217
|
+
}
|
|
218
|
+
async _click(x, y, options) {
|
|
219
|
+
await Promise.all([
|
|
220
|
+
this.visualizer.moveVirtualCursor(x, y),
|
|
221
|
+
this.page.mouse.move(x, y, { steps: 20 })
|
|
222
|
+
]);
|
|
223
|
+
// await this.visualizer.moveVirtualCursor(x, y);
|
|
224
|
+
// await this.page.mouse.move(x, y, { steps: 20 });
|
|
225
|
+
await this.visualizer.hideAll(); // hide / show pointer because no-pointer is not always consistent and visualizer can block click
|
|
226
|
+
await this.page.mouse.click(x, y);
|
|
227
|
+
await this.visualizer.showAll();
|
|
228
|
+
}
|
|
229
|
+
async rightClick({ x, y }, options) {
|
|
230
|
+
if (options?.transform ?? true)
|
|
231
|
+
({ x, y } = await this.transformCoordinates({ x, y }));
|
|
232
|
+
await this._click(x, y, { button: "right" });
|
|
233
|
+
await this.waitForStability();
|
|
234
|
+
}
|
|
235
|
+
async doubleClick({ x, y }, options) {
|
|
236
|
+
if (options?.transform ?? true)
|
|
237
|
+
({ x, y } = await this.transformCoordinates({ x, y }));
|
|
238
|
+
await this.visualizer.moveVirtualCursor(x, y);
|
|
239
|
+
await this.visualizer.hideAll();
|
|
240
|
+
await this.page.mouse.dblclick(x, y);
|
|
241
|
+
await this.visualizer.showAll();
|
|
242
|
+
await this.waitForStability();
|
|
243
|
+
}
|
|
244
|
+
async drag({ x1, y1, x2, y2 }, options) {
|
|
245
|
+
if (options?.transform ?? true)
|
|
246
|
+
({ x: x1, y: y1 } = await this.transformCoordinates({ x: x1, y: y1 }));
|
|
247
|
+
if (options?.transform ?? true)
|
|
248
|
+
({ x: x2, y: y2 } = await this.transformCoordinates({ x: x2, y: y2 }));
|
|
249
|
+
//console.log(`Dragging: (${x1}, ${y1}) -> (${x2}, ${y2})`);
|
|
250
|
+
await this.page.mouse.move(x1, y1, { steps: 1 });
|
|
251
|
+
await this.page.mouse.down();
|
|
252
|
+
await this.visualizer.moveVirtualCursor(x1, y1);
|
|
253
|
+
await this.page.waitForTimeout(500);
|
|
254
|
+
await Promise.all([
|
|
255
|
+
this.page.mouse.move(x2, y2, { steps: 20 }),
|
|
256
|
+
this.visualizer.moveVirtualCursor(x2, y2)
|
|
257
|
+
]);
|
|
258
|
+
// await this.page.mouse.move(x2, y2, { steps: 100 });
|
|
259
|
+
// await this.visualizer.visualizeAction(x2, y2);
|
|
260
|
+
await this.page.mouse.up();
|
|
261
|
+
await this.waitForStability();
|
|
262
|
+
//await this.visualizer.removeActionVisuals();
|
|
263
|
+
}
|
|
264
|
+
async type({ content }) {
|
|
265
|
+
await this._type(content);
|
|
266
|
+
await this.waitForStability();
|
|
267
|
+
}
|
|
268
|
+
async clickAndType({ x, y, content }, options) {
|
|
269
|
+
// TODO: transforms incorrect for moondream grounding with virtual screen dims (claude) - unsure why
|
|
270
|
+
//console.log(`Pre transform: ${x}, ${y}`);
|
|
271
|
+
if (options?.transform ?? true)
|
|
272
|
+
({ x, y } = await this.transformCoordinates({ x, y }));
|
|
273
|
+
//console.log(`Post transform: ${x}, ${y}`);
|
|
274
|
+
await this.visualizer.moveVirtualCursor(x, y);
|
|
275
|
+
this._click(x, y);
|
|
276
|
+
await this._type(content);
|
|
277
|
+
await this.waitForStability();
|
|
278
|
+
}
|
|
279
|
+
async scroll({ x, y, deltaX, deltaY }, options) {
|
|
280
|
+
if (options?.transform ?? true)
|
|
281
|
+
({ x, y } = await this.transformCoordinates({ x, y }));
|
|
282
|
+
await this.visualizer.moveVirtualCursor(x, y);
|
|
283
|
+
await this.page.mouse.move(x, y);
|
|
284
|
+
await this.page.mouse.wheel(deltaX, deltaY);
|
|
285
|
+
await this.waitForStability();
|
|
286
|
+
}
|
|
287
|
+
async switchTab({ index }) {
|
|
288
|
+
await this.tabs.switchTab(index);
|
|
289
|
+
await this.waitForStability();
|
|
290
|
+
}
|
|
291
|
+
async newTab() {
|
|
292
|
+
await this.context.newPage();
|
|
293
|
+
// Reasonable default and less confusing than white about:blank page
|
|
294
|
+
await this.navigate("https://google.com");
|
|
295
|
+
}
|
|
296
|
+
async navigate(url) {
|
|
297
|
+
// Only wait for DOM content on goto since we handle waiting for network idle etc ourselves
|
|
298
|
+
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
299
|
+
await this.waitForStability();
|
|
300
|
+
}
|
|
301
|
+
async selectAll() {
|
|
302
|
+
await this.page.keyboard.down('ControlOrMeta');
|
|
303
|
+
await this.page.keyboard.press('KeyA');
|
|
304
|
+
await this.page.keyboard.up('ControlOrMeta');
|
|
305
|
+
}
|
|
306
|
+
async enter() {
|
|
307
|
+
await this.page.keyboard.press('Enter');
|
|
308
|
+
}
|
|
309
|
+
async backspace() {
|
|
310
|
+
await this.page.keyboard.press('Backspace');
|
|
311
|
+
}
|
|
312
|
+
async tab() {
|
|
313
|
+
await this.page.keyboard.press('Tab');
|
|
314
|
+
}
|
|
315
|
+
async goBack() {
|
|
316
|
+
await this.page.goBack();
|
|
317
|
+
}
|
|
318
|
+
async executeAction(action) {
|
|
319
|
+
if (action.variant === 'click') {
|
|
320
|
+
await this.click(action);
|
|
321
|
+
}
|
|
322
|
+
else if (action.variant === 'type') {
|
|
323
|
+
await this.clickAndType(action);
|
|
324
|
+
}
|
|
325
|
+
else if (action.variant === 'scroll') {
|
|
326
|
+
await this.scroll(action);
|
|
327
|
+
}
|
|
328
|
+
else if (action.variant === 'tab') {
|
|
329
|
+
await this.switchTab(action);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
throw Error(`Unhandled web action variant: ${action.variant}`);
|
|
333
|
+
}
|
|
334
|
+
//await this.stability.waitForStability();
|
|
335
|
+
//await this.visualizer.redrawLastPosition();
|
|
336
|
+
}
|
|
337
|
+
async waitForStability(timeout) {
|
|
338
|
+
await this.stability.waitForStability(timeout);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
export interface ImageDiff {
|
|
3
|
+
/** Difference score between 0-1, where 0 means identical */
|
|
4
|
+
difference: number;
|
|
5
|
+
/** Optional error message if comparison failed */
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PageStabilityOptions {
|
|
9
|
+
/** Threshold below which screenshots are considered similar (0-1) */
|
|
10
|
+
differenceThreshold?: number;
|
|
11
|
+
/** Number of consecutive stable checks required to confirm stability */
|
|
12
|
+
requiredStableChecks?: number;
|
|
13
|
+
/** Interval between stability checks in ms */
|
|
14
|
+
checkInterval?: number;
|
|
15
|
+
/** Whether to log detailed stability information */
|
|
16
|
+
/** Minimum time to wait before getting page state */
|
|
17
|
+
minimumWaitPageLoadTime?: number;
|
|
18
|
+
/** Time to wait for network requests to finish before getting page state */
|
|
19
|
+
waitForNetworkIdleTime?: number;
|
|
20
|
+
/** Maximum time to wait for page load before proceeding anyway */
|
|
21
|
+
maximumWaitPageLoadTime?: number;
|
|
22
|
+
/** Whether to disable visual stability checking */
|
|
23
|
+
disableVisualStability?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Utility class to check for visual stability of a page
|
|
27
|
+
*/
|
|
28
|
+
export declare class PageStabilityAnalyzer {
|
|
29
|
+
private page;
|
|
30
|
+
private options;
|
|
31
|
+
private lastStart;
|
|
32
|
+
private logger;
|
|
33
|
+
constructor(options?: PageStabilityOptions);
|
|
34
|
+
setActivePage(page: Page): void;
|
|
35
|
+
private log;
|
|
36
|
+
/**
|
|
37
|
+
* Compare two screenshots and return their difference score
|
|
38
|
+
* @returns Difference as float between 0-1, where 0 means identical
|
|
39
|
+
*/
|
|
40
|
+
private compareScreenshots;
|
|
41
|
+
/**
|
|
42
|
+
* Wait for the network to become stable
|
|
43
|
+
* This monitors network requests and waits until there's a period of inactivity
|
|
44
|
+
* @param timeout Maximum time to wait for network stability
|
|
45
|
+
*/
|
|
46
|
+
waitForNetworkStability(timeout?: number): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Wait for the page to become visually stable
|
|
49
|
+
* @param timeout Maximum time to wait for stability in ms
|
|
50
|
+
*/
|
|
51
|
+
waitForVisualStability(timeout?: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Wait for both network and visual stability
|
|
54
|
+
* @param timeout Maximum time to wait for page load
|
|
55
|
+
*/
|
|
56
|
+
waitForStability(timeout?: number): Promise<void>;
|
|
57
|
+
}
|