testchimp-runner-core 0.0.35 → 0.0.36
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/package.json +6 -1
- package/plandocs/BEFORE_AFTER_VERIFICATION.md +0 -148
- package/plandocs/COORDINATE_MODE_DIAGNOSIS.md +0 -144
- package/plandocs/CREDIT_CALLBACK_ARCHITECTURE.md +0 -253
- package/plandocs/HUMAN_LIKE_IMPROVEMENTS.md +0 -642
- package/plandocs/IMPLEMENTATION_STATUS.md +0 -108
- package/plandocs/INTEGRATION_COMPLETE.md +0 -322
- package/plandocs/MULTI_AGENT_ARCHITECTURE_REVIEW.md +0 -844
- package/plandocs/ORCHESTRATOR_MVP_SUMMARY.md +0 -539
- package/plandocs/PHASE1_ABSTRACTION_COMPLETE.md +0 -241
- package/plandocs/PHASE1_FINAL_STATUS.md +0 -210
- package/plandocs/PHASE_1_COMPLETE.md +0 -165
- package/plandocs/PHASE_1_SUMMARY.md +0 -184
- package/plandocs/PLANNING_SESSION_SUMMARY.md +0 -372
- package/plandocs/PROMPT_OPTIMIZATION_ANALYSIS.md +0 -120
- package/plandocs/PROMPT_SANITY_CHECK.md +0 -120
- package/plandocs/SCRIPT_CLEANUP_FEATURE.md +0 -201
- package/plandocs/SCRIPT_GENERATION_ARCHITECTURE.md +0 -364
- package/plandocs/SELECTOR_IMPROVEMENTS.md +0 -139
- package/plandocs/SESSION_SUMMARY_v0.0.33.md +0 -151
- package/plandocs/TROUBLESHOOTING_SESSION.md +0 -72
- package/plandocs/VISION_DIAGNOSTICS_IMPROVEMENTS.md +0 -336
- package/plandocs/VISUAL_AGENT_EVOLUTION_PLAN.md +0 -396
- package/plandocs/WHATS_NEW_v0.0.33.md +0 -183
- package/plandocs/exploratory-mode-support-v2.plan.md +0 -953
- package/plandocs/exploratory-mode-support.plan.md +0 -928
- package/plandocs/journey-id-tracking-addendum.md +0 -227
- package/releasenotes/RELEASE_0.0.26.md +0 -165
- package/releasenotes/RELEASE_0.0.27.md +0 -236
- package/releasenotes/RELEASE_0.0.28.md +0 -286
- package/src/auth-config.ts +0 -84
- package/src/credit-usage-service.ts +0 -188
- package/src/env-loader.ts +0 -103
- package/src/execution-service.ts +0 -996
- package/src/file-handler.ts +0 -104
- package/src/index.ts +0 -432
- package/src/llm-facade.ts +0 -821
- package/src/llm-provider.ts +0 -53
- package/src/model-constants.ts +0 -35
- package/src/orchestrator/decision-parser.ts +0 -139
- package/src/orchestrator/index.ts +0 -58
- package/src/orchestrator/orchestrator-agent.ts +0 -1282
- package/src/orchestrator/orchestrator-prompts.ts +0 -786
- package/src/orchestrator/page-som-handler.ts +0 -1565
- package/src/orchestrator/som-types.ts +0 -188
- package/src/orchestrator/tool-registry.ts +0 -184
- package/src/orchestrator/tools/check-page-ready.ts +0 -75
- package/src/orchestrator/tools/extract-data.ts +0 -92
- package/src/orchestrator/tools/index.ts +0 -15
- package/src/orchestrator/tools/inspect-page.ts +0 -42
- package/src/orchestrator/tools/recall-history.ts +0 -72
- package/src/orchestrator/tools/refresh-som-markers.ts +0 -69
- package/src/orchestrator/tools/take-screenshot.ts +0 -128
- package/src/orchestrator/tools/verify-action-result.ts +0 -159
- package/src/orchestrator/tools/view-previous-screenshot.ts +0 -103
- package/src/orchestrator/types.ts +0 -291
- package/src/playwright-mcp-service.ts +0 -224
- package/src/progress-reporter.ts +0 -144
- package/src/prompts.ts +0 -842
- package/src/providers/backend-proxy-llm-provider.ts +0 -91
- package/src/providers/local-llm-provider.ts +0 -38
- package/src/scenario-service.ts +0 -252
- package/src/scenario-worker-class.ts +0 -1110
- package/src/script-utils.ts +0 -203
- package/src/types.ts +0 -239
- package/src/utils/browser-utils.ts +0 -348
- package/src/utils/coordinate-converter.ts +0 -162
- package/src/utils/page-info-retry.ts +0 -65
- package/src/utils/page-info-utils.ts +0 -285
- package/testchimp-runner-core-0.0.35.tgz +0 -0
- package/tsconfig.json +0 -19
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as os from 'os';
|
|
4
|
-
const esbuild = require('esbuild');
|
|
5
|
-
const { build } = esbuild;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Initialize browser with Playwright configuration
|
|
9
|
-
* @param playwrightConfig - JavaScript config file content (playwright.config.js)
|
|
10
|
-
* @param headless - Override headless mode (optional)
|
|
11
|
-
* @param playwrightConfigFilePath - Path to playwright config file (optional)
|
|
12
|
-
* @param logger - Optional logger callback for capturing logs
|
|
13
|
-
* @returns Browser, context, and page instances
|
|
14
|
-
*/
|
|
15
|
-
export async function initializeBrowser(
|
|
16
|
-
playwrightConfigContent?: string,
|
|
17
|
-
headless?: boolean,
|
|
18
|
-
playwrightConfigFilePath?: string,
|
|
19
|
-
logger?: (message: string, level?: 'log' | 'error' | 'warn') => void
|
|
20
|
-
): Promise<{ browser: any; context: any; page: any }> {
|
|
21
|
-
const log = (message: string, level: 'log' | 'error' | 'warn' = 'log') => {
|
|
22
|
-
if (logger) {
|
|
23
|
-
logger(message, level);
|
|
24
|
-
}
|
|
25
|
-
// No console fallback - logs are routed to consumer
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
log('Initializing browser with Playwright');
|
|
29
|
-
|
|
30
|
-
// Import Playwright modules dynamically
|
|
31
|
-
const playwright = await import('playwright');
|
|
32
|
-
const { chromium, firefox, webkit } = playwright;
|
|
33
|
-
|
|
34
|
-
let contextOptions: any = {};
|
|
35
|
-
|
|
36
|
-
// Use Playwright config content if provided
|
|
37
|
-
if (playwrightConfigContent) {
|
|
38
|
-
log('Using provided Playwright config content');
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// Transpile the config content in-memory and evaluate it
|
|
42
|
-
log(`Transpiling config content (${playwrightConfigContent.length} characters)`);
|
|
43
|
-
|
|
44
|
-
const result = await build({
|
|
45
|
-
stdin: {
|
|
46
|
-
contents: playwrightConfigContent,
|
|
47
|
-
resolveDir: String(process.cwd()),
|
|
48
|
-
},
|
|
49
|
-
bundle: true,
|
|
50
|
-
platform: 'node',
|
|
51
|
-
format: 'cjs',
|
|
52
|
-
sourcemap: false,
|
|
53
|
-
target: 'node18',
|
|
54
|
-
logLevel: 'silent',
|
|
55
|
-
write: false, // Don't write to file, get the result in memory
|
|
56
|
-
external: [
|
|
57
|
-
'@playwright/test',
|
|
58
|
-
'playwright',
|
|
59
|
-
'playwright-core'
|
|
60
|
-
]
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (!result.outputFiles || result.outputFiles.length === 0) {
|
|
64
|
-
throw new Error('esbuild failed to generate output');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const transpiledCode = result.outputFiles[0].text;
|
|
68
|
-
log(`Transpilation complete. Generated ${transpiledCode.length} characters of code`);
|
|
69
|
-
|
|
70
|
-
// Evaluate the transpiled code in a safe context
|
|
71
|
-
const loadedConfig = eval(transpiledCode);
|
|
72
|
-
log(`Loaded config object: ${JSON.stringify(loadedConfig)}`);
|
|
73
|
-
log(`Config type: ${typeof loadedConfig}`);
|
|
74
|
-
log(`Config keys: ${loadedConfig ? Object.keys(loadedConfig).join(', ') : 'null/undefined'}`);
|
|
75
|
-
|
|
76
|
-
// Get the actual config from the default export (ES module transpiled to CommonJS)
|
|
77
|
-
const actualConfig = loadedConfig.default || loadedConfig;
|
|
78
|
-
|
|
79
|
-
if (actualConfig && typeof actualConfig === 'object') {
|
|
80
|
-
log('Successfully loaded Playwright config from content');
|
|
81
|
-
log(`Config keys: ${Object.keys(actualConfig).join(', ')}`);
|
|
82
|
-
|
|
83
|
-
// Extract context options from the config
|
|
84
|
-
if (actualConfig.use && actualConfig.use.contextOptions) {
|
|
85
|
-
contextOptions = { ...contextOptions, ...actualConfig.use.contextOptions };
|
|
86
|
-
log('Applied context options from config');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Apply other config settings
|
|
90
|
-
if (actualConfig.use && actualConfig.use.headless !== undefined) {
|
|
91
|
-
// Override headless mode if specified in config and not explicitly overridden
|
|
92
|
-
if (headless === undefined) {
|
|
93
|
-
headless = actualConfig.use.headless;
|
|
94
|
-
log(`Using headless mode from config: ${headless}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Apply timeout settings
|
|
99
|
-
if (actualConfig.use && actualConfig.use.actionTimeout) {
|
|
100
|
-
log(`Using action timeout from config: ${actualConfig.use.actionTimeout}ms`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Apply viewport settings
|
|
104
|
-
if (actualConfig.use && actualConfig.use.viewport) {
|
|
105
|
-
contextOptions.viewport = actualConfig.use.viewport;
|
|
106
|
-
log(`Using viewport from config: ${JSON.stringify(actualConfig.use.viewport)}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Apply user agent
|
|
110
|
-
if (actualConfig.use && actualConfig.use.userAgent) {
|
|
111
|
-
contextOptions.userAgent = actualConfig.use.userAgent;
|
|
112
|
-
log(`Using user agent from config: ${actualConfig.use.userAgent}`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Apply other context options
|
|
116
|
-
if (actualConfig.use && actualConfig.use.extraHTTPHeaders) {
|
|
117
|
-
contextOptions.extraHTTPHeaders = actualConfig.use.extraHTTPHeaders;
|
|
118
|
-
log(`Using extra HTTP headers from config`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (actualConfig.use && actualConfig.use.locale) {
|
|
122
|
-
contextOptions.locale = actualConfig.use.locale;
|
|
123
|
-
log(`Using locale from config: ${actualConfig.use.locale}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (actualConfig.use && actualConfig.use.timezoneId) {
|
|
127
|
-
contextOptions.timezoneId = actualConfig.use.timezoneId;
|
|
128
|
-
log(`Using timezone from config: ${actualConfig.use.timezoneId}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (actualConfig.use && actualConfig.use.geolocation) {
|
|
132
|
-
contextOptions.geolocation = actualConfig.use.geolocation;
|
|
133
|
-
log(`Using geolocation from config: ${JSON.stringify(actualConfig.use.geolocation)}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (actualConfig.use && actualConfig.use.permissions) {
|
|
137
|
-
contextOptions.permissions = actualConfig.use.permissions;
|
|
138
|
-
log(`Using permissions from config: ${JSON.stringify(actualConfig.use.permissions)}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (actualConfig.use && actualConfig.use.colorScheme) {
|
|
142
|
-
contextOptions.colorScheme = actualConfig.use.colorScheme;
|
|
143
|
-
log(`Using color scheme from config: ${actualConfig.use.colorScheme}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (actualConfig.use && actualConfig.use.reducedMotion) {
|
|
147
|
-
contextOptions.reducedMotion = actualConfig.use.reducedMotion;
|
|
148
|
-
log(`Using reduced motion from config: ${actualConfig.use.reducedMotion}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (actualConfig.use && actualConfig.use.forcedColors) {
|
|
152
|
-
contextOptions.forcedColors = actualConfig.use.forcedColors;
|
|
153
|
-
log(`Using forced colors from config: ${actualConfig.use.forcedColors}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (actualConfig.use && actualConfig.use.acceptDownloads) {
|
|
157
|
-
contextOptions.acceptDownloads = actualConfig.use.acceptDownloads;
|
|
158
|
-
log(`Using accept downloads from config: ${actualConfig.use.acceptDownloads}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (actualConfig.use && actualConfig.use.bypassCSP) {
|
|
162
|
-
contextOptions.bypassCSP = actualConfig.use.bypassCSP;
|
|
163
|
-
log(`Using bypass CSP from config: ${actualConfig.use.bypassCSP}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (actualConfig.use && actualConfig.use.javaScriptEnabled) {
|
|
167
|
-
contextOptions.javaScriptEnabled = actualConfig.use.javaScriptEnabled;
|
|
168
|
-
log(`Using JavaScript enabled from config: ${actualConfig.use.javaScriptEnabled}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (actualConfig.use && actualConfig.use.ignoreHTTPSErrors) {
|
|
172
|
-
contextOptions.ignoreHTTPSErrors = actualConfig.use.ignoreHTTPSErrors;
|
|
173
|
-
log(`Using ignore HTTPS errors from config: ${actualConfig.use.ignoreHTTPSErrors}`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
log(`Final context options: ${JSON.stringify(contextOptions, null, 2)}`);
|
|
177
|
-
} else {
|
|
178
|
-
log('Config loaded but no valid configuration found');
|
|
179
|
-
}
|
|
180
|
-
} catch (error) {
|
|
181
|
-
log(`Error transpiling config content: ${error}`, 'error');
|
|
182
|
-
log('Falling back to default configuration');
|
|
183
|
-
}
|
|
184
|
-
} else if (playwrightConfigFilePath) {
|
|
185
|
-
// Fallback to file path if no content provided (for backward compatibility)
|
|
186
|
-
log('No config content provided, falling back to file path');
|
|
187
|
-
// Resolve the path - it might be relative or absolute
|
|
188
|
-
const resolvedPath = path.isAbsolute(String(playwrightConfigFilePath))
|
|
189
|
-
? String(playwrightConfigFilePath)
|
|
190
|
-
: path.resolve(String(process.cwd()), String(playwrightConfigFilePath));
|
|
191
|
-
|
|
192
|
-
log(`Looking for Playwright config at: ${resolvedPath}`);
|
|
193
|
-
log(`File exists: ${fs.existsSync(String(resolvedPath))}`);
|
|
194
|
-
|
|
195
|
-
if (fs.existsSync(String(resolvedPath))) {
|
|
196
|
-
log(`Loading Playwright config from: ${resolvedPath}`);
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
// Transpile the config in-memory and evaluate it
|
|
200
|
-
log(`Transpiling config in-memory from: ${resolvedPath}`);
|
|
201
|
-
|
|
202
|
-
const result = await build({
|
|
203
|
-
entryPoints: [String(resolvedPath)],
|
|
204
|
-
bundle: true,
|
|
205
|
-
platform: 'node',
|
|
206
|
-
format: 'cjs',
|
|
207
|
-
sourcemap: false,
|
|
208
|
-
target: 'node18',
|
|
209
|
-
logLevel: 'silent',
|
|
210
|
-
write: false, // Don't write to file, get the result in memory
|
|
211
|
-
external: [
|
|
212
|
-
'@playwright/test',
|
|
213
|
-
'playwright',
|
|
214
|
-
'playwright-core'
|
|
215
|
-
]
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
if (!result.outputFiles || result.outputFiles.length === 0) {
|
|
219
|
-
throw new Error('esbuild failed to generate output');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const transpiledCode = result.outputFiles[0].text;
|
|
223
|
-
log(`Transpilation complete. Generated ${transpiledCode.length} characters of code`);
|
|
224
|
-
|
|
225
|
-
// Evaluate the transpiled code in a safe context
|
|
226
|
-
const loadedConfig = eval(transpiledCode);
|
|
227
|
-
log(`Loaded config object: ${JSON.stringify(loadedConfig)}`);
|
|
228
|
-
log(`Config type: ${typeof loadedConfig}`);
|
|
229
|
-
log(`Config keys: ${loadedConfig ? Object.keys(loadedConfig).join(', ') : 'null/undefined'}`);
|
|
230
|
-
|
|
231
|
-
// Get the actual config from the default export (ES module transpiled to CommonJS)
|
|
232
|
-
const actualConfig = loadedConfig.default || loadedConfig;
|
|
233
|
-
log(`Actual config: ${JSON.stringify(actualConfig)}`);
|
|
234
|
-
log(`Actual config keys: ${actualConfig ? Object.keys(actualConfig).join(', ') : 'null/undefined'}`);
|
|
235
|
-
|
|
236
|
-
if (!actualConfig) {
|
|
237
|
-
log('Config import did not return a valid config; using defaults');
|
|
238
|
-
} else {
|
|
239
|
-
// Apply global use options
|
|
240
|
-
if (actualConfig.use) {
|
|
241
|
-
contextOptions = { ...actualConfig.use };
|
|
242
|
-
log('Applied context options from Playwright config:', contextOptions);
|
|
243
|
-
} else {
|
|
244
|
-
log('No use property found in config');
|
|
245
|
-
}
|
|
246
|
-
// Apply first project overrides if present
|
|
247
|
-
if (Array.isArray(actualConfig.projects) && actualConfig.projects.length > 0) {
|
|
248
|
-
const firstProject = actualConfig.projects[0];
|
|
249
|
-
if (firstProject && firstProject.use) {
|
|
250
|
-
contextOptions = { ...contextOptions, ...firstProject.use };
|
|
251
|
-
log('Applied project-specific options:', firstProject.use);
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
log('No projects found in config');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// No cleanup needed - we used in-memory transpilation
|
|
259
|
-
|
|
260
|
-
} catch (error) {
|
|
261
|
-
log(`Failed to load Playwright config via esbuild/import: ${error}`);
|
|
262
|
-
log('Using default browser settings');
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
log(`Playwright config file not found at: ${resolvedPath}`);
|
|
266
|
-
log('Using default browser settings');
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Use chromium as default browser
|
|
271
|
-
const browser = await chromium.launch({
|
|
272
|
-
headless: headless !== undefined ? headless : false
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Create context with config options or defaults
|
|
276
|
-
const context = await browser.newContext(contextOptions);
|
|
277
|
-
const page = await context.newPage();
|
|
278
|
-
|
|
279
|
-
// Set default timeout to 5 seconds (unless overridden by playwright config)
|
|
280
|
-
if (!contextOptions.timeout) {
|
|
281
|
-
context.setDefaultTimeout(5000);
|
|
282
|
-
page.setDefaultTimeout(5000);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return { browser, context, page };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Capture an optimized screenshot for vision API calls
|
|
290
|
-
* - Sets temporary viewport to reduce image size
|
|
291
|
-
* - Uses JPEG compression
|
|
292
|
-
* - Returns base64 data URL
|
|
293
|
-
*
|
|
294
|
-
* @param page - Playwright page instance
|
|
295
|
-
* @param options - Screenshot options
|
|
296
|
-
* @param log - Optional logger callback
|
|
297
|
-
* @returns Data URL string (data:image/jpeg;base64,...)
|
|
298
|
-
*/
|
|
299
|
-
export async function captureOptimizedScreenshot(
|
|
300
|
-
page: any,
|
|
301
|
-
options: {
|
|
302
|
-
quality?: number; // JPEG quality (0-100), default 60 (optimal for vision API)
|
|
303
|
-
width?: number; // Viewport width, default 800
|
|
304
|
-
height?: number; // Viewport height, default 800
|
|
305
|
-
timeout?: number; // Screenshot timeout in ms, default 10000
|
|
306
|
-
} = {},
|
|
307
|
-
log?: (message: string) => void
|
|
308
|
-
): Promise<string> {
|
|
309
|
-
const {
|
|
310
|
-
quality = 60, // Sweet spot: 50% smaller than quality 85, minimal visual loss
|
|
311
|
-
width = 800,
|
|
312
|
-
height = 800,
|
|
313
|
-
timeout = 10000
|
|
314
|
-
} = options;
|
|
315
|
-
|
|
316
|
-
// Save current viewport
|
|
317
|
-
const currentViewport = page.viewportSize();
|
|
318
|
-
|
|
319
|
-
// Set smaller viewport to reduce screenshot size
|
|
320
|
-
await page.setViewportSize({ width, height });
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
const screenshotBuffer = await page.screenshot({
|
|
324
|
-
type: 'jpeg', // JPEG for smaller file size
|
|
325
|
-
quality, // Compression quality
|
|
326
|
-
fullPage: false, // Viewport only
|
|
327
|
-
timeout // Timeout for slow pages
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Convert Buffer to base64 string explicitly
|
|
331
|
-
const screenshotBase64 = screenshotBuffer.toString('base64');
|
|
332
|
-
|
|
333
|
-
if (log) {
|
|
334
|
-
const sizeKB = Math.round(screenshotBase64.length * 0.75 / 1024);
|
|
335
|
-
log(` 📏 Screenshot size: ~${sizeKB}KB (base64)`);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Construct data URL for vision API
|
|
339
|
-
const imageDataUrl = `data:image/jpeg;base64,${screenshotBase64}`;
|
|
340
|
-
|
|
341
|
-
return imageDataUrl;
|
|
342
|
-
} finally {
|
|
343
|
-
// Always restore original viewport, even if screenshot fails
|
|
344
|
-
if (currentViewport) {
|
|
345
|
-
await page.setViewportSize(currentViewport);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Coordinate Converter Utility
|
|
3
|
-
* Converts percentage-based coordinates to pixel coordinates and generates Playwright commands
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { CoordinateAction } from '../orchestrator/types';
|
|
7
|
-
|
|
8
|
-
export class CoordinateConverter {
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Convert percentage coordinates to actual pixel coordinates
|
|
12
|
-
*/
|
|
13
|
-
static percentToPixels(
|
|
14
|
-
xPercent: number,
|
|
15
|
-
yPercent: number,
|
|
16
|
-
viewportWidth: number,
|
|
17
|
-
viewportHeight: number
|
|
18
|
-
): { x: number; y: number } {
|
|
19
|
-
return {
|
|
20
|
-
x: Math.round((xPercent / 100) * viewportWidth),
|
|
21
|
-
y: Math.round((yPercent / 100) * viewportHeight)
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get viewport dimensions from page
|
|
27
|
-
*/
|
|
28
|
-
static async getViewportSize(page: any): Promise<{ width: number; height: number }> {
|
|
29
|
-
return await page.evaluate((): { width: number; height: number } => {
|
|
30
|
-
const win = (globalThis as any).window;
|
|
31
|
-
return {
|
|
32
|
-
width: win.innerWidth as number,
|
|
33
|
-
height: win.innerHeight as number
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Convert coordinate action with percentages to Playwright commands
|
|
40
|
-
* Returns array of command strings
|
|
41
|
-
*/
|
|
42
|
-
static async generateCommands(
|
|
43
|
-
action: CoordinateAction,
|
|
44
|
-
page: any
|
|
45
|
-
): Promise<string[]> {
|
|
46
|
-
const viewport = await this.getViewportSize(page);
|
|
47
|
-
const { x, y } = this.percentToPixels(action.xPercent, action.yPercent, viewport.width, viewport.height);
|
|
48
|
-
|
|
49
|
-
const commands: string[] = [];
|
|
50
|
-
|
|
51
|
-
switch (action.action) {
|
|
52
|
-
case 'click':
|
|
53
|
-
commands.push(`await page.mouse.click(${x}, ${y});`);
|
|
54
|
-
break;
|
|
55
|
-
|
|
56
|
-
case 'doubleClick':
|
|
57
|
-
commands.push(`await page.mouse.dblclick(${x}, ${y});`);
|
|
58
|
-
break;
|
|
59
|
-
|
|
60
|
-
case 'rightClick':
|
|
61
|
-
commands.push(`await page.mouse.click(${x}, ${y}, { button: 'right' });`);
|
|
62
|
-
break;
|
|
63
|
-
|
|
64
|
-
case 'hover':
|
|
65
|
-
commands.push(`await page.mouse.move(${x}, ${y});`);
|
|
66
|
-
break;
|
|
67
|
-
|
|
68
|
-
case 'drag':
|
|
69
|
-
if (action.toXPercent === undefined || action.toYPercent === undefined) {
|
|
70
|
-
throw new Error('Drag action requires toXPercent and toYPercent');
|
|
71
|
-
}
|
|
72
|
-
const to = this.percentToPixels(action.toXPercent, action.toYPercent, viewport.width, viewport.height);
|
|
73
|
-
commands.push(`await page.mouse.move(${x}, ${y});`);
|
|
74
|
-
commands.push(`await page.mouse.down();`);
|
|
75
|
-
commands.push(`await page.mouse.move(${to.x}, ${to.y});`);
|
|
76
|
-
commands.push(`await page.mouse.up();`);
|
|
77
|
-
break;
|
|
78
|
-
|
|
79
|
-
case 'fill':
|
|
80
|
-
if (!action.value) {
|
|
81
|
-
throw new Error('Fill action requires value');
|
|
82
|
-
}
|
|
83
|
-
// Click to focus, wait briefly, then type
|
|
84
|
-
commands.push(`await page.mouse.click(${x}, ${y});`);
|
|
85
|
-
commands.push(`await page.waitForTimeout(100);`);
|
|
86
|
-
commands.push(`await page.keyboard.type(${JSON.stringify(action.value)});`);
|
|
87
|
-
break;
|
|
88
|
-
|
|
89
|
-
case 'scroll':
|
|
90
|
-
const scrollAmount = action.scrollAmount || 100;
|
|
91
|
-
// Move to position, then scroll
|
|
92
|
-
commands.push(`await page.mouse.move(${x}, ${y});`);
|
|
93
|
-
commands.push(`await page.mouse.wheel(0, ${scrollAmount});`);
|
|
94
|
-
break;
|
|
95
|
-
|
|
96
|
-
default:
|
|
97
|
-
throw new Error(`Unknown coordinate action: ${action.action}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return commands;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Execute coordinate action directly on page
|
|
105
|
-
* Used during agent execution (converts and runs immediately)
|
|
106
|
-
*/
|
|
107
|
-
static async executeAction(
|
|
108
|
-
action: CoordinateAction,
|
|
109
|
-
page: any
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
const viewport = await this.getViewportSize(page);
|
|
112
|
-
const { x, y } = this.percentToPixels(action.xPercent, action.yPercent, viewport.width, viewport.height);
|
|
113
|
-
|
|
114
|
-
switch (action.action) {
|
|
115
|
-
case 'click':
|
|
116
|
-
await page.mouse.click(x, y);
|
|
117
|
-
break;
|
|
118
|
-
|
|
119
|
-
case 'doubleClick':
|
|
120
|
-
await page.mouse.dblclick(x, y);
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'rightClick':
|
|
124
|
-
await page.mouse.click(x, y, { button: 'right' });
|
|
125
|
-
break;
|
|
126
|
-
|
|
127
|
-
case 'hover':
|
|
128
|
-
await page.mouse.move(x, y);
|
|
129
|
-
break;
|
|
130
|
-
|
|
131
|
-
case 'drag':
|
|
132
|
-
if (action.toXPercent === undefined || action.toYPercent === undefined) {
|
|
133
|
-
throw new Error('Drag requires toXPercent and toYPercent');
|
|
134
|
-
}
|
|
135
|
-
const to = this.percentToPixels(action.toXPercent, action.toYPercent, viewport.width, viewport.height);
|
|
136
|
-
await page.mouse.move(x, y);
|
|
137
|
-
await page.mouse.down();
|
|
138
|
-
await page.mouse.move(to.x, to.y);
|
|
139
|
-
await page.mouse.up();
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
case 'fill':
|
|
143
|
-
if (!action.value) {
|
|
144
|
-
throw new Error('Fill requires value');
|
|
145
|
-
}
|
|
146
|
-
await page.mouse.click(x, y);
|
|
147
|
-
await page.waitForTimeout(100);
|
|
148
|
-
await page.keyboard.type(action.value);
|
|
149
|
-
break;
|
|
150
|
-
|
|
151
|
-
case 'scroll':
|
|
152
|
-
const scrollAmount = action.scrollAmount || 100;
|
|
153
|
-
await page.mouse.move(x, y);
|
|
154
|
-
await page.mouse.wheel(0, scrollAmount);
|
|
155
|
-
break;
|
|
156
|
-
|
|
157
|
-
default:
|
|
158
|
-
throw new Error(`Unknown coordinate action: ${action.action}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page Info Retry Utility
|
|
3
|
-
* Handles adaptive page loading with exponential backoff
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getEnhancedPageInfo, PageInfo } from './page-info-utils';
|
|
7
|
-
|
|
8
|
-
export class PageInfoRetry {
|
|
9
|
-
/**
|
|
10
|
-
* Get page info with retry logic - waits for interactive elements to appear
|
|
11
|
-
* Uses exponential backoff to handle slow-loading React/Vue/Angular apps
|
|
12
|
-
*/
|
|
13
|
-
static async getWithRetry(page: any, maxAttempts: number = 6): Promise<PageInfo> {
|
|
14
|
-
// Wait for initial page load (generous timeout for slow apps)
|
|
15
|
-
try {
|
|
16
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 20000 }).catch(() => {});
|
|
17
|
-
} catch (waitError) {
|
|
18
|
-
// Continue even if wait fails
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let attempt = 0;
|
|
22
|
-
let backoffMs = 1000; // Start with 1 second (adequate for most sites)
|
|
23
|
-
|
|
24
|
-
while (attempt < maxAttempts) {
|
|
25
|
-
attempt++;
|
|
26
|
-
|
|
27
|
-
// Try to extract page info
|
|
28
|
-
const pageInfo = await getEnhancedPageInfo(page);
|
|
29
|
-
|
|
30
|
-
// If we got a reasonable number of elements, we're done
|
|
31
|
-
if (pageInfo.interactiveElements && pageInfo.interactiveElements.length >= 3) {
|
|
32
|
-
if (attempt > 1) {
|
|
33
|
-
console.log(`[PageInfoRetry] ✓ Page elements loaded after ${attempt} attempts`);
|
|
34
|
-
}
|
|
35
|
-
return pageInfo;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// If this is the last attempt, return what we have
|
|
39
|
-
if (attempt >= maxAttempts) {
|
|
40
|
-
const totalWait = this.calculateTotalWaitTime(maxAttempts);
|
|
41
|
-
console.log(`[PageInfoRetry] ⚠️ Only found ${pageInfo.interactiveElements?.length || 0} elements after ${maxAttempts} attempts (total wait: ~${totalWait}ms)`);
|
|
42
|
-
return pageInfo;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Wait with exponential backoff before retrying
|
|
46
|
-
console.log(`[PageInfoRetry] Only ${pageInfo.interactiveElements?.length || 0} elements found (attempt ${attempt}/${maxAttempts}), waiting ${backoffMs}ms...`);
|
|
47
|
-
await page.waitForTimeout(backoffMs);
|
|
48
|
-
backoffMs = Math.min(backoffMs * 1.6, 15000); // Cap at 15 seconds per attempt
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Fallback (shouldn't reach here, but for type safety)
|
|
52
|
-
return await getEnhancedPageInfo(page);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private static calculateTotalWaitTime(maxAttempts: number): number {
|
|
56
|
-
let total = 0;
|
|
57
|
-
let backoffMs = 1000;
|
|
58
|
-
for (let i = 1; i < maxAttempts; i++) {
|
|
59
|
-
total += backoffMs;
|
|
60
|
-
backoffMs = Math.min(backoffMs * 1.6, 15000);
|
|
61
|
-
}
|
|
62
|
-
return Math.round(total);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|