@dyyz1993/agent-browser 0.9.2 → 0.11.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/__tests__/utils/parseCli.d.ts +1 -0
- package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
- package/dist/__tests__/utils/parseCli.js +18 -10
- package/dist/__tests__/utils/parseCli.js.map +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +63 -3
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +46 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +343 -13
- package/dist/browser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +8 -3
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/connection.d.ts.map +1 -1
- package/dist/cli/connection.js +39 -1
- package/dist/cli/connection.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +27 -20
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +5 -0
- package/dist/cli/output.js.map +1 -1
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +147 -1
- package/dist/daemon.js.map +1 -1
- package/dist/message-bridge.d.ts.map +1 -1
- package/dist/message-bridge.js +22 -4
- package/dist/message-bridge.js.map +1 -1
- package/dist/openapi.d.ts +22 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +382 -0
- package/dist/openapi.js.map +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -1
- package/dist/recorder/inject.js +61 -134
- package/dist/stream-server-standalone.d.ts +10 -0
- package/dist/stream-server-standalone.d.ts.map +1 -1
- package/dist/stream-server-standalone.js +594 -74
- package/dist/stream-server-standalone.js.map +1 -1
- package/dist/stream-server.d.ts +67 -2
- package/dist/stream-server.d.ts.map +1 -1
- package/dist/stream-server.js +371 -51
- package/dist/stream-server.js.map +1 -1
- package/dist/swagger-ui.d.ts +6 -0
- package/dist/swagger-ui.d.ts.map +1 -0
- package/dist/swagger-ui.js +51 -0
- package/dist/swagger-ui.js.map +1 -0
- package/dist/test-live.d.ts +2 -0
- package/dist/test-live.d.ts.map +1 -0
- package/dist/test-live.js +333 -0
- package/dist/test-live.js.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer-html.d.ts.map +1 -1
- package/dist/viewer-html.js +270 -58
- package/dist/viewer-html.js.map +1 -1
- package/dist/viewer-script.d.ts +20 -2
- package/dist/viewer-script.d.ts.map +1 -1
- package/dist/viewer-script.js +911 -154
- package/dist/viewer-script.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +6 -32
- package/scripts/test-cli-help.sh +51 -0
- package/scripts/verify-form.sh +67 -0
- package/scripts/verify-login.sh +65 -0
- package/scripts/verify-recording.sh +80 -0
- package/scripts/verify-upload.sh +41 -0
- package/skills/agent-browser/SKILL.md +297 -160
- package/skills/agent-browser/references/commands.md +3 -0
- package/skills/agent-browser/references/mobile-viewer.md +188 -0
- package/skills/agent-browser/references/network-monitoring.md +232 -0
- package/skills/agent-browser/references/recorder.md +319 -0
- package/skills/agent-browser/references/viewer-mode.md +148 -0
- package/skills/agent-browser/templates/api-interception.sh +3 -1
- package/skills/agent-browser/templates/data-extraction.sh +8 -4
- package/skills/agent-browser/templates/form-automation.sh +18 -23
- package/skills/agent-browser/templates/network-intercept-crawl.sh +256 -0
- package/skills/agent-browser/templates/recorder-workflow.sh +51 -0
- package/skills/agent-browser/templates/viewer-remote.sh +41 -0
- package/dist/__tests__/test-iframe.d.ts +0 -2
- package/dist/__tests__/test-iframe.d.ts.map +0 -1
- package/dist/__tests__/test-iframe.js +0 -52
- package/dist/__tests__/test-iframe.js.map +0 -1
- package/dist/cli-new.d.ts +0 -3
- package/dist/cli-new.d.ts.map +0 -1
- package/dist/cli-new.js +0 -308
- package/dist/cli-new.js.map +0 -1
- package/dist/cli-old.d.ts +0 -3
- package/dist/cli-old.d.ts.map +0 -1
- package/dist/cli-old.js +0 -1101
- package/dist/cli-old.js.map +0 -1
- package/dist/recorder/binding.d.ts +0 -24
- package/dist/recorder/binding.d.ts.map +0 -1
- package/dist/recorder/binding.js +0 -215
- package/dist/recorder/binding.js.map +0 -1
- package/dist/recorder/index.d.ts +0 -4
- package/dist/recorder/index.d.ts.map +0 -1
- package/dist/recorder/index.js +0 -4
- package/dist/recorder/index.js.map +0 -1
- package/dist/recorder/recorder.d.ts +0 -19
- package/dist/recorder/recorder.d.ts.map +0 -1
- package/dist/recorder/recorder.js +0 -101
- package/dist/recorder/recorder.js.map +0 -1
- package/dist/recorder/store.d.ts +0 -22
- package/dist/recorder/store.d.ts.map +0 -1
- package/dist/recorder/store.js +0 -150
- package/dist/recorder/store.js.map +0 -1
- package/dist/recorder/types.d.ts +0 -73
- package/dist/recorder/types.d.ts.map +0 -1
- package/dist/recorder/types.js +0 -5
- package/dist/recorder/types.js.map +0 -1
package/dist/browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chromium, firefox, webkit, devices, } from 'playwright-core';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { existsSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync, statSync } from 'node:fs';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
import { getEnhancedSnapshot, parseRef } from './snapshot.js';
|
|
@@ -24,6 +24,13 @@ export class BrowserManager {
|
|
|
24
24
|
activePageIndex = 0;
|
|
25
25
|
dialogHandler = null;
|
|
26
26
|
trackedRequests = [];
|
|
27
|
+
isRequestTrackingEnabled = false;
|
|
28
|
+
isResponseCaptureEnabled = false;
|
|
29
|
+
// Map to track requests for response matching (instance variable for cross-listener access)
|
|
30
|
+
pendingRequests = new Map();
|
|
31
|
+
// Store request listener references for proper cleanup
|
|
32
|
+
requestListener = null;
|
|
33
|
+
responseListener = null;
|
|
27
34
|
routes = new Map();
|
|
28
35
|
consoleMessages = [];
|
|
29
36
|
pageErrors = [];
|
|
@@ -252,27 +259,104 @@ export class BrowserManager {
|
|
|
252
259
|
}
|
|
253
260
|
/**
|
|
254
261
|
* Start tracking requests
|
|
262
|
+
* @param captureResponse - Whether to capture response body (default: false for backward compatibility)
|
|
255
263
|
*/
|
|
256
|
-
startRequestTracking() {
|
|
264
|
+
startRequestTracking(captureResponse = false) {
|
|
257
265
|
const page = this.getPage();
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
// If already tracking with the same captureResponse setting, do nothing
|
|
267
|
+
if (this.isRequestTrackingEnabled && this.isResponseCaptureEnabled === captureResponse) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Remove existing listeners if any
|
|
271
|
+
if (this.requestListener) {
|
|
272
|
+
page.off('request', this.requestListener);
|
|
273
|
+
}
|
|
274
|
+
if (this.responseListener) {
|
|
275
|
+
page.off('response', this.responseListener);
|
|
276
|
+
}
|
|
277
|
+
// Update flags
|
|
278
|
+
this.isRequestTrackingEnabled = true;
|
|
279
|
+
this.isResponseCaptureEnabled = captureResponse;
|
|
280
|
+
// Create request listener
|
|
281
|
+
this.requestListener = (request) => {
|
|
282
|
+
const trackedRequest = {
|
|
260
283
|
url: request.url(),
|
|
261
284
|
method: request.method(),
|
|
262
285
|
headers: request.headers(),
|
|
263
286
|
timestamp: Date.now(),
|
|
264
287
|
resourceType: request.resourceType(),
|
|
265
|
-
}
|
|
266
|
-
|
|
288
|
+
};
|
|
289
|
+
// Store the request
|
|
290
|
+
this.trackedRequests.push(trackedRequest);
|
|
291
|
+
// Store for response matching
|
|
292
|
+
const key = `${request.url()}:${trackedRequest.timestamp}`;
|
|
293
|
+
this.pendingRequests.set(key, trackedRequest);
|
|
294
|
+
};
|
|
295
|
+
page.on('request', this.requestListener);
|
|
296
|
+
// Listen for response event (more reliable than request.response())
|
|
297
|
+
if (captureResponse) {
|
|
298
|
+
this.responseListener = async (response) => {
|
|
299
|
+
const request = response.request();
|
|
300
|
+
const url = request.url();
|
|
301
|
+
// Find the matching tracked request
|
|
302
|
+
for (const [key, trackedRequest] of this.pendingRequests.entries()) {
|
|
303
|
+
if (key.startsWith(url + ':')) {
|
|
304
|
+
trackedRequest.status = response.status();
|
|
305
|
+
trackedRequest.statusText = response.statusText();
|
|
306
|
+
trackedRequest.responseHeaders = response.headers();
|
|
307
|
+
trackedRequest.contentType = response.headers()['content-type'] || '';
|
|
308
|
+
// Try to get response body
|
|
309
|
+
try {
|
|
310
|
+
const body = await response.text();
|
|
311
|
+
// Try to parse as JSON if content-type indicates JSON
|
|
312
|
+
if (trackedRequest.contentType.includes('application/json') ||
|
|
313
|
+
trackedRequest.contentType.includes('text/json')) {
|
|
314
|
+
try {
|
|
315
|
+
trackedRequest.responseBody = JSON.parse(body);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
trackedRequest.responseBody = body;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
trackedRequest.responseBody = body;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// Response body not available (e.g., for binary data or failed requests)
|
|
327
|
+
trackedRequest.responseBody = undefined;
|
|
328
|
+
}
|
|
329
|
+
// Remove from pending after processing
|
|
330
|
+
this.pendingRequests.delete(key);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
page.on('response', this.responseListener);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this.responseListener = null;
|
|
339
|
+
}
|
|
267
340
|
}
|
|
268
341
|
/**
|
|
269
342
|
* Get tracked requests
|
|
343
|
+
* @param filter - URL pattern to filter
|
|
344
|
+
* @param type - Filter by response type (e.g., 'json')
|
|
270
345
|
*/
|
|
271
|
-
getRequests(filter) {
|
|
346
|
+
getRequests(filter, type) {
|
|
347
|
+
let requests = this.trackedRequests;
|
|
348
|
+
// Filter by URL pattern
|
|
272
349
|
if (filter) {
|
|
273
|
-
|
|
350
|
+
requests = requests.filter((r) => r.url.includes(filter));
|
|
274
351
|
}
|
|
275
|
-
|
|
352
|
+
// Filter by response type
|
|
353
|
+
if (type === 'json') {
|
|
354
|
+
requests = requests.filter((r) => {
|
|
355
|
+
const contentType = r.contentType || '';
|
|
356
|
+
return contentType.includes('application/json') || contentType.includes('text/json');
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return requests;
|
|
276
360
|
}
|
|
277
361
|
/**
|
|
278
362
|
* Clear tracked requests
|
|
@@ -280,6 +364,79 @@ export class BrowserManager {
|
|
|
280
364
|
clearRequests() {
|
|
281
365
|
this.trackedRequests = [];
|
|
282
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Save tracked requests to a directory
|
|
369
|
+
* @param outputDir - Directory path to save requests
|
|
370
|
+
* @param filter - URL pattern to filter
|
|
371
|
+
* @param type - Filter by response type (e.g., 'json')
|
|
372
|
+
* @returns Object with saved count and output path
|
|
373
|
+
*/
|
|
374
|
+
saveRequestsToDir(outputDir, filter, type) {
|
|
375
|
+
// Get filtered requests
|
|
376
|
+
const requests = this.getRequests(filter, type);
|
|
377
|
+
// Resolve to absolute path
|
|
378
|
+
const absolutePath = path.resolve(outputDir);
|
|
379
|
+
// Check if path looks like a file (has extension and not already a directory)
|
|
380
|
+
const hasExtension = path.extname(absolutePath) !== '';
|
|
381
|
+
const isExistingDirectory = existsSync(absolutePath) && statSync(absolutePath).isDirectory();
|
|
382
|
+
// If path looks like a file and doesn't exist as directory, use parent directory
|
|
383
|
+
let targetPath = absolutePath;
|
|
384
|
+
let warningMessage;
|
|
385
|
+
if (hasExtension && !isExistingDirectory) {
|
|
386
|
+
// User specified a file path, use parent directory instead
|
|
387
|
+
targetPath = path.dirname(absolutePath);
|
|
388
|
+
warningMessage = `Warning: "${outputDir}" looks like a file path. Using directory: "${targetPath}"`;
|
|
389
|
+
console.warn(warningMessage);
|
|
390
|
+
}
|
|
391
|
+
// Create output directory if not exists
|
|
392
|
+
if (!existsSync(targetPath)) {
|
|
393
|
+
mkdirSync(targetPath, { recursive: true });
|
|
394
|
+
}
|
|
395
|
+
// Build index data
|
|
396
|
+
const indexData = {
|
|
397
|
+
capturedAt: new Date().toISOString(),
|
|
398
|
+
totalRequests: requests.length,
|
|
399
|
+
requests: [],
|
|
400
|
+
};
|
|
401
|
+
// Save each request to a separate file
|
|
402
|
+
requests.forEach((request, index) => {
|
|
403
|
+
const fileIndex = String(index + 1).padStart(3, '0');
|
|
404
|
+
// Generate filename from URL or use index
|
|
405
|
+
const urlObj = new URL(request.url);
|
|
406
|
+
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
407
|
+
const baseName = pathParts.length > 0 ? pathParts.join('_').substring(0, 50) : 'request';
|
|
408
|
+
const fileName = `${fileIndex}_${baseName}.json`;
|
|
409
|
+
const filePath = path.join(targetPath, fileName);
|
|
410
|
+
// Save individual request file
|
|
411
|
+
const requestData = {
|
|
412
|
+
url: request.url,
|
|
413
|
+
method: request.method,
|
|
414
|
+
status: request.status,
|
|
415
|
+
contentType: request.contentType,
|
|
416
|
+
timestamp: request.timestamp,
|
|
417
|
+
body: request.responseBody,
|
|
418
|
+
};
|
|
419
|
+
writeFileSync(filePath, JSON.stringify(requestData, null, 2), 'utf-8');
|
|
420
|
+
// Add to index
|
|
421
|
+
indexData.requests.push({
|
|
422
|
+
index: index + 1,
|
|
423
|
+
file: fileName,
|
|
424
|
+
url: request.url,
|
|
425
|
+
method: request.method,
|
|
426
|
+
status: request.status,
|
|
427
|
+
contentType: request.contentType,
|
|
428
|
+
timestamp: request.timestamp,
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
// Save index file
|
|
432
|
+
const indexPath = path.join(targetPath, 'index.json');
|
|
433
|
+
writeFileSync(indexPath, JSON.stringify(indexData, null, 2), 'utf-8');
|
|
434
|
+
return {
|
|
435
|
+
savedCount: requests.length,
|
|
436
|
+
outputPath: targetPath,
|
|
437
|
+
indexPath,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
283
440
|
/**
|
|
284
441
|
* Add a route to intercept requests
|
|
285
442
|
*/
|
|
@@ -986,16 +1143,15 @@ export class BrowserManager {
|
|
|
986
1143
|
? ['--allow-file-access-from-files', '--allow-file-access']
|
|
987
1144
|
: [];
|
|
988
1145
|
// Add anti-detection args
|
|
1146
|
+
const isHeaded = hasExtensions || options.headless === false;
|
|
989
1147
|
const antiDetectionArgs = [
|
|
990
1148
|
'--disable-blink-features=AutomationControlled',
|
|
991
1149
|
'--disable-dev-shm-usage',
|
|
992
1150
|
'--no-sandbox',
|
|
993
|
-
'--disable-gpu',
|
|
994
|
-
'--disable-software-rasterizer',
|
|
1151
|
+
...(isHeaded ? [] : ['--disable-gpu']),
|
|
995
1152
|
'--enable-features=WebGL',
|
|
996
1153
|
'--ignore-gpu-blacklist',
|
|
997
|
-
'--use-gl=desktop',
|
|
998
|
-
'--enable-gpu-compositing',
|
|
1154
|
+
...(isHeaded ? ['--use-gl=desktop', '--enable-gpu-compositing'] : []),
|
|
999
1155
|
];
|
|
1000
1156
|
const baseArgs = options.args
|
|
1001
1157
|
? [...fileAccessArgs, ...antiDetectionArgs, ...options.args]
|
|
@@ -1565,6 +1721,162 @@ export class BrowserManager {
|
|
|
1565
1721
|
const cdp = await this.getCDPSession();
|
|
1566
1722
|
await cdp.send('Input.insertText', { text });
|
|
1567
1723
|
}
|
|
1724
|
+
_lastFillSelector = '';
|
|
1725
|
+
_lastFillValue = '';
|
|
1726
|
+
_fillFocusedSelector = '';
|
|
1727
|
+
async fillValue(selector, value) {
|
|
1728
|
+
const page = this.getPage();
|
|
1729
|
+
if (!page)
|
|
1730
|
+
return;
|
|
1731
|
+
if (!selector || value === undefined)
|
|
1732
|
+
return;
|
|
1733
|
+
if (value === this._lastFillValue && selector === this._lastFillSelector)
|
|
1734
|
+
return;
|
|
1735
|
+
this._lastFillSelector = selector;
|
|
1736
|
+
this._lastFillValue = value;
|
|
1737
|
+
const needsFocus = !this._fillFocusedSelector || this._fillFocusedSelector !== selector;
|
|
1738
|
+
await page.evaluate(({ selector, value, needsFocus }) => {
|
|
1739
|
+
const el = document.querySelector(selector);
|
|
1740
|
+
if (!el)
|
|
1741
|
+
return { ok: false, reason: 'not_found' };
|
|
1742
|
+
const isContentEditable = el instanceof HTMLElement &&
|
|
1743
|
+
(el.isContentEditable || el.getAttribute('contenteditable') === 'true');
|
|
1744
|
+
if (needsFocus && !isContentEditable) {
|
|
1745
|
+
el.focus();
|
|
1746
|
+
}
|
|
1747
|
+
if (isContentEditable) {
|
|
1748
|
+
if (needsFocus)
|
|
1749
|
+
el.focus();
|
|
1750
|
+
document.execCommand('selectAll', false, undefined);
|
|
1751
|
+
document.execCommand('insertText', false, value);
|
|
1752
|
+
return { ok: true, method: 'contenteditable' };
|
|
1753
|
+
}
|
|
1754
|
+
const tag = el.tagName.toLowerCase();
|
|
1755
|
+
const isInput = tag === 'input';
|
|
1756
|
+
const isTextarea = tag === 'textarea';
|
|
1757
|
+
if (!isInput && !isTextarea) {
|
|
1758
|
+
return { ok: false, reason: 'not_input' };
|
|
1759
|
+
}
|
|
1760
|
+
const proto = isInput
|
|
1761
|
+
? window.HTMLInputElement.prototype
|
|
1762
|
+
: window.HTMLTextAreaElement.prototype;
|
|
1763
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
1764
|
+
if (nativeInputValueSetter) {
|
|
1765
|
+
nativeInputValueSetter.call(el, value);
|
|
1766
|
+
}
|
|
1767
|
+
else {
|
|
1768
|
+
el.value = value;
|
|
1769
|
+
}
|
|
1770
|
+
el.dispatchEvent(new InputEvent('input', {
|
|
1771
|
+
bubbles: true,
|
|
1772
|
+
cancelable: true,
|
|
1773
|
+
inputType: 'insertReplacementText',
|
|
1774
|
+
data: value,
|
|
1775
|
+
}));
|
|
1776
|
+
return { ok: true, method: 'native_setter' };
|
|
1777
|
+
}, { selector, value, needsFocus });
|
|
1778
|
+
if (needsFocus) {
|
|
1779
|
+
this._fillFocusedSelector = selector;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
clearFillState(selector) {
|
|
1783
|
+
if (selector && this._fillFocusedSelector === selector) {
|
|
1784
|
+
this._fillFocusedSelector = '';
|
|
1785
|
+
}
|
|
1786
|
+
if (!selector) {
|
|
1787
|
+
this._fillFocusedSelector = '';
|
|
1788
|
+
this._lastFillSelector = '';
|
|
1789
|
+
this._lastFillValue = '';
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
async blurElement(selector) {
|
|
1793
|
+
const page = this.getPage();
|
|
1794
|
+
if (!page)
|
|
1795
|
+
return;
|
|
1796
|
+
await page.evaluate((sel) => {
|
|
1797
|
+
const el = document.querySelector(sel);
|
|
1798
|
+
if (el)
|
|
1799
|
+
el.blur();
|
|
1800
|
+
}, selector);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Press a key on the page via Playwright.
|
|
1804
|
+
*/
|
|
1805
|
+
async pressKey(key) {
|
|
1806
|
+
const page = this.getPage();
|
|
1807
|
+
if (!page)
|
|
1808
|
+
return;
|
|
1809
|
+
await page.keyboard.press(key);
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Inject focus/input/blur event listeners into the remote page.
|
|
1813
|
+
* Uses Playwright exposeFunction + addInitScript so the
|
|
1814
|
+
* injected script can call back to Node.js when input elements are focused.
|
|
1815
|
+
*/
|
|
1816
|
+
async injectFocusListener(onEvent) {
|
|
1817
|
+
const page = this.getPage();
|
|
1818
|
+
if (!page)
|
|
1819
|
+
return;
|
|
1820
|
+
try {
|
|
1821
|
+
await page.exposeFunction('__agentBrowserInputEvent', (data) => {
|
|
1822
|
+
onEvent(data);
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
catch {
|
|
1826
|
+
// Already registered from previous injection - safe to continue
|
|
1827
|
+
}
|
|
1828
|
+
const injectScript = `
|
|
1829
|
+
(function() {
|
|
1830
|
+
if (window.__agentBrowserListenerInjected) return;
|
|
1831
|
+
window.__agentBrowserListenerInjected = true;
|
|
1832
|
+
|
|
1833
|
+
document.addEventListener('focus', function(e) {
|
|
1834
|
+
var el = e.target;
|
|
1835
|
+
if (!el) return;
|
|
1836
|
+
var tag = el.tagName;
|
|
1837
|
+
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !el.isContentEditable) return;
|
|
1838
|
+
try {
|
|
1839
|
+
window.__agentBrowserInputEvent({
|
|
1840
|
+
type: 'input_focused',
|
|
1841
|
+
tag: tag,
|
|
1842
|
+
inputType: el.type || '',
|
|
1843
|
+
value: typeof el.value === 'string' ? el.value : '',
|
|
1844
|
+
placeholder: el.placeholder || '',
|
|
1845
|
+
id: el.id || '',
|
|
1846
|
+
selector: (function() {
|
|
1847
|
+
if (el.id) return '#' + el.id;
|
|
1848
|
+
if (el.name && el.name) return '[name="' + el.name + '"]';
|
|
1849
|
+
return el.tagName.toLowerCase();
|
|
1850
|
+
})()
|
|
1851
|
+
});
|
|
1852
|
+
} catch(ex) {}
|
|
1853
|
+
}, true);
|
|
1854
|
+
|
|
1855
|
+
document.addEventListener('input', function(e) {
|
|
1856
|
+
var el = e.target;
|
|
1857
|
+
if (!el) return;
|
|
1858
|
+
var tag = el.tagName;
|
|
1859
|
+
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !el.isContentEditable) return;
|
|
1860
|
+
try {
|
|
1861
|
+
window.__agentBrowserInputEvent({
|
|
1862
|
+
type: 'input_value',
|
|
1863
|
+
text: typeof el.value === 'string' ? el.value : ''
|
|
1864
|
+
});
|
|
1865
|
+
} catch(ex) {}
|
|
1866
|
+
}, true);
|
|
1867
|
+
|
|
1868
|
+
document.addEventListener('blur', function() {
|
|
1869
|
+
try {
|
|
1870
|
+
window.__agentBrowserInputEvent({ type: 'input_blur' });
|
|
1871
|
+
} catch(ex) {}
|
|
1872
|
+
}, true);
|
|
1873
|
+
})();
|
|
1874
|
+
`;
|
|
1875
|
+
// Inject into future navigations
|
|
1876
|
+
await page.addInitScript(injectScript);
|
|
1877
|
+
// Also inject into current page (already loaded)
|
|
1878
|
+
await page.evaluate(injectScript);
|
|
1879
|
+
}
|
|
1568
1880
|
/**
|
|
1569
1881
|
* Check if video recording is currently active
|
|
1570
1882
|
*/
|
|
@@ -2510,6 +2822,24 @@ export class BrowserManager {
|
|
|
2510
2822
|
this.recorderPageHandler = null;
|
|
2511
2823
|
}
|
|
2512
2824
|
}
|
|
2825
|
+
// Clean up network tracking state and listeners
|
|
2826
|
+
if (page) {
|
|
2827
|
+
if (this.requestListener) {
|
|
2828
|
+
page.off('request', this.requestListener);
|
|
2829
|
+
this.requestListener = null;
|
|
2830
|
+
}
|
|
2831
|
+
if (this.responseListener) {
|
|
2832
|
+
page.off('response', this.responseListener);
|
|
2833
|
+
this.responseListener = null;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
this.trackedRequests = [];
|
|
2837
|
+
this.pendingRequests.clear();
|
|
2838
|
+
this.isRequestTrackingEnabled = false;
|
|
2839
|
+
this.isResponseCaptureEnabled = false;
|
|
2840
|
+
this.routes.clear();
|
|
2841
|
+
this.consoleMessages = [];
|
|
2842
|
+
this.pageErrors = [];
|
|
2513
2843
|
// Clean up navigation state
|
|
2514
2844
|
this.navigationHistory = [];
|
|
2515
2845
|
this.navigationHistoryIndex = -1;
|