cdp-skill 1.0.8 → 1.0.15
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 +80 -35
- package/SKILL.md +157 -241
- package/install.js +1 -0
- package/package.json +1 -1
- package/src/aria/index.js +8 -0
- package/src/aria/output-processor.js +173 -0
- package/src/aria/role-query.js +1229 -0
- package/src/aria/snapshot.js +459 -0
- package/src/aria.js +251 -50
- package/src/cdp/browser.js +22 -4
- package/src/cdp-skill.js +246 -69
- package/src/dom/LazyResolver.js +634 -0
- package/src/dom/click-executor.js +366 -94
- package/src/dom/element-locator.js +34 -25
- package/src/dom/fill-executor.js +83 -50
- package/src/dom/index.js +3 -0
- package/src/page/dialog-handler.js +119 -0
- package/src/page/page-controller.js +236 -3
- package/src/runner/context-helpers.js +33 -55
- package/src/runner/execute-dynamic.js +8 -7
- package/src/runner/execute-form.js +11 -11
- package/src/runner/execute-input.js +2 -2
- package/src/runner/execute-interaction.js +105 -126
- package/src/runner/execute-navigation.js +14 -29
- package/src/runner/execute-query.js +17 -11
- package/src/runner/step-executors.js +225 -84
- package/src/runner/step-registry.js +1064 -0
- package/src/runner/step-validator.js +16 -754
- package/src/tests/Aria.test.js +1025 -0
- package/src/tests/ClickExecutor.test.js +170 -50
- package/src/tests/ContextHelpers.test.js +41 -30
- package/src/tests/ExecuteBrowser.test.js +572 -0
- package/src/tests/ExecuteDynamic.test.js +2 -457
- package/src/tests/ExecuteForm.test.js +700 -0
- package/src/tests/ExecuteInput.test.js +540 -0
- package/src/tests/ExecuteInteraction.test.js +319 -0
- package/src/tests/ExecuteQuery.test.js +820 -0
- package/src/tests/FillExecutor.test.js +89 -37
- package/src/tests/LazyResolver.test.js +383 -0
- package/src/tests/StepValidator.test.js +224 -78
- package/src/tests/TestRunner.test.js +38 -27
- package/src/tests/integration.test.js +2 -1
- package/src/types.js +9 -9
- package/src/utils/backoff.js +118 -0
- package/src/utils/cdp-helpers.js +130 -0
- package/src/utils/devices.js +140 -0
- package/src/utils/errors.js +242 -0
- package/src/utils/index.js +65 -0
- package/src/utils/temp.js +75 -0
- package/src/utils/validators.js +433 -0
- package/src/utils.js +14 -1142
|
@@ -30,13 +30,27 @@ const MAX_TIMEOUT = TIMEOUTS.MAX;
|
|
|
30
30
|
* @param {Object} session - CDP session
|
|
31
31
|
* @param {Object} [options] - Configuration options
|
|
32
32
|
* @param {number} [options.timeout=30000] - Default timeout in ms
|
|
33
|
+
* @param {Function} [options.getFrameContext] - Returns contextId when in a non-main frame
|
|
33
34
|
* @returns {Object} Element locator interface
|
|
34
35
|
*/
|
|
35
36
|
export function createElementLocator(session, options = {}) {
|
|
36
37
|
if (!session) throw new Error('CDP session is required');
|
|
37
38
|
|
|
39
|
+
const getFrameContext = options.getFrameContext || null;
|
|
38
40
|
let defaultTimeout = options.timeout || 30000;
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Build Runtime.evaluate params, injecting contextId when in an iframe.
|
|
44
|
+
*/
|
|
45
|
+
function evalParams(expression, returnByValue = false) {
|
|
46
|
+
const params = { expression, returnByValue };
|
|
47
|
+
if (getFrameContext) {
|
|
48
|
+
const contextId = getFrameContext();
|
|
49
|
+
if (contextId) params.contextId = contextId;
|
|
50
|
+
}
|
|
51
|
+
return params;
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
function validateTimeout(timeout) {
|
|
41
55
|
if (typeof timeout !== 'number' || !Number.isFinite(timeout)) return defaultTimeout;
|
|
42
56
|
if (timeout < 0) return 0;
|
|
@@ -59,10 +73,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
59
73
|
|
|
60
74
|
let result;
|
|
61
75
|
try {
|
|
62
|
-
result = await session.send('Runtime.evaluate',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
76
|
+
result = await session.send('Runtime.evaluate',
|
|
77
|
+
evalParams(`document.querySelector(${JSON.stringify(selector)})`, false)
|
|
78
|
+
);
|
|
66
79
|
} catch (error) {
|
|
67
80
|
throw connectionError(error.message, 'Runtime.evaluate (querySelector)');
|
|
68
81
|
}
|
|
@@ -89,10 +102,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
89
102
|
|
|
90
103
|
let result;
|
|
91
104
|
try {
|
|
92
|
-
result = await session.send('Runtime.evaluate',
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
105
|
+
result = await session.send('Runtime.evaluate',
|
|
106
|
+
evalParams(`Array.from(document.querySelectorAll(${JSON.stringify(selector)}))`, false)
|
|
107
|
+
);
|
|
96
108
|
} catch (error) {
|
|
97
109
|
throw connectionError(error.message, 'Runtime.evaluate (querySelectorAll)');
|
|
98
110
|
}
|
|
@@ -175,10 +187,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
175
187
|
while (Date.now() - startTime < validatedTimeout) {
|
|
176
188
|
let result;
|
|
177
189
|
try {
|
|
178
|
-
result = await session.send('Runtime.evaluate',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
190
|
+
result = await session.send('Runtime.evaluate',
|
|
191
|
+
evalParams(checkExpr, true)
|
|
192
|
+
);
|
|
182
193
|
} catch (error) {
|
|
183
194
|
throw connectionError(error.message, 'Runtime.evaluate (waitForText)');
|
|
184
195
|
}
|
|
@@ -278,10 +289,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
278
289
|
|
|
279
290
|
let result;
|
|
280
291
|
try {
|
|
281
|
-
result = await session.send('Runtime.evaluate',
|
|
282
|
-
expression,
|
|
283
|
-
|
|
284
|
-
});
|
|
292
|
+
result = await session.send('Runtime.evaluate',
|
|
293
|
+
evalParams(expression, false)
|
|
294
|
+
);
|
|
285
295
|
} catch (error) {
|
|
286
296
|
throw connectionError(error.message, 'Runtime.evaluate (queryByRole)');
|
|
287
297
|
}
|
|
@@ -403,10 +413,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
403
413
|
|
|
404
414
|
let result;
|
|
405
415
|
try {
|
|
406
|
-
result = await session.send('Runtime.evaluate',
|
|
407
|
-
expression,
|
|
408
|
-
|
|
409
|
-
});
|
|
416
|
+
result = await session.send('Runtime.evaluate',
|
|
417
|
+
evalParams(expression, false)
|
|
418
|
+
);
|
|
410
419
|
} catch (error) {
|
|
411
420
|
throw connectionError(error.message, 'Runtime.evaluate (findElementByText)');
|
|
412
421
|
}
|
|
@@ -514,10 +523,9 @@ export function createElementLocator(session, options = {}) {
|
|
|
514
523
|
|
|
515
524
|
let result;
|
|
516
525
|
try {
|
|
517
|
-
result = await session.send('Runtime.evaluate',
|
|
518
|
-
expression,
|
|
519
|
-
|
|
520
|
-
});
|
|
526
|
+
result = await session.send('Runtime.evaluate',
|
|
527
|
+
evalParams(expression, false)
|
|
528
|
+
);
|
|
521
529
|
} catch (error) {
|
|
522
530
|
throw connectionError(error.message, 'Runtime.evaluate (findElementByTextWithinSelector)');
|
|
523
531
|
}
|
|
@@ -582,6 +590,7 @@ export function createElementLocator(session, options = {}) {
|
|
|
582
590
|
waitForElementByText,
|
|
583
591
|
getBoundingBox,
|
|
584
592
|
getDefaultTimeout: () => defaultTimeout,
|
|
585
|
-
setDefaultTimeout: (timeout) => { defaultTimeout = validateTimeout(timeout); }
|
|
593
|
+
setDefaultTimeout: (timeout) => { defaultTimeout = validateTimeout(timeout); },
|
|
594
|
+
get getFrameContext() { return getFrameContext; }
|
|
586
595
|
};
|
|
587
596
|
}
|
package/src/dom/fill-executor.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { createActionabilityChecker } from './actionability.js';
|
|
17
17
|
import { createElementValidator } from './element-validator.js';
|
|
18
18
|
import { createReactInputFiller } from './react-filler.js';
|
|
19
|
+
import { createLazyResolver } from './LazyResolver.js';
|
|
19
20
|
import {
|
|
20
21
|
sleep,
|
|
21
22
|
elementNotFoundError,
|
|
@@ -31,50 +32,95 @@ import {
|
|
|
31
32
|
* @param {Object} elementLocator - Element locator instance
|
|
32
33
|
* @param {Object} inputEmulator - Input emulator instance
|
|
33
34
|
* @param {Object} [ariaSnapshot] - Optional ARIA snapshot instance
|
|
35
|
+
* @param {Object} [options] - Configuration options
|
|
36
|
+
* @param {Function} [options.getFrameContext] - Returns contextId when in a non-main frame
|
|
34
37
|
* @returns {Object} Fill executor interface
|
|
35
38
|
*/
|
|
36
|
-
export function createFillExecutor(session, elementLocator, inputEmulator, ariaSnapshot = null) {
|
|
39
|
+
export function createFillExecutor(session, elementLocator, inputEmulator, ariaSnapshot = null, options = {}) {
|
|
37
40
|
if (!session) throw new Error('CDP session is required');
|
|
38
41
|
if (!elementLocator) throw new Error('Element locator is required');
|
|
39
42
|
if (!inputEmulator) throw new Error('Input emulator is required');
|
|
40
43
|
|
|
44
|
+
const getFrameContext = options.getFrameContext || null;
|
|
41
45
|
const actionabilityChecker = createActionabilityChecker(session);
|
|
42
46
|
const elementValidator = createElementValidator(session);
|
|
43
47
|
const reactInputFiller = createReactInputFiller(session);
|
|
48
|
+
const lazyResolver = createLazyResolver(session, { getFrameContext });
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Build Runtime.evaluate params, injecting contextId when in an iframe.
|
|
52
|
+
*/
|
|
53
|
+
function evalParams(expression, returnByValue = false) {
|
|
54
|
+
const params = { expression, returnByValue };
|
|
55
|
+
if (getFrameContext) {
|
|
56
|
+
const contextId = getFrameContext();
|
|
57
|
+
if (contextId) params.contextId = contextId;
|
|
50
58
|
}
|
|
59
|
+
return params;
|
|
60
|
+
}
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Select all and fill with value, handling the empty-string case.
|
|
64
|
+
* When value is "" and clear is true, presses Delete after selectAll
|
|
65
|
+
* to actually remove the selected content (insertText("") is a no-op).
|
|
66
|
+
*/
|
|
67
|
+
async function selectAndFill(value, clear) {
|
|
68
|
+
if (clear) {
|
|
69
|
+
await inputEmulator.selectAll();
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
if (value === '' && clear) {
|
|
72
|
+
// insertText("") is a no-op in CDP — press Delete to remove selected text
|
|
73
|
+
await inputEmulator.press('Delete');
|
|
74
|
+
// Dispatch input/change events so frameworks (React, Vue, etc.) react to the clear
|
|
75
|
+
await session.send('Runtime.evaluate', evalParams(`
|
|
76
|
+
(function() {
|
|
77
|
+
const el = document.activeElement;
|
|
78
|
+
if (el) {
|
|
79
|
+
el.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
|
|
80
|
+
el.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
|
81
|
+
}
|
|
82
|
+
})()
|
|
83
|
+
`, true));
|
|
84
|
+
} else {
|
|
85
|
+
await inputEmulator.insertText(String(value));
|
|
59
86
|
}
|
|
87
|
+
}
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
async function fillByRef(ref, value, opts = {}) {
|
|
90
|
+
const { clear = true, react = false } = opts;
|
|
91
|
+
|
|
92
|
+
// LAZY RESOLUTION: Always resolve ref from metadata, never rely on cached element
|
|
93
|
+
// This eliminates stale element errors entirely
|
|
94
|
+
const resolved = await lazyResolver.resolveRef(ref);
|
|
95
|
+
if (!resolved) {
|
|
96
|
+
throw elementNotFoundError(`ref:${ref}`, 0);
|
|
63
97
|
}
|
|
64
98
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
const objectId = resolved.objectId;
|
|
100
|
+
|
|
101
|
+
// Get visibility info using the resolved element
|
|
102
|
+
const visibilityResult = await session.send('Runtime.callFunctionOn', {
|
|
103
|
+
objectId,
|
|
104
|
+
functionDeclaration: `function() {
|
|
105
|
+
const style = window.getComputedStyle(this);
|
|
106
|
+
const rect = this.getBoundingClientRect();
|
|
107
|
+
return {
|
|
108
|
+
isVisible: style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && rect.width > 0 && rect.height > 0,
|
|
109
|
+
box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
|
110
|
+
};
|
|
111
|
+
}`,
|
|
112
|
+
returnByValue: true
|
|
71
113
|
});
|
|
72
114
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
115
|
+
const refInfo = {
|
|
116
|
+
box: visibilityResult.result?.value?.box || resolved.box,
|
|
117
|
+
isVisible: visibilityResult.result?.value?.isVisible ?? true
|
|
118
|
+
};
|
|
76
119
|
|
|
77
|
-
|
|
120
|
+
if (refInfo.isVisible === false) {
|
|
121
|
+
await releaseObject(session, objectId);
|
|
122
|
+
throw new Error(`Element ref:${ref} exists but is not visible. It may be hidden or have zero dimensions.`);
|
|
123
|
+
}
|
|
78
124
|
|
|
79
125
|
const editableCheck = await elementValidator.isEditable(objectId);
|
|
80
126
|
if (!editableCheck.editable) {
|
|
@@ -106,11 +152,7 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
106
152
|
functionDeclaration: `function() { this.focus(); }`
|
|
107
153
|
});
|
|
108
154
|
|
|
109
|
-
|
|
110
|
-
await inputEmulator.selectAll();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
await inputEmulator.insertText(String(value));
|
|
155
|
+
await selectAndFill(value, clear);
|
|
114
156
|
|
|
115
157
|
return { filled: true, ref, method: 'insertText' };
|
|
116
158
|
} finally {
|
|
@@ -153,11 +195,7 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
153
195
|
functionDeclaration: `function() { this.focus(); }`
|
|
154
196
|
});
|
|
155
197
|
|
|
156
|
-
|
|
157
|
-
await inputEmulator.selectAll();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await inputEmulator.insertText(String(value));
|
|
198
|
+
await selectAndFill(value, clear);
|
|
161
199
|
|
|
162
200
|
return { filled: true, selector, method: 'insertText' };
|
|
163
201
|
} catch (e) {
|
|
@@ -277,10 +315,9 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
277
315
|
|
|
278
316
|
let result;
|
|
279
317
|
try {
|
|
280
|
-
result = await session.send('Runtime.evaluate',
|
|
281
|
-
expression,
|
|
282
|
-
|
|
283
|
-
});
|
|
318
|
+
result = await session.send('Runtime.evaluate',
|
|
319
|
+
evalParams(expression, false)
|
|
320
|
+
);
|
|
284
321
|
} catch (error) {
|
|
285
322
|
throw connectionError(error.message, 'Runtime.evaluate (findInputByLabel)');
|
|
286
323
|
}
|
|
@@ -383,11 +420,7 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
383
420
|
functionDeclaration: `function() { this.focus(); }`
|
|
384
421
|
});
|
|
385
422
|
|
|
386
|
-
|
|
387
|
-
await inputEmulator.selectAll();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
await inputEmulator.insertText(String(value));
|
|
423
|
+
await selectAndFill(value, clear);
|
|
391
424
|
|
|
392
425
|
return { filled: true, label, method: 'insertText', foundBy: foundMethod };
|
|
393
426
|
} catch (e) {
|
|
@@ -405,9 +438,9 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
405
438
|
throw new Error('Fill requires value');
|
|
406
439
|
}
|
|
407
440
|
|
|
408
|
-
// Detect if selector looks like a versioned ref (s{N}e{M})
|
|
409
|
-
// This allows {"fill": {"selector": "
|
|
410
|
-
if (!ref && selector && /^s\d+e\d+$/.test(selector)) {
|
|
441
|
+
// Detect if selector looks like a versioned ref (f{frameId}s{N}e{M})
|
|
442
|
+
// This allows {"fill": {"selector": "f0s1e1", "value": "..."}} to work like {"fill": {"ref": "f0s1e1", "value": "..."}}
|
|
443
|
+
if (!ref && selector && /^f(\d+|\[[^\]]+\])s\d+e\d+$/.test(selector)) {
|
|
411
444
|
ref = selector;
|
|
412
445
|
}
|
|
413
446
|
|
|
@@ -430,7 +463,7 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
430
463
|
|
|
431
464
|
async function executeBatch(params) {
|
|
432
465
|
if (!params || typeof params !== 'object') {
|
|
433
|
-
throw new Error('
|
|
466
|
+
throw new Error('fill batch requires an object mapping selectors to values');
|
|
434
467
|
}
|
|
435
468
|
|
|
436
469
|
// Support both formats:
|
|
@@ -450,7 +483,7 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
450
483
|
|
|
451
484
|
const entries = Object.entries(fields);
|
|
452
485
|
if (entries.length === 0) {
|
|
453
|
-
throw new Error('
|
|
486
|
+
throw new Error('fill batch requires at least one field');
|
|
454
487
|
}
|
|
455
488
|
|
|
456
489
|
const results = [];
|
|
@@ -458,8 +491,8 @@ export function createFillExecutor(session, elementLocator, inputEmulator, ariaS
|
|
|
458
491
|
|
|
459
492
|
for (const [selector, value] of entries) {
|
|
460
493
|
try {
|
|
461
|
-
// Match versioned ref format s{N}e{M}
|
|
462
|
-
const isRef = /^s\d+e\d+$/.test(selector);
|
|
494
|
+
// Match versioned ref format f{frameId}s{N}e{M}
|
|
495
|
+
const isRef = /^f(\d+|\[[^\]]+\])s\d+e\d+$/.test(selector);
|
|
463
496
|
|
|
464
497
|
if (isRef) {
|
|
465
498
|
await fillByRef(selector, value, { clear: true, react: useReact });
|
package/src/dom/index.js
CHANGED
|
@@ -76,6 +76,9 @@ export { createKeyboardExecutor } from './keyboard-executor.js';
|
|
|
76
76
|
// Wait executor (waiting operations)
|
|
77
77
|
export { createWaitExecutor } from './wait-executor.js';
|
|
78
78
|
|
|
79
|
+
// Lazy resolver (stateless element resolution)
|
|
80
|
+
export { createLazyResolver } from './LazyResolver.js';
|
|
81
|
+
|
|
79
82
|
// ============================================================================
|
|
80
83
|
// Convenience Functions
|
|
81
84
|
// ============================================================================
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog Handler Module
|
|
3
|
+
* Handles JavaScript alerts, confirms, and prompts
|
|
4
|
+
*
|
|
5
|
+
* PUBLIC EXPORTS:
|
|
6
|
+
* - createDialogHandler(session) - Factory for dialog handler
|
|
7
|
+
*
|
|
8
|
+
* @module cdp-skill/page/dialog-handler
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a dialog handler for JavaScript dialogs
|
|
13
|
+
* @param {import('../types.js').CDPSession} session - CDP session
|
|
14
|
+
* @returns {Object} Dialog handler interface
|
|
15
|
+
*/
|
|
16
|
+
export function createDialogHandler(session) {
|
|
17
|
+
let dialogCallback = null;
|
|
18
|
+
let boundHandler = null;
|
|
19
|
+
const responseQueue = [];
|
|
20
|
+
|
|
21
|
+
function onDialogOpening(params) {
|
|
22
|
+
const { type, message, defaultPrompt } = params;
|
|
23
|
+
|
|
24
|
+
// Default behavior: accept all dialogs
|
|
25
|
+
let accept = true;
|
|
26
|
+
let promptText = undefined;
|
|
27
|
+
|
|
28
|
+
// Check if there's a queued response
|
|
29
|
+
if (responseQueue.length > 0) {
|
|
30
|
+
const queued = responseQueue.shift();
|
|
31
|
+
accept = queued.accept !== false;
|
|
32
|
+
promptText = queued.promptText;
|
|
33
|
+
} else if (dialogCallback) {
|
|
34
|
+
// If custom callback is set, use it
|
|
35
|
+
const result = dialogCallback({ type, message, defaultPrompt });
|
|
36
|
+
accept = result.accept !== false;
|
|
37
|
+
promptText = result.promptText;
|
|
38
|
+
} else {
|
|
39
|
+
// Auto-accept with reasonable defaults for prompts
|
|
40
|
+
if (type === 'prompt') {
|
|
41
|
+
// Use defaultPrompt if available
|
|
42
|
+
// Otherwise, for test automation purposes, use a reasonable default
|
|
43
|
+
if (defaultPrompt !== undefined && defaultPrompt.length > 0) {
|
|
44
|
+
promptText = defaultPrompt;
|
|
45
|
+
} else if (message && message.toLowerCase().includes('prompt')) {
|
|
46
|
+
// For prompt dialogs asking for input, use a test value
|
|
47
|
+
promptText = 'Hello CDP';
|
|
48
|
+
} else {
|
|
49
|
+
promptText = '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle the dialog
|
|
55
|
+
session.send('Page.handleJavaScriptDialog', {
|
|
56
|
+
accept,
|
|
57
|
+
promptText
|
|
58
|
+
}).catch(err => {
|
|
59
|
+
// Ignore errors - dialog may have been already handled
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Enable dialog handling
|
|
65
|
+
* @param {Function} [callback] - Optional callback to customize dialog handling
|
|
66
|
+
* @returns {Promise<void>}
|
|
67
|
+
*/
|
|
68
|
+
async function enable(callback = null) {
|
|
69
|
+
dialogCallback = callback;
|
|
70
|
+
|
|
71
|
+
if (!boundHandler) {
|
|
72
|
+
boundHandler = onDialogOpening;
|
|
73
|
+
session.on('Page.javascriptDialogOpening', boundHandler);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Enable page domain if not already enabled
|
|
77
|
+
try {
|
|
78
|
+
await session.send('Page.enable');
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// Ignore if already enabled
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Disable dialog handling
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async function disable() {
|
|
89
|
+
if (boundHandler) {
|
|
90
|
+
session.off('Page.javascriptDialogOpening', boundHandler);
|
|
91
|
+
boundHandler = null;
|
|
92
|
+
}
|
|
93
|
+
dialogCallback = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set a custom dialog handler
|
|
98
|
+
* @param {Function} callback - Callback({type, message, defaultPrompt}) => {accept, promptText}
|
|
99
|
+
*/
|
|
100
|
+
function setHandler(callback) {
|
|
101
|
+
dialogCallback = callback;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Queue a response for the next dialog
|
|
106
|
+
* @param {boolean} accept - Whether to accept the dialog
|
|
107
|
+
* @param {string} [promptText] - Text to enter for prompts
|
|
108
|
+
*/
|
|
109
|
+
function queueResponse(accept, promptText) {
|
|
110
|
+
responseQueue.push({ accept, promptText });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
enable,
|
|
115
|
+
disable,
|
|
116
|
+
setHandler,
|
|
117
|
+
queueResponse
|
|
118
|
+
};
|
|
119
|
+
}
|