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.
- package/README.md +430 -45
- package/dist/cli/CLIApplication.js +9 -0
- package/dist/cli/CommandRouter.js +12 -1
- package/dist/handlers/ClickHandler.js +241 -0
- package/dist/handlers/DragHandler.js +267 -0
- package/dist/handlers/FillFormHandler.js +245 -0
- package/dist/handlers/FillHandler.js +354 -0
- package/dist/handlers/HandleDialogHandler.js +197 -0
- package/dist/handlers/HoverHandler.js +268 -0
- package/dist/handlers/InstallClaudeSkillHandler.js +706 -58
- package/dist/handlers/InstallCursorCommandHandler.js +208 -74
- package/dist/handlers/PressKeyHandler.js +337 -0
- package/dist/handlers/TakeSnapshotHandler.js +104 -32
- package/dist/handlers/UploadFileHandler.js +325 -0
- package/dist/handlers/WaitForHandler.js +331 -0
- package/dist/handlers/index.js +9 -0
- package/package.json +2 -2
|
@@ -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;
|