chrome-cdp-cli 1.2.2 → 1.3.0

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.
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClickHandler = void 0;
4
+ class ClickHandler {
5
+ constructor() {
6
+ this.name = 'click';
7
+ }
8
+ async execute(client, args) {
9
+ const clickArgs = args;
10
+ if (!clickArgs.selector) {
11
+ return {
12
+ success: false,
13
+ error: 'CSS selector is required for click command'
14
+ };
15
+ }
16
+ if (typeof clickArgs.selector !== 'string') {
17
+ return {
18
+ success: false,
19
+ error: 'CSS selector must be a string'
20
+ };
21
+ }
22
+ try {
23
+ await client.send('DOM.enable');
24
+ await client.send('Runtime.enable');
25
+ const timeout = clickArgs.timeout || 5000;
26
+ const waitForElement = clickArgs.waitForElement !== false;
27
+ if (waitForElement) {
28
+ const elementFound = await this.waitForElement(client, clickArgs.selector, timeout);
29
+ if (!elementFound) {
30
+ return {
31
+ success: false,
32
+ error: `Element with selector "${clickArgs.selector}" not found within ${timeout}ms`
33
+ };
34
+ }
35
+ }
36
+ const cdpResult = await this.clickViaCDP(client, clickArgs.selector);
37
+ if (cdpResult.success) {
38
+ return cdpResult;
39
+ }
40
+ const evalResult = await this.clickViaEval(client, clickArgs.selector);
41
+ return evalResult;
42
+ }
43
+ catch (error) {
44
+ return {
45
+ success: false,
46
+ error: error instanceof Error ? error.message : String(error)
47
+ };
48
+ }
49
+ }
50
+ async waitForElement(client, selector, timeout) {
51
+ const startTime = Date.now();
52
+ while (Date.now() - startTime < timeout) {
53
+ try {
54
+ const result = await client.send('Runtime.evaluate', {
55
+ expression: `document.querySelector('${selector.replace(/'/g, "\\'")}') !== null`,
56
+ returnByValue: true
57
+ });
58
+ if (result.result.value) {
59
+ return true;
60
+ }
61
+ await new Promise(resolve => setTimeout(resolve, 100));
62
+ }
63
+ catch (error) {
64
+ await new Promise(resolve => setTimeout(resolve, 100));
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ async clickViaCDP(client, selector) {
70
+ try {
71
+ const documentResponse = await client.send('DOM.getDocument');
72
+ const rootNodeId = documentResponse.root.nodeId;
73
+ const queryResponse = await client.send('DOM.querySelector', {
74
+ nodeId: rootNodeId,
75
+ selector: selector
76
+ });
77
+ if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
78
+ return {
79
+ success: false,
80
+ error: `Element with selector "${selector}" not found`
81
+ };
82
+ }
83
+ const objectResponse = await client.send('DOM.resolveNode', {
84
+ nodeId: queryResponse.nodeId
85
+ });
86
+ if (!objectResponse.object?.objectId) {
87
+ return {
88
+ success: false,
89
+ error: 'Failed to resolve element to remote object'
90
+ };
91
+ }
92
+ const clickResponse = await client.send('Runtime.callFunctionOn', {
93
+ objectId: objectResponse.object.objectId,
94
+ functionDeclaration: `
95
+ function() {
96
+ // Scroll element into view if needed
97
+ this.scrollIntoView({ behavior: 'instant', block: 'center' });
98
+
99
+ // Trigger click event
100
+ this.click();
101
+
102
+ return {
103
+ success: true,
104
+ tagName: this.tagName,
105
+ id: this.id,
106
+ className: this.className
107
+ };
108
+ }
109
+ `,
110
+ returnByValue: true,
111
+ userGesture: true
112
+ });
113
+ if (clickResponse.exceptionDetails) {
114
+ return {
115
+ success: false,
116
+ error: `Click execution failed: ${clickResponse.exceptionDetails.exception?.description || clickResponse.exceptionDetails.text}`
117
+ };
118
+ }
119
+ return {
120
+ success: true,
121
+ data: {
122
+ selector: selector,
123
+ element: clickResponse.result.value,
124
+ method: 'CDP'
125
+ }
126
+ };
127
+ }
128
+ catch (error) {
129
+ return {
130
+ success: false,
131
+ error: `CDP click failed: ${error instanceof Error ? error.message : String(error)}`
132
+ };
133
+ }
134
+ }
135
+ async clickViaEval(client, selector) {
136
+ try {
137
+ const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
138
+ const expression = `
139
+ (function() {
140
+ const element = document.querySelector('${escapedSelector}');
141
+ if (!element) {
142
+ throw new Error('Element with selector "${escapedSelector}" not found');
143
+ }
144
+
145
+ // Scroll element into view if needed
146
+ element.scrollIntoView({ behavior: 'instant', block: 'center' });
147
+
148
+ // Trigger click event
149
+ element.click();
150
+
151
+ return {
152
+ success: true,
153
+ tagName: element.tagName,
154
+ id: element.id,
155
+ className: element.className
156
+ };
157
+ })()
158
+ `;
159
+ const response = await client.send('Runtime.evaluate', {
160
+ expression: expression,
161
+ returnByValue: true,
162
+ userGesture: true
163
+ });
164
+ if (response.exceptionDetails) {
165
+ return {
166
+ success: false,
167
+ error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
168
+ };
169
+ }
170
+ return {
171
+ success: true,
172
+ data: {
173
+ selector: selector,
174
+ element: response.result.value,
175
+ method: 'eval'
176
+ }
177
+ };
178
+ }
179
+ catch (error) {
180
+ return {
181
+ success: false,
182
+ error: `Eval click failed: ${error instanceof Error ? error.message : String(error)}`
183
+ };
184
+ }
185
+ }
186
+ validateArgs(args) {
187
+ if (typeof args !== 'object' || args === null) {
188
+ return false;
189
+ }
190
+ const clickArgs = args;
191
+ if (!clickArgs.selector || typeof clickArgs.selector !== 'string') {
192
+ return false;
193
+ }
194
+ if (clickArgs.waitForElement !== undefined && typeof clickArgs.waitForElement !== 'boolean') {
195
+ return false;
196
+ }
197
+ if (clickArgs.timeout !== undefined && typeof clickArgs.timeout !== 'number') {
198
+ return false;
199
+ }
200
+ return true;
201
+ }
202
+ getHelp() {
203
+ return `
204
+ click - Click on an element using CSS selector
205
+
206
+ Usage:
207
+ click <selector>
208
+ click "#submit-button"
209
+ click ".menu-item" --timeout 10000
210
+ click "button[type='submit']" --no-wait
211
+
212
+ Arguments:
213
+ <selector> CSS selector for the element to click
214
+
215
+ Options:
216
+ --wait-for-element Wait for element to be available (default: true)
217
+ --no-wait Don't wait for element (same as --wait-for-element=false)
218
+ --timeout <ms> Timeout for waiting for element (default: 5000ms)
219
+
220
+ Examples:
221
+ # Click a button by ID
222
+ click "#submit-button"
223
+
224
+ # Click a link by text content (using CSS selector)
225
+ click "a[href='/login']"
226
+
227
+ # Click with custom timeout
228
+ click ".slow-loading-button" --timeout 10000
229
+
230
+ # Click without waiting (fail immediately if not found)
231
+ click "#optional-element" --no-wait
232
+
233
+ Note:
234
+ - Uses CDP DOM.querySelector and Runtime.callFunctionOn for precise control
235
+ - Falls back to JavaScript eval if CDP approach fails
236
+ - Automatically scrolls element into view before clicking
237
+ - Triggers actual click events that work with event listeners
238
+ `;
239
+ }
240
+ }
241
+ exports.ClickHandler = ClickHandler;
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DragHandler = void 0;
4
+ class DragHandler {
5
+ constructor() {
6
+ this.name = 'drag';
7
+ }
8
+ async execute(client, args) {
9
+ const dragArgs = args;
10
+ if (!dragArgs.sourceSelector) {
11
+ return {
12
+ success: false,
13
+ error: 'Source CSS selector is required for drag command'
14
+ };
15
+ }
16
+ if (typeof dragArgs.sourceSelector !== 'string') {
17
+ return {
18
+ success: false,
19
+ error: 'Source CSS selector must be a string'
20
+ };
21
+ }
22
+ if (!dragArgs.targetSelector) {
23
+ return {
24
+ success: false,
25
+ error: 'Target CSS selector is required for drag command'
26
+ };
27
+ }
28
+ if (typeof dragArgs.targetSelector !== 'string') {
29
+ return {
30
+ success: false,
31
+ error: 'Target CSS selector must be a string'
32
+ };
33
+ }
34
+ try {
35
+ await client.send('Runtime.enable');
36
+ const timeout = dragArgs.timeout || 5000;
37
+ const waitForElement = dragArgs.waitForElement !== false;
38
+ if (waitForElement) {
39
+ const sourceFound = await this.waitForElement(client, dragArgs.sourceSelector, timeout);
40
+ if (!sourceFound) {
41
+ return {
42
+ success: false,
43
+ error: `Source element with selector "${dragArgs.sourceSelector}" not found within ${timeout}ms`
44
+ };
45
+ }
46
+ const targetFound = await this.waitForElement(client, dragArgs.targetSelector, timeout);
47
+ if (!targetFound) {
48
+ return {
49
+ success: false,
50
+ error: `Target element with selector "${dragArgs.targetSelector}" not found within ${timeout}ms`
51
+ };
52
+ }
53
+ }
54
+ const result = await this.dragViaEval(client, dragArgs.sourceSelector, dragArgs.targetSelector);
55
+ return result;
56
+ }
57
+ catch (error) {
58
+ return {
59
+ success: false,
60
+ error: error instanceof Error ? error.message : String(error)
61
+ };
62
+ }
63
+ }
64
+ async waitForElement(client, selector, timeout) {
65
+ const startTime = Date.now();
66
+ while (Date.now() - startTime < timeout) {
67
+ try {
68
+ const result = await client.send('Runtime.evaluate', {
69
+ expression: `document.querySelector('${selector.replace(/'/g, "\\'")}') !== null`,
70
+ returnByValue: true
71
+ });
72
+ if (result.result.value) {
73
+ return true;
74
+ }
75
+ await new Promise(resolve => setTimeout(resolve, 100));
76
+ }
77
+ catch (error) {
78
+ await new Promise(resolve => setTimeout(resolve, 100));
79
+ }
80
+ }
81
+ return false;
82
+ }
83
+ async dragViaEval(client, sourceSelector, targetSelector) {
84
+ try {
85
+ const escapedSource = sourceSelector.replace(/'/g, "\\'").replace(/"/g, '\\"');
86
+ const escapedTarget = targetSelector.replace(/'/g, "\\'").replace(/"/g, '\\"');
87
+ const expression = `
88
+ (function() {
89
+ const sourceElement = document.querySelector('${escapedSource}');
90
+ if (!sourceElement) {
91
+ throw new Error('Source element with selector "${escapedSource}" not found');
92
+ }
93
+
94
+ const targetElement = document.querySelector('${escapedTarget}');
95
+ if (!targetElement) {
96
+ throw new Error('Target element with selector "${escapedTarget}" not found');
97
+ }
98
+
99
+ // Get element positions
100
+ const sourceRect = sourceElement.getBoundingClientRect();
101
+ const targetRect = targetElement.getBoundingClientRect();
102
+
103
+ // Calculate center positions
104
+ const sourceX = sourceRect.left + sourceRect.width / 2;
105
+ const sourceY = sourceRect.top + sourceRect.height / 2;
106
+ const targetX = targetRect.left + targetRect.width / 2;
107
+ const targetY = targetRect.top + targetRect.height / 2;
108
+
109
+ // Create and dispatch dragstart event
110
+ const dragStartEvent = new DragEvent('dragstart', {
111
+ bubbles: true,
112
+ cancelable: true,
113
+ view: window,
114
+ clientX: sourceX,
115
+ clientY: sourceY,
116
+ dataTransfer: new DataTransfer()
117
+ });
118
+ sourceElement.dispatchEvent(dragStartEvent);
119
+
120
+ // Create and dispatch dragenter event on target
121
+ const dragEnterEvent = new DragEvent('dragenter', {
122
+ bubbles: true,
123
+ cancelable: true,
124
+ view: window,
125
+ clientX: targetX,
126
+ clientY: targetY,
127
+ dataTransfer: dragStartEvent.dataTransfer
128
+ });
129
+ targetElement.dispatchEvent(dragEnterEvent);
130
+
131
+ // Create and dispatch dragover event on target
132
+ const dragOverEvent = new DragEvent('dragover', {
133
+ bubbles: true,
134
+ cancelable: true,
135
+ view: window,
136
+ clientX: targetX,
137
+ clientY: targetY,
138
+ dataTransfer: dragStartEvent.dataTransfer
139
+ });
140
+ targetElement.dispatchEvent(dragOverEvent);
141
+
142
+ // Create and dispatch drop event on target
143
+ const dropEvent = new DragEvent('drop', {
144
+ bubbles: true,
145
+ cancelable: true,
146
+ view: window,
147
+ clientX: targetX,
148
+ clientY: targetY,
149
+ dataTransfer: dragStartEvent.dataTransfer
150
+ });
151
+ targetElement.dispatchEvent(dropEvent);
152
+
153
+ // Create and dispatch dragend event on source
154
+ const dragEndEvent = new DragEvent('dragend', {
155
+ bubbles: true,
156
+ cancelable: true,
157
+ view: window,
158
+ clientX: targetX,
159
+ clientY: targetY,
160
+ dataTransfer: dragStartEvent.dataTransfer
161
+ });
162
+ sourceElement.dispatchEvent(dragEndEvent);
163
+
164
+ return {
165
+ success: true,
166
+ source: {
167
+ tagName: sourceElement.tagName,
168
+ id: sourceElement.id,
169
+ className: sourceElement.className,
170
+ position: { x: sourceX, y: sourceY }
171
+ },
172
+ target: {
173
+ tagName: targetElement.tagName,
174
+ id: targetElement.id,
175
+ className: targetElement.className,
176
+ position: { x: targetX, y: targetY }
177
+ }
178
+ };
179
+ })()
180
+ `;
181
+ const response = await client.send('Runtime.evaluate', {
182
+ expression: expression,
183
+ returnByValue: true,
184
+ userGesture: true
185
+ });
186
+ if (response.exceptionDetails) {
187
+ return {
188
+ success: false,
189
+ error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
190
+ };
191
+ }
192
+ return {
193
+ success: true,
194
+ data: {
195
+ sourceSelector: sourceSelector,
196
+ targetSelector: targetSelector,
197
+ result: response.result.value
198
+ }
199
+ };
200
+ }
201
+ catch (error) {
202
+ return {
203
+ success: false,
204
+ error: `Drag operation failed: ${error instanceof Error ? error.message : String(error)}`
205
+ };
206
+ }
207
+ }
208
+ validateArgs(args) {
209
+ if (typeof args !== 'object' || args === null) {
210
+ return false;
211
+ }
212
+ const dragArgs = args;
213
+ if (!dragArgs.sourceSelector || typeof dragArgs.sourceSelector !== 'string') {
214
+ return false;
215
+ }
216
+ if (!dragArgs.targetSelector || typeof dragArgs.targetSelector !== 'string') {
217
+ return false;
218
+ }
219
+ if (dragArgs.waitForElement !== undefined && typeof dragArgs.waitForElement !== 'boolean') {
220
+ return false;
221
+ }
222
+ if (dragArgs.timeout !== undefined && typeof dragArgs.timeout !== 'number') {
223
+ return false;
224
+ }
225
+ return true;
226
+ }
227
+ getHelp() {
228
+ return `
229
+ drag - Perform drag and drop operation from source to target element
230
+
231
+ Usage:
232
+ drag <sourceSelector> <targetSelector>
233
+ drag "#draggable" "#dropzone"
234
+ drag ".item" ".container" --timeout 10000
235
+ drag "#source" "#target" --no-wait
236
+
237
+ Arguments:
238
+ <sourceSelector> CSS selector for the element to drag
239
+ <targetSelector> CSS selector for the drop target
240
+
241
+ Options:
242
+ --wait-for-element Wait for elements to be available (default: true)
243
+ --no-wait Don't wait for elements (same as --wait-for-element=false)
244
+ --timeout <ms> Timeout for waiting for elements (default: 5000ms)
245
+
246
+ Examples:
247
+ # Drag an item to a dropzone
248
+ drag "#draggable-item" "#drop-zone"
249
+
250
+ # Drag a file to upload area
251
+ drag ".file-item" ".upload-area"
252
+
253
+ # Drag with custom timeout
254
+ drag ".slow-loading-item" ".target" --timeout 10000
255
+
256
+ # Drag without waiting (fail immediately if not found)
257
+ drag "#source" "#target" --no-wait
258
+
259
+ Note:
260
+ - Uses JavaScript DragEvent simulation for drag and drop
261
+ - Dispatches all standard drag events: dragstart, dragenter, dragover, drop, dragend
262
+ - Automatically calculates element center positions for drag coordinates
263
+ - Works with HTML5 drag and drop API event listeners
264
+ `;
265
+ }
266
+ }
267
+ exports.DragHandler = DragHandler;