cdp-skill 1.0.2 → 1.0.4

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.
Files changed (78) hide show
  1. package/README.md +3 -0
  2. package/SKILL.md +34 -5
  3. package/package.json +2 -1
  4. package/src/capture/console-capture.js +241 -0
  5. package/src/capture/debug-capture.js +144 -0
  6. package/src/capture/error-aggregator.js +151 -0
  7. package/src/capture/eval-serializer.js +320 -0
  8. package/src/capture/index.js +40 -0
  9. package/src/capture/network-capture.js +211 -0
  10. package/src/capture/pdf-capture.js +256 -0
  11. package/src/capture/screenshot-capture.js +325 -0
  12. package/src/cdp/browser.js +569 -0
  13. package/src/cdp/connection.js +369 -0
  14. package/src/cdp/discovery.js +138 -0
  15. package/src/cdp/index.js +29 -0
  16. package/src/cdp/target-and-session.js +439 -0
  17. package/src/cdp-skill.js +25 -11
  18. package/src/constants.js +79 -0
  19. package/src/dom/actionability.js +638 -0
  20. package/src/dom/click-executor.js +923 -0
  21. package/src/dom/element-handle.js +496 -0
  22. package/src/dom/element-locator.js +475 -0
  23. package/src/dom/element-validator.js +120 -0
  24. package/src/dom/fill-executor.js +489 -0
  25. package/src/dom/index.js +248 -0
  26. package/src/dom/input-emulator.js +406 -0
  27. package/src/dom/keyboard-executor.js +202 -0
  28. package/src/dom/quad-helpers.js +89 -0
  29. package/src/dom/react-filler.js +94 -0
  30. package/src/dom/wait-executor.js +423 -0
  31. package/src/index.js +6 -6
  32. package/src/page/cookie-manager.js +202 -0
  33. package/src/page/dom-stability.js +181 -0
  34. package/src/page/index.js +36 -0
  35. package/src/{page.js → page/page-controller.js} +109 -839
  36. package/src/page/wait-utilities.js +302 -0
  37. package/src/page/web-storage-manager.js +108 -0
  38. package/src/runner/context-helpers.js +224 -0
  39. package/src/runner/execute-browser.js +518 -0
  40. package/src/runner/execute-form.js +315 -0
  41. package/src/runner/execute-input.js +308 -0
  42. package/src/runner/execute-interaction.js +672 -0
  43. package/src/runner/execute-navigation.js +180 -0
  44. package/src/runner/execute-query.js +771 -0
  45. package/src/runner/index.js +51 -0
  46. package/src/runner/step-executors.js +421 -0
  47. package/src/runner/step-validator.js +641 -0
  48. package/src/tests/Actionability.test.js +613 -0
  49. package/src/tests/BrowserClient.test.js +1 -1
  50. package/src/tests/ChromeDiscovery.test.js +1 -1
  51. package/src/tests/ClickExecutor.test.js +554 -0
  52. package/src/tests/ConsoleCapture.test.js +1 -1
  53. package/src/tests/ContextHelpers.test.js +453 -0
  54. package/src/tests/CookieManager.test.js +450 -0
  55. package/src/tests/DebugCapture.test.js +307 -0
  56. package/src/tests/ElementHandle.test.js +1 -1
  57. package/src/tests/ElementLocator.test.js +1 -1
  58. package/src/tests/ErrorAggregator.test.js +1 -1
  59. package/src/tests/EvalSerializer.test.js +391 -0
  60. package/src/tests/FillExecutor.test.js +611 -0
  61. package/src/tests/InputEmulator.test.js +1 -1
  62. package/src/tests/KeyboardExecutor.test.js +430 -0
  63. package/src/tests/NetworkErrorCapture.test.js +1 -1
  64. package/src/tests/PageController.test.js +1 -1
  65. package/src/tests/PdfCapture.test.js +333 -0
  66. package/src/tests/ScreenshotCapture.test.js +1 -1
  67. package/src/tests/SessionRegistry.test.js +1 -1
  68. package/src/tests/StepValidator.test.js +527 -0
  69. package/src/tests/TargetManager.test.js +1 -1
  70. package/src/tests/TestRunner.test.js +1 -1
  71. package/src/tests/WaitStrategy.test.js +1 -1
  72. package/src/tests/WaitUtilities.test.js +508 -0
  73. package/src/tests/WebStorageManager.test.js +333 -0
  74. package/src/types.js +309 -0
  75. package/src/capture.js +0 -1400
  76. package/src/cdp.js +0 -1286
  77. package/src/dom.js +0 -4379
  78. package/src/runner.js +0 -3676
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Keyboard Executor
3
+ * Type and select operations for form inputs
4
+ *
5
+ * EXPORTS:
6
+ * - createKeyboardExecutor(session, elementLocator, inputEmulator) → KeyboardExecutor
7
+ * Methods: executeType, executeSelect
8
+ *
9
+ * DEPENDENCIES:
10
+ * - ./element-validator.js: createElementValidator
11
+ * - ../utils.js: elementNotFoundError, elementNotEditableError
12
+ */
13
+
14
+ import { createElementValidator } from './element-validator.js';
15
+ import { elementNotFoundError, elementNotEditableError } from '../utils.js';
16
+
17
+ /**
18
+ * Create a keyboard executor for handling type and select operations
19
+ * @param {Object} session - CDP session
20
+ * @param {Object} elementLocator - Element locator instance
21
+ * @param {Object} inputEmulator - Input emulator instance
22
+ * @returns {Object} Keyboard executor interface
23
+ */
24
+ export function createKeyboardExecutor(session, elementLocator, inputEmulator) {
25
+ const validator = createElementValidator(session);
26
+
27
+ async function executeType(params) {
28
+ const { selector, text, delay = 0 } = params;
29
+
30
+ if (!selector || text === undefined) {
31
+ throw new Error('Type requires selector and text');
32
+ }
33
+
34
+ const element = await elementLocator.findElement(selector);
35
+ if (!element) {
36
+ throw elementNotFoundError(selector, 0);
37
+ }
38
+
39
+ const editableCheck = await validator.isEditable(element._handle.objectId);
40
+ if (!editableCheck.editable) {
41
+ await element._handle.dispose();
42
+ throw elementNotEditableError(selector, editableCheck.reason);
43
+ }
44
+
45
+ try {
46
+ await element._handle.scrollIntoView({ block: 'center' });
47
+ await element._handle.waitForStability({ frames: 2, timeout: 500 });
48
+
49
+ await element._handle.focus();
50
+
51
+ await inputEmulator.type(String(text), { delay });
52
+
53
+ return {
54
+ selector,
55
+ typed: String(text),
56
+ length: String(text).length
57
+ };
58
+ } finally {
59
+ await element._handle.dispose();
60
+ }
61
+ }
62
+
63
+ async function executeSelect(params) {
64
+ let selector;
65
+ let start = null;
66
+ let end = null;
67
+
68
+ if (typeof params === 'string') {
69
+ selector = params;
70
+ } else if (params && typeof params === 'object') {
71
+ selector = params.selector;
72
+ start = params.start !== undefined ? params.start : null;
73
+ end = params.end !== undefined ? params.end : null;
74
+ } else {
75
+ throw new Error('Select requires a selector string or params object');
76
+ }
77
+
78
+ if (!selector) {
79
+ throw new Error('Select requires selector');
80
+ }
81
+
82
+ const element = await elementLocator.findElement(selector);
83
+ if (!element) {
84
+ throw elementNotFoundError(selector, 0);
85
+ }
86
+
87
+ try {
88
+ await element._handle.scrollIntoView({ block: 'center' });
89
+ await element._handle.waitForStability({ frames: 2, timeout: 500 });
90
+
91
+ await element._handle.focus();
92
+
93
+ const result = await session.send('Runtime.callFunctionOn', {
94
+ objectId: element._handle.objectId,
95
+ functionDeclaration: `function(start, end) {
96
+ const el = this;
97
+ const tagName = el.tagName.toLowerCase();
98
+
99
+ if (tagName === 'input' || tagName === 'textarea') {
100
+ const len = el.value.length;
101
+ const selStart = start !== null ? Math.min(start, len) : 0;
102
+ const selEnd = end !== null ? Math.min(end, len) : len;
103
+
104
+ el.focus();
105
+ el.setSelectionRange(selStart, selEnd);
106
+
107
+ return {
108
+ success: true,
109
+ start: selStart,
110
+ end: selEnd,
111
+ selectedText: el.value.substring(selStart, selEnd),
112
+ totalLength: len
113
+ };
114
+ }
115
+
116
+ if (el.isContentEditable) {
117
+ const range = document.createRange();
118
+ const text = el.textContent || '';
119
+ const len = text.length;
120
+ const selStart = start !== null ? Math.min(start, len) : 0;
121
+ const selEnd = end !== null ? Math.min(end, len) : len;
122
+
123
+ let currentPos = 0;
124
+ let startNode = null, startOffset = 0;
125
+ let endNode = null, endOffset = 0;
126
+
127
+ function findPosition(node, target) {
128
+ if (node.nodeType === Node.TEXT_NODE) {
129
+ const nodeLen = node.textContent.length;
130
+ if (!startNode && currentPos + nodeLen >= selStart) {
131
+ startNode = node;
132
+ startOffset = selStart - currentPos;
133
+ }
134
+ if (!endNode && currentPos + nodeLen >= selEnd) {
135
+ endNode = node;
136
+ endOffset = selEnd - currentPos;
137
+ return true;
138
+ }
139
+ currentPos += nodeLen;
140
+ } else {
141
+ for (const child of node.childNodes) {
142
+ if (findPosition(child, target)) return true;
143
+ }
144
+ }
145
+ return false;
146
+ }
147
+
148
+ findPosition(el, null);
149
+
150
+ if (startNode && endNode) {
151
+ range.setStart(startNode, startOffset);
152
+ range.setEnd(endNode, endOffset);
153
+
154
+ const selection = window.getSelection();
155
+ selection.removeAllRanges();
156
+ selection.addRange(range);
157
+
158
+ return {
159
+ success: true,
160
+ start: selStart,
161
+ end: selEnd,
162
+ selectedText: text.substring(selStart, selEnd),
163
+ totalLength: len
164
+ };
165
+ }
166
+ }
167
+
168
+ return {
169
+ success: false,
170
+ reason: 'Element does not support text selection'
171
+ };
172
+ }`,
173
+ arguments: [
174
+ { value: start },
175
+ { value: end }
176
+ ],
177
+ returnByValue: true
178
+ });
179
+
180
+ const selectionResult = result.result.value;
181
+
182
+ if (!selectionResult.success) {
183
+ throw new Error(selectionResult.reason || 'Selection failed');
184
+ }
185
+
186
+ return {
187
+ selector,
188
+ start: selectionResult.start,
189
+ end: selectionResult.end,
190
+ selectedText: selectionResult.selectedText,
191
+ totalLength: selectionResult.totalLength
192
+ };
193
+ } finally {
194
+ await element._handle.dispose();
195
+ }
196
+ }
197
+
198
+ return {
199
+ executeType,
200
+ executeSelect
201
+ };
202
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Quad Helpers
3
+ * Geometry calculations for content quads (used by CDP for element positioning)
4
+ *
5
+ * EXPORTS:
6
+ * - calculateQuadCenter(quad) → {x, y} - Get center point of a quad
7
+ * - calculateQuadArea(quad) → number - Calculate area using shoelace formula
8
+ * - isPointInQuad(quad, x, y) → boolean - Ray casting point-in-polygon test
9
+ * - getLargestQuad(quads) → quad|null - Find largest quad by area
10
+ *
11
+ * DEPENDENCIES: None
12
+ */
13
+
14
+ /**
15
+ * Calculate center point of a quad
16
+ * Quads are arrays of 8 numbers: [x1,y1, x2,y2, x3,y3, x4,y4]
17
+ * @param {number[]} quad - Quad coordinates
18
+ * @returns {{x: number, y: number}}
19
+ */
20
+ export function calculateQuadCenter(quad) {
21
+ let x = 0, y = 0;
22
+ for (let i = 0; i < 8; i += 2) {
23
+ x += quad[i];
24
+ y += quad[i + 1];
25
+ }
26
+ return { x: x / 4, y: y / 4 };
27
+ }
28
+
29
+ /**
30
+ * Calculate area of a quad using shoelace formula
31
+ * @param {number[]} quad - Quad coordinates
32
+ * @returns {number}
33
+ */
34
+ export function calculateQuadArea(quad) {
35
+ let area = 0;
36
+ for (let i = 0; i < 4; i++) {
37
+ const j = (i + 1) % 4;
38
+ area += quad[i * 2] * quad[j * 2 + 1];
39
+ area -= quad[j * 2] * quad[i * 2 + 1];
40
+ }
41
+ return Math.abs(area) / 2;
42
+ }
43
+
44
+ /**
45
+ * Check if a point is inside a quad using ray casting algorithm
46
+ * @param {number[]} quad - Quad coordinates
47
+ * @param {number} x - Point x
48
+ * @param {number} y - Point y
49
+ * @returns {boolean}
50
+ */
51
+ export function isPointInQuad(quad, x, y) {
52
+ const points = [];
53
+ for (let i = 0; i < 8; i += 2) {
54
+ points.push({ x: quad[i], y: quad[i + 1] });
55
+ }
56
+
57
+ let inside = false;
58
+ for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
59
+ const xi = points[i].x, yi = points[i].y;
60
+ const xj = points[j].x, yj = points[j].y;
61
+
62
+ if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
63
+ inside = !inside;
64
+ }
65
+ }
66
+ return inside;
67
+ }
68
+
69
+ /**
70
+ * Get the largest quad from an array (most likely the visible content area)
71
+ * @param {number[][]} quads - Array of quads
72
+ * @returns {number[]|null}
73
+ */
74
+ export function getLargestQuad(quads) {
75
+ if (!quads || quads.length === 0) return null;
76
+ if (quads.length === 1) return quads[0];
77
+
78
+ let largest = quads[0];
79
+ let largestArea = calculateQuadArea(quads[0]);
80
+
81
+ for (let i = 1; i < quads.length; i++) {
82
+ const area = calculateQuadArea(quads[i]);
83
+ if (area > largestArea) {
84
+ largestArea = area;
85
+ largest = quads[i];
86
+ }
87
+ }
88
+ return largest;
89
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * React Input Filler
3
+ * Handles React controlled component input filling
4
+ *
5
+ * EXPORTS:
6
+ * - createReactInputFiller(session) → ReactInputFiller
7
+ * Methods: fillByObjectId, fillBySelector
8
+ *
9
+ * DEPENDENCIES: None (uses session directly)
10
+ */
11
+
12
+ /**
13
+ * Create a React input filler for handling React controlled components
14
+ * @param {Object} session - CDP session
15
+ * @returns {Object} React input filler interface
16
+ */
17
+ export function createReactInputFiller(session) {
18
+ if (!session) {
19
+ throw new Error('CDP session is required');
20
+ }
21
+
22
+ async function fillByObjectId(objectId, value) {
23
+ const result = await session.send('Runtime.callFunctionOn', {
24
+ objectId,
25
+ functionDeclaration: `function(newValue) {
26
+ const el = this;
27
+ const prototype = el.tagName === 'TEXTAREA'
28
+ ? window.HTMLTextAreaElement.prototype
29
+ : window.HTMLInputElement.prototype;
30
+ const nativeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
31
+ nativeValueSetter.call(el, newValue);
32
+ el.dispatchEvent(new Event('input', { bubbles: true }));
33
+ el.dispatchEvent(new Event('change', { bubbles: true }));
34
+ return { success: true, value: el.value };
35
+ }`,
36
+ arguments: [{ value: String(value) }],
37
+ returnByValue: true
38
+ });
39
+
40
+ if (result.exceptionDetails) {
41
+ const errorText = result.exceptionDetails.exception?.description ||
42
+ result.exceptionDetails.text ||
43
+ 'Unknown error during React fill';
44
+ throw new Error(`React fill failed: ${errorText}`);
45
+ }
46
+
47
+ return result.result.value;
48
+ }
49
+
50
+ async function fillBySelector(selector, value) {
51
+ const result = await session.send('Runtime.evaluate', {
52
+ expression: `
53
+ (function(selector, newValue) {
54
+ const el = document.querySelector(selector);
55
+ if (!el) {
56
+ return { success: false, error: 'Element not found: ' + selector };
57
+ }
58
+ const prototype = el.tagName === 'TEXTAREA'
59
+ ? window.HTMLTextAreaElement.prototype
60
+ : window.HTMLInputElement.prototype;
61
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
62
+ if (!descriptor || !descriptor.set) {
63
+ return { success: false, error: 'Cannot get native value setter' };
64
+ }
65
+ const nativeValueSetter = descriptor.set;
66
+ nativeValueSetter.call(el, newValue);
67
+ el.dispatchEvent(new Event('input', { bubbles: true }));
68
+ el.dispatchEvent(new Event('change', { bubbles: true }));
69
+ return { success: true, value: el.value };
70
+ })(${JSON.stringify(selector)}, ${JSON.stringify(String(value))})
71
+ `,
72
+ returnByValue: true
73
+ });
74
+
75
+ if (result.exceptionDetails) {
76
+ const errorText = result.exceptionDetails.exception?.description ||
77
+ result.exceptionDetails.text ||
78
+ 'Unknown error during React fill';
79
+ throw new Error(`React fill failed: ${errorText}`);
80
+ }
81
+
82
+ const fillResult = result.result.value;
83
+ if (!fillResult.success) {
84
+ throw new Error(fillResult.error);
85
+ }
86
+
87
+ return fillResult;
88
+ }
89
+
90
+ return {
91
+ fillByObjectId,
92
+ fillBySelector
93
+ };
94
+ }