cdp-skill 1.0.16 → 1.0.17
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 +4 -4
- package/SKILL.md +276 -170
- package/package.json +9 -8
- package/{src → scripts}/aria/index.js +1 -1
- package/scripts/aria/role-query.js +295 -0
- package/{src → scripts}/aria.js +11 -5
- package/{src → scripts}/capture/console-capture.js +11 -9
- package/{src → scripts}/capture/screenshot-capture.js +8 -9
- package/{src → scripts}/cdp/connection.js +30 -6
- package/{src → scripts}/cdp-skill.js +7 -6
- package/{src → scripts}/diff.js +7 -6
- package/{src → scripts}/dom/LazyResolver.js +23 -12
- package/{src → scripts}/dom/actionability.js +39 -22
- package/{src → scripts}/dom/click-executor.js +90 -53
- package/{src → scripts}/dom/element-locator.js +4 -4
- package/{src → scripts}/dom/fill-executor.js +8 -4
- package/{src → scripts}/dom/input-emulator.js +47 -9
- package/{src → scripts}/dom/react-filler.js +11 -3
- package/{src → scripts}/dom/wait-executor.js +10 -2
- package/{src → scripts}/page/dialog-handler.js +7 -3
- package/{src → scripts}/page/dom-stability.js +17 -10
- package/{src → scripts}/page/page-controller.js +41 -34
- package/{src → scripts}/runner/context-helpers.js +7 -0
- package/{src → scripts}/runner/execute-browser.js +3 -118
- package/{src → scripts}/runner/execute-dynamic.js +46 -11
- package/{src → scripts}/runner/execute-form.js +6 -4
- package/{src → scripts}/runner/execute-input.js +127 -100
- package/{src → scripts}/runner/execute-interaction.js +31 -46
- package/{src → scripts}/runner/execute-navigation.js +14 -12
- package/{src → scripts}/runner/step-executors.js +28 -9
- package/{src → scripts}/runner/step-registry.js +57 -8
- package/{src → scripts}/runner/step-validator.js +13 -3
- package/{src → scripts}/tests/ExecuteInput.test.js +58 -188
- package/src/aria/role-query.js +0 -1229
- package/src/aria/snapshot.js +0 -459
- /package/{src → scripts}/aria/output-processor.js +0 -0
- /package/{src → scripts}/capture/debug-capture.js +0 -0
- /package/{src → scripts}/capture/error-aggregator.js +0 -0
- /package/{src → scripts}/capture/eval-serializer.js +0 -0
- /package/{src → scripts}/capture/index.js +0 -0
- /package/{src → scripts}/capture/network-capture.js +0 -0
- /package/{src → scripts}/capture/pdf-capture.js +0 -0
- /package/{src → scripts}/cdp/browser.js +0 -0
- /package/{src → scripts}/cdp/discovery.js +0 -0
- /package/{src → scripts}/cdp/index.js +0 -0
- /package/{src → scripts}/cdp/target-and-session.js +0 -0
- /package/{src → scripts}/constants.js +0 -0
- /package/{src → scripts}/dom/element-handle.js +0 -0
- /package/{src → scripts}/dom/element-validator.js +0 -0
- /package/{src → scripts}/dom/index.js +0 -0
- /package/{src → scripts}/dom/keyboard-executor.js +0 -0
- /package/{src → scripts}/dom/quad-helpers.js +0 -0
- /package/{src → scripts}/index.js +0 -0
- /package/{src → scripts}/page/cookie-manager.js +0 -0
- /package/{src → scripts}/page/index.js +0 -0
- /package/{src → scripts}/page/wait-utilities.js +0 -0
- /package/{src → scripts}/page/web-storage-manager.js +0 -0
- /package/{src → scripts}/runner/execute-query.js +0 -0
- /package/{src → scripts}/runner/index.js +0 -0
- /package/{src → scripts}/tests/Actionability.test.js +0 -0
- /package/{src → scripts}/tests/Aria.test.js +0 -0
- /package/{src → scripts}/tests/BrowserClient.test.js +0 -0
- /package/{src → scripts}/tests/CDPConnection.test.js +0 -0
- /package/{src → scripts}/tests/ChromeDiscovery.test.js +0 -0
- /package/{src → scripts}/tests/ClickExecutor.test.js +0 -0
- /package/{src → scripts}/tests/ConsoleCapture.test.js +0 -0
- /package/{src → scripts}/tests/ContextHelpers.test.js +0 -0
- /package/{src → scripts}/tests/CookieManager.test.js +0 -0
- /package/{src → scripts}/tests/DebugCapture.test.js +0 -0
- /package/{src → scripts}/tests/ElementHandle.test.js +0 -0
- /package/{src → scripts}/tests/ElementLocator.test.js +0 -0
- /package/{src → scripts}/tests/ErrorAggregator.test.js +0 -0
- /package/{src → scripts}/tests/EvalSerializer.test.js +0 -0
- /package/{src → scripts}/tests/ExecuteBrowser.test.js +0 -0
- /package/{src → scripts}/tests/ExecuteDynamic.test.js +0 -0
- /package/{src → scripts}/tests/ExecuteForm.test.js +0 -0
- /package/{src → scripts}/tests/ExecuteInteraction.test.js +0 -0
- /package/{src → scripts}/tests/ExecuteQuery.test.js +0 -0
- /package/{src → scripts}/tests/FillExecutor.test.js +0 -0
- /package/{src → scripts}/tests/InputEmulator.test.js +0 -0
- /package/{src → scripts}/tests/KeyboardExecutor.test.js +0 -0
- /package/{src → scripts}/tests/LazyResolver.test.js +0 -0
- /package/{src → scripts}/tests/NetworkErrorCapture.test.js +0 -0
- /package/{src → scripts}/tests/PageController.test.js +0 -0
- /package/{src → scripts}/tests/PdfCapture.test.js +0 -0
- /package/{src → scripts}/tests/ScreenshotCapture.test.js +0 -0
- /package/{src → scripts}/tests/SessionRegistry.test.js +0 -0
- /package/{src → scripts}/tests/StepValidator.test.js +0 -0
- /package/{src → scripts}/tests/TargetManager.test.js +0 -0
- /package/{src → scripts}/tests/TestRunner.test.js +0 -0
- /package/{src → scripts}/tests/WaitStrategy.test.js +0 -0
- /package/{src → scripts}/tests/WaitUtilities.test.js +0 -0
- /package/{src → scripts}/tests/WebStorageManager.test.js +0 -0
- /package/{src → scripts}/tests/integration.test.js +0 -0
- /package/{src → scripts}/types.js +0 -0
- /package/{src → scripts}/utils/backoff.js +0 -0
- /package/{src → scripts}/utils/cdp-helpers.js +0 -0
- /package/{src → scripts}/utils/devices.js +0 -0
- /package/{src → scripts}/utils/errors.js +0 -0
- /package/{src → scripts}/utils/index.js +0 -0
- /package/{src → scripts}/utils/temp.js +0 -0
- /package/{src → scripts}/utils/validators.js +0 -0
- /package/{src → scripts}/utils.js +0 -0
|
@@ -140,8 +140,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function onRequestStarted({ requestId
|
|
144
|
-
if (frameId && frameId !== mainFrameId) return;
|
|
143
|
+
function onRequestStarted({ requestId }) {
|
|
145
144
|
pendingRequests.add(requestId);
|
|
146
145
|
networkRequestCount++;
|
|
147
146
|
lastNetworkActivity = Date.now();
|
|
@@ -424,7 +423,9 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
424
423
|
addListener('Runtime.executionContextCreated', ({ context }) => {
|
|
425
424
|
if (context.auxData && context.auxData.frameId) {
|
|
426
425
|
frameExecutionContexts.set(context.auxData.frameId, context.id);
|
|
427
|
-
|
|
426
|
+
// Update current context when the active frame gets a new context
|
|
427
|
+
// (handles both main frame and iframes after switchToFrame)
|
|
428
|
+
if (context.auxData.frameId === currentFrameId) {
|
|
428
429
|
currentExecutionContextId = context.id;
|
|
429
430
|
}
|
|
430
431
|
}
|
|
@@ -437,6 +438,14 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
437
438
|
break;
|
|
438
439
|
}
|
|
439
440
|
}
|
|
441
|
+
if (executionContextId === currentExecutionContextId) {
|
|
442
|
+
currentExecutionContextId = null;
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
addListener('Runtime.executionContextsCleared', () => {
|
|
447
|
+
frameExecutionContexts.clear();
|
|
448
|
+
currentExecutionContextId = null;
|
|
440
449
|
});
|
|
441
450
|
|
|
442
451
|
addListener('Page.lifecycleEvent', onLifecycleEvent);
|
|
@@ -552,9 +561,8 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
552
561
|
|
|
553
562
|
await waitPromise;
|
|
554
563
|
|
|
555
|
-
if
|
|
556
|
-
|
|
557
|
-
}
|
|
564
|
+
// Note: if abort happened during waitPromise, abortWaiters already rejected it.
|
|
565
|
+
// We don't re-check abortReason here to avoid rejecting a successfully completed navigation.
|
|
558
566
|
|
|
559
567
|
return {
|
|
560
568
|
frameId: response.frameId,
|
|
@@ -646,10 +654,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
646
654
|
*/
|
|
647
655
|
async function getUrl() {
|
|
648
656
|
try {
|
|
649
|
-
const result = await
|
|
650
|
-
expression: 'window.location.href',
|
|
651
|
-
returnByValue: true
|
|
652
|
-
});
|
|
657
|
+
const result = await evaluateInFrame('window.location.href');
|
|
653
658
|
return result.result.value;
|
|
654
659
|
} catch (error) {
|
|
655
660
|
throw connectionError(error.message, 'Runtime.evaluate (getUrl)');
|
|
@@ -662,10 +667,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
662
667
|
*/
|
|
663
668
|
async function getTitle() {
|
|
664
669
|
try {
|
|
665
|
-
const result = await
|
|
666
|
-
expression: 'document.title',
|
|
667
|
-
returnByValue: true
|
|
668
|
-
});
|
|
670
|
+
const result = await evaluateInFrame('document.title');
|
|
669
671
|
return result.result.value;
|
|
670
672
|
} catch (error) {
|
|
671
673
|
throw connectionError(error.message, 'Runtime.evaluate (getTitle)');
|
|
@@ -689,6 +691,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
689
691
|
pendingRequests.clear();
|
|
690
692
|
crashWaiters.clear();
|
|
691
693
|
abortWaiters.clear();
|
|
694
|
+
networkIdleWaiters.clear();
|
|
692
695
|
}
|
|
693
696
|
|
|
694
697
|
/**
|
|
@@ -754,11 +757,11 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
754
757
|
async function setGeolocation(options) {
|
|
755
758
|
const { latitude, longitude, accuracy = 1 } = options;
|
|
756
759
|
|
|
757
|
-
if (latitude < -90 || latitude > 90) {
|
|
758
|
-
throw new Error('Latitude must be between -90 and 90');
|
|
760
|
+
if (typeof latitude !== 'number' || Number.isNaN(latitude) || latitude < -90 || latitude > 90) {
|
|
761
|
+
throw new Error('Latitude must be a number between -90 and 90');
|
|
759
762
|
}
|
|
760
|
-
if (longitude < -180 || longitude > 180) {
|
|
761
|
-
throw new Error('Longitude must be between -180 and 180');
|
|
763
|
+
if (typeof longitude !== 'number' || Number.isNaN(longitude) || longitude < -180 || longitude > 180) {
|
|
764
|
+
throw new Error('Longitude must be a number between -180 and 180');
|
|
762
765
|
}
|
|
763
766
|
|
|
764
767
|
await cdpClient.send('Emulation.setGeolocationOverride', {
|
|
@@ -811,7 +814,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
811
814
|
|
|
812
815
|
// Discover cross-origin iframes via DOM query
|
|
813
816
|
try {
|
|
814
|
-
const
|
|
817
|
+
const evalParams = {
|
|
815
818
|
expression: `
|
|
816
819
|
(function() {
|
|
817
820
|
const iframes = document.querySelectorAll('iframe');
|
|
@@ -826,7 +829,9 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
826
829
|
})()
|
|
827
830
|
`,
|
|
828
831
|
returnByValue: true
|
|
829
|
-
}
|
|
832
|
+
};
|
|
833
|
+
if (currentExecutionContextId) evalParams.contextId = currentExecutionContextId;
|
|
834
|
+
const domResult = await cdpClient.send('Runtime.evaluate', evalParams);
|
|
830
835
|
|
|
831
836
|
const domIframes = domResult.result?.value;
|
|
832
837
|
if (Array.isArray(domIframes)) {
|
|
@@ -894,7 +899,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
894
899
|
targetFrame = allFrames.find(f => f.frame.name === params);
|
|
895
900
|
|
|
896
901
|
if (!targetFrame) {
|
|
897
|
-
const
|
|
902
|
+
const findParams = {
|
|
898
903
|
expression: `
|
|
899
904
|
(function() {
|
|
900
905
|
const iframe = document.querySelector(${JSON.stringify(params)});
|
|
@@ -903,10 +908,12 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
903
908
|
})()
|
|
904
909
|
`,
|
|
905
910
|
returnByValue: true
|
|
906
|
-
}
|
|
911
|
+
};
|
|
912
|
+
if (currentExecutionContextId) findParams.contextId = currentExecutionContextId;
|
|
913
|
+
const result = await cdpClient.send('Runtime.evaluate', findParams);
|
|
907
914
|
|
|
908
|
-
if (result.result
|
|
909
|
-
const
|
|
915
|
+
if (result.result?.value === 'found') {
|
|
916
|
+
const srcParams = {
|
|
910
917
|
expression: `
|
|
911
918
|
(function() {
|
|
912
919
|
const iframe = document.querySelector(${JSON.stringify(params)});
|
|
@@ -918,15 +925,18 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
918
925
|
})()
|
|
919
926
|
`,
|
|
920
927
|
returnByValue: true
|
|
921
|
-
}
|
|
928
|
+
};
|
|
929
|
+
if (currentExecutionContextId) srcParams.contextId = currentExecutionContextId;
|
|
930
|
+
const srcResult = await cdpClient.send('Runtime.evaluate', srcParams);
|
|
922
931
|
|
|
923
|
-
if (srcResult.result
|
|
932
|
+
if (srcResult.result?.value) {
|
|
924
933
|
const { src, name } = srcResult.result.value;
|
|
925
934
|
targetFrame = allFrames.find(f =>
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
935
|
+
f.frame.parentId && (
|
|
936
|
+
(src && f.frame.url === src) ||
|
|
937
|
+
(src && f.frame.url.endsWith(src)) ||
|
|
938
|
+
(name && f.frame.name === name)
|
|
939
|
+
)
|
|
930
940
|
);
|
|
931
941
|
|
|
932
942
|
if (!targetFrame) {
|
|
@@ -1108,10 +1118,7 @@ export function createPageController(cdpClient, options = {}) {
|
|
|
1108
1118
|
*/
|
|
1109
1119
|
async function getViewport() {
|
|
1110
1120
|
try {
|
|
1111
|
-
const result = await
|
|
1112
|
-
expression: '({ width: window.innerWidth, height: window.innerHeight })',
|
|
1113
|
-
returnByValue: true
|
|
1114
|
-
});
|
|
1121
|
+
const result = await evaluateInFrame('({ width: window.innerWidth, height: window.innerHeight })');
|
|
1115
1122
|
return result.result.value;
|
|
1116
1123
|
} catch (error) {
|
|
1117
1124
|
throw connectionError(error.message, 'Runtime.evaluate (getViewport)');
|
|
@@ -69,6 +69,13 @@ export function buildActionContext(action, params, context) {
|
|
|
69
69
|
case 'press': {
|
|
70
70
|
return `Pressed ${params || 'key'}`;
|
|
71
71
|
}
|
|
72
|
+
case 'upload': {
|
|
73
|
+
if (typeof params === 'string') return `Uploaded ${params}`;
|
|
74
|
+
if (Array.isArray(params)) return `Uploaded ${params.length} file(s)`;
|
|
75
|
+
if (params?.selector) return `Uploaded to ${params.selector}`;
|
|
76
|
+
if (params?.ref) return `Uploaded to [ref=${params.ref}]`;
|
|
77
|
+
return 'Uploaded file(s)';
|
|
78
|
+
}
|
|
72
79
|
default:
|
|
73
80
|
return '';
|
|
74
81
|
}
|
|
@@ -120,14 +120,14 @@ export async function executeEval(pageController, params) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Process serialized result if serialization was used
|
|
123
|
-
if (serialize && result.result
|
|
123
|
+
if (serialize && result.result?.value && typeof result.result.value === 'object') {
|
|
124
124
|
const evalSerializer = createEvalSerializer();
|
|
125
125
|
return evalSerializer.processResult(result.result.value);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
return {
|
|
129
|
-
value: result.result
|
|
130
|
-
type: result.result
|
|
129
|
+
value: result.result?.value,
|
|
130
|
+
type: result.result?.type
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -402,121 +402,6 @@ export function formatCommandConsole(consoleCapture, messageCountBefore) {
|
|
|
402
402
|
};
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
/**
|
|
406
|
-
* Run an array of test steps
|
|
407
|
-
* @param {Object} deps - Dependencies
|
|
408
|
-
* @param {Array<Object>} steps - Array of step definitions
|
|
409
|
-
* @param {Object} [options] - Execution options
|
|
410
|
-
* @param {boolean} [options.stopOnError=true] - Stop on first error
|
|
411
|
-
* @param {number} [options.stepTimeout=30000] - Timeout per step
|
|
412
|
-
* @returns {Promise<{status: string, steps: Array, errors: Array}>}
|
|
413
|
-
*/
|
|
414
|
-
export async function runSteps(deps, steps, options = {}) {
|
|
415
|
-
const validation = validateSteps(steps);
|
|
416
|
-
if (!validation.valid) {
|
|
417
|
-
throw stepValidationError(validation.errors);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const stopOnError = options.stopOnError !== false;
|
|
421
|
-
const result = {
|
|
422
|
-
status: 'ok',
|
|
423
|
-
steps: [],
|
|
424
|
-
errors: []
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
// Capture console message count before command starts
|
|
428
|
-
const consoleCountBefore = deps.consoleCapture ? deps.consoleCapture.getMessages().length : 0;
|
|
429
|
-
|
|
430
|
-
// Feature 8.1: Capture BEFORE state at command start (for diff baseline)
|
|
431
|
-
let beforeUrl, beforeViewport, beforeSnapshot;
|
|
432
|
-
const contextCapture = deps.pageController ? createContextCapture(deps.pageController.session) : null;
|
|
433
|
-
|
|
434
|
-
if (deps.ariaSnapshot && contextCapture) {
|
|
435
|
-
try {
|
|
436
|
-
beforeUrl = await getCurrentUrl(deps.pageController.session);
|
|
437
|
-
// Capture viewport-only snapshot for command-level diff
|
|
438
|
-
// Use preserveRefs to avoid clobbering refs from snapshotSearch
|
|
439
|
-
// Use internal to avoid incrementing snapshot ID (this is for diff, not agent-facing)
|
|
440
|
-
beforeViewport = await deps.ariaSnapshot.generate({ mode: 'ai', viewportOnly: true, preserveRefs: true, internal: true });
|
|
441
|
-
} catch {
|
|
442
|
-
// Ignore initial snapshot errors - will just skip diff comparison
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
for (const step of steps) {
|
|
447
|
-
const stepResult = await executeStep(deps, step, options);
|
|
448
|
-
result.steps.push(stepResult);
|
|
449
|
-
|
|
450
|
-
if (stepResult.status === 'error') {
|
|
451
|
-
result.status = 'error';
|
|
452
|
-
result.errors.push({
|
|
453
|
-
step: result.steps.length,
|
|
454
|
-
action: stepResult.action,
|
|
455
|
-
error: stepResult.error
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
if (stopOnError) {
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
// 'skipped' (optional) steps don't fail the run
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Wait for async console messages after steps complete
|
|
466
|
-
if (deps.consoleCapture) {
|
|
467
|
-
await sleep(250);
|
|
468
|
-
const consoleSummary = formatCommandConsole(deps.consoleCapture, consoleCountBefore);
|
|
469
|
-
if (consoleSummary) {
|
|
470
|
-
result.console = consoleSummary;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// Feature 8.1: Capture AFTER state and compute command-level diff
|
|
475
|
-
if (deps.ariaSnapshot && contextCapture && beforeViewport) {
|
|
476
|
-
try {
|
|
477
|
-
const afterUrl = await getCurrentUrl(deps.pageController.session);
|
|
478
|
-
const afterContext = await contextCapture.captureContext();
|
|
479
|
-
|
|
480
|
-
// Capture both viewport and full page snapshots
|
|
481
|
-
// Use preserveRefs to avoid clobbering refs from snapshotSearch
|
|
482
|
-
// Use internal to avoid incrementing snapshot ID (this is for diff, not agent-facing)
|
|
483
|
-
const afterViewport = await deps.ariaSnapshot.generate({ mode: 'ai', viewportOnly: true, preserveRefs: true, internal: true });
|
|
484
|
-
const afterFull = await deps.ariaSnapshot.generate({ mode: 'ai', viewportOnly: false, preserveRefs: true, internal: true });
|
|
485
|
-
|
|
486
|
-
const navigated = contextCapture.isNavigation(beforeUrl, afterUrl);
|
|
487
|
-
|
|
488
|
-
// Save full page snapshot to file (use tabAlias for filename)
|
|
489
|
-
const fullSnapshotPath = await resolveTempPath(`${options.tabAlias || 'command'}.after.yaml`, '.yaml');
|
|
490
|
-
await fs.writeFile(fullSnapshotPath, afterFull.yaml || '', 'utf8');
|
|
491
|
-
|
|
492
|
-
// Add command-level results
|
|
493
|
-
result.navigated = navigated;
|
|
494
|
-
result.fullSnapshot = fullSnapshotPath;
|
|
495
|
-
result.context = afterContext;
|
|
496
|
-
|
|
497
|
-
// Always include viewport snapshot inline
|
|
498
|
-
result.viewportSnapshot = afterViewport.yaml;
|
|
499
|
-
result.truncated = afterViewport.truncated || false;
|
|
500
|
-
|
|
501
|
-
// For same-page interactions, compute viewport diff
|
|
502
|
-
if (!navigated && beforeViewport?.yaml) {
|
|
503
|
-
const differ = createSnapshotDiffer();
|
|
504
|
-
const viewportDiff = differ.computeDiff(beforeViewport.yaml, afterViewport.yaml);
|
|
505
|
-
|
|
506
|
-
// Report changes if any significant changes found
|
|
507
|
-
if (differ.hasSignificantChanges(viewportDiff)) {
|
|
508
|
-
const actionContext = buildCommandContext(steps);
|
|
509
|
-
result.changes = differ.formatDiff(viewportDiff, { actionContext });
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
} catch (e) {
|
|
513
|
-
result.viewportSnapshotError = e.message;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return result;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
405
|
/**
|
|
521
406
|
* Execute a validate step - query validation state of an element
|
|
522
407
|
*/
|
|
@@ -65,8 +65,20 @@ export async function executePageFunction(pageController, params) {
|
|
|
65
65
|
const arg = useRefs ? 'window.__ariaRefs' : '';
|
|
66
66
|
const serializerFn = getSerializationWrapper();
|
|
67
67
|
|
|
68
|
+
// Detect async functions to await their return value
|
|
69
|
+
const isAsync = /^async[\s(]/.test(fn.trim()) ||
|
|
70
|
+
(fn.trim().startsWith('(') && /^async[\s(]/.test(fn.trim().slice(1).trim()));
|
|
71
|
+
|
|
68
72
|
// Wrap the agent function so its return value is serialized
|
|
69
|
-
|
|
73
|
+
// Use async wrapper when the function is async so we can await its result
|
|
74
|
+
const wrapped = isAsync
|
|
75
|
+
? `(async function() {
|
|
76
|
+
const __fn = ${fn};
|
|
77
|
+
const __serialize = ${serializerFn};
|
|
78
|
+
const __result = await __fn(${arg});
|
|
79
|
+
return __serialize(__result);
|
|
80
|
+
})()`
|
|
81
|
+
: `(function() {
|
|
70
82
|
const __fn = ${fn};
|
|
71
83
|
const __serialize = ${serializerFn};
|
|
72
84
|
const __result = __fn(${arg});
|
|
@@ -75,7 +87,7 @@ export async function executePageFunction(pageController, params) {
|
|
|
75
87
|
|
|
76
88
|
const evalPromise = pageController.evaluateInFrame(wrapped, {
|
|
77
89
|
returnByValue: true,
|
|
78
|
-
awaitPromise:
|
|
90
|
+
awaitPromise: isAsync
|
|
79
91
|
});
|
|
80
92
|
|
|
81
93
|
let result;
|
|
@@ -84,8 +96,14 @@ export async function executePageFunction(pageController, params) {
|
|
|
84
96
|
const tp = new Promise((_, reject) => {
|
|
85
97
|
tid = setTimeout(() => reject(new Error(`pageFunction timed out after ${timeout}ms`)), timeout);
|
|
86
98
|
});
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
try {
|
|
100
|
+
result = await Promise.race([evalPromise, tp]);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
evalPromise.catch(() => {});
|
|
103
|
+
throw err;
|
|
104
|
+
} finally {
|
|
105
|
+
clearTimeout(tid);
|
|
106
|
+
}
|
|
89
107
|
} else {
|
|
90
108
|
result = await evalPromise;
|
|
91
109
|
}
|
|
@@ -96,7 +114,7 @@ export async function executePageFunction(pageController, params) {
|
|
|
96
114
|
throw new Error(`pageFunction error: ${errorText}\nSource: ${fn.substring(0, 200)}`);
|
|
97
115
|
}
|
|
98
116
|
|
|
99
|
-
return processSerializedResult(result.result
|
|
117
|
+
return processSerializedResult(result.result?.value);
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
// ---------------------------------------------------------------------------
|
|
@@ -122,7 +140,18 @@ export async function executePoll(pageController, params) {
|
|
|
122
140
|
}
|
|
123
141
|
|
|
124
142
|
const serializerFn = getSerializationWrapper();
|
|
125
|
-
|
|
143
|
+
|
|
144
|
+
// Detect async predicates to properly await their return value
|
|
145
|
+
const isAsync = /^async[\s(]/.test(fn.trim()) ||
|
|
146
|
+
(fn.trim().startsWith('(') && /^async[\s(]/.test(fn.trim().slice(1).trim()));
|
|
147
|
+
|
|
148
|
+
const expression = isAsync
|
|
149
|
+
? `(async function() {
|
|
150
|
+
const __fn = ${fn};
|
|
151
|
+
const __serialize = ${serializerFn};
|
|
152
|
+
return __serialize(await __fn());
|
|
153
|
+
})()`
|
|
154
|
+
: `(function() {
|
|
126
155
|
const __fn = ${fn};
|
|
127
156
|
const __serialize = ${serializerFn};
|
|
128
157
|
return __serialize(__fn());
|
|
@@ -134,7 +163,7 @@ export async function executePoll(pageController, params) {
|
|
|
134
163
|
while (true) {
|
|
135
164
|
const result = await pageController.evaluateInFrame(expression, {
|
|
136
165
|
returnByValue: true,
|
|
137
|
-
awaitPromise:
|
|
166
|
+
awaitPromise: isAsync
|
|
138
167
|
});
|
|
139
168
|
|
|
140
169
|
if (result.exceptionDetails) {
|
|
@@ -143,11 +172,11 @@ export async function executePoll(pageController, params) {
|
|
|
143
172
|
throw new Error(`poll error: ${errorText}\nSource: ${fn.substring(0, 200)}`);
|
|
144
173
|
}
|
|
145
174
|
|
|
146
|
-
const processed = processSerializedResult(result.result
|
|
175
|
+
const processed = processSerializedResult(result.result?.value);
|
|
147
176
|
lastValue = processed;
|
|
148
177
|
|
|
149
178
|
// Check truthiness from the raw value (before serialization wrapping)
|
|
150
|
-
const rawVal = result.result
|
|
179
|
+
const rawVal = result.result?.value;
|
|
151
180
|
const isTruthy = rawVal !== null && rawVal !== undefined &&
|
|
152
181
|
rawVal !== false && rawVal !== 0 && rawVal !== '' &&
|
|
153
182
|
!(typeof rawVal === 'object' && rawVal !== null && rawVal.type === 'null') &&
|
|
@@ -336,8 +365,14 @@ export async function executePipeline(pageController, params) {
|
|
|
336
365
|
const tp = new Promise((_, reject) => {
|
|
337
366
|
tid = setTimeout(() => reject(new Error(`pipeline timed out after ${timeout}ms`)), timeout);
|
|
338
367
|
});
|
|
339
|
-
|
|
340
|
-
|
|
368
|
+
try {
|
|
369
|
+
result = await Promise.race([evalPromise, tp]);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
evalPromise.catch(() => {});
|
|
372
|
+
throw err;
|
|
373
|
+
} finally {
|
|
374
|
+
clearTimeout(tid);
|
|
375
|
+
}
|
|
341
376
|
} else {
|
|
342
377
|
result = await evalPromise;
|
|
343
378
|
}
|
|
@@ -105,8 +105,10 @@ export async function executeExtract(deps, params) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
// Get data rows
|
|
109
|
-
const dataRows = tableEl.
|
|
108
|
+
// Get data rows - prefer tbody rows, fall back to all rows
|
|
109
|
+
const dataRows = tableEl.querySelector('tbody')
|
|
110
|
+
? tableEl.querySelectorAll('tbody tr')
|
|
111
|
+
: tableEl.querySelectorAll('tr');
|
|
110
112
|
let count = 0;
|
|
111
113
|
for (const row of dataRows) {
|
|
112
114
|
// Skip header row
|
|
@@ -222,7 +224,7 @@ export async function executeAssert(pageController, elementLocator, params) {
|
|
|
222
224
|
|
|
223
225
|
// URL assertion
|
|
224
226
|
if (params.url) {
|
|
225
|
-
const currentUrl = await pageController.getUrl();
|
|
227
|
+
const currentUrl = (await pageController.getUrl()) || '';
|
|
226
228
|
const urlAssertion = { type: 'url', actual: currentUrl };
|
|
227
229
|
|
|
228
230
|
if (params.url.contains) {
|
|
@@ -264,7 +266,7 @@ export async function executeAssert(pageController, elementLocator, params) {
|
|
|
264
266
|
})()
|
|
265
267
|
`);
|
|
266
268
|
|
|
267
|
-
const actualText = textResult.result
|
|
269
|
+
const actualText = textResult.result?.value ?? null;
|
|
268
270
|
textAssertion.found = actualText !== null;
|
|
269
271
|
|
|
270
272
|
if (actualText === null) {
|