@@ -257,6 +257,90 @@ func MouseUpByNodeID(ctx context.Context, nodeID int64) error {
257257 }))
258258}
259259
260+ // HTML5DragByNodeID performs a synthetic HTML5 Drag and Drop between two elements.
261+ // Uses JS to dispatch dragstart, dragover, drop, and dragend events.
262+ func HTML5DragByNodeID (ctx context.Context , sourceNodeID , targetNodeID int64 ) error {
263+ js := `
264+ (function(srcId, tgtId) {
265+ function nodeById(id) {
266+ return document.querySelector('[data-ptab-nid="' + id + '"]') ||
267+ (function() {
268+ const all = document.querySelectorAll('*');
269+ for (const el of all) if (el.__backendNodeId === id) return el;
270+ return null;
271+ })();
272+ }
273+ // Resolve via CDP-injected attribute or fallback
274+ const src = nodeById(srcId);
275+ const tgt = nodeById(tgtId);
276+ if (!src || !tgt) return JSON.stringify({error: 'element not found', src: !!src, tgt: !!tgt});
277+
278+ const dt = new DataTransfer();
279+ const opts = {bubbles: true, cancelable: true, dataTransfer: dt};
280+ src.dispatchEvent(new DragEvent('dragstart', opts));
281+ tgt.dispatchEvent(new DragEvent('dragenter', opts));
282+ tgt.dispatchEvent(new DragEvent('dragover', opts));
283+ tgt.dispatchEvent(new DragEvent('drop', opts));
284+ src.dispatchEvent(new DragEvent('dragend', opts));
285+ return JSON.stringify({ok: true});
286+ })
287+ `
288+ // We need to resolve backendNodeId to DOM elements. Use DOM.resolveNode + evaluate.
289+ return chromedp .Run (ctx ,
290+ // First, tag both elements with a data attribute so JS can find them
291+ chromedp .ActionFunc (func (ctx context.Context ) error {
292+ js := fmt .Sprintf (`document.querySelector('[data-ptab-nid="%d"]')?.removeAttribute('data-ptab-nid'); void 0` , sourceNodeID )
293+ return chromedp .FromContext (ctx ).Target .Execute (ctx , "Runtime.evaluate" , map [string ]any {"expression" : js }, nil )
294+ }),
295+ chromedp .ActionFunc (func (ctx context.Context ) error {
296+ // Resolve source node and tag it
297+ var result map [string ]any
298+ if err := chromedp .FromContext (ctx ).Target .Execute (ctx , "DOM.resolveNode" , map [string ]any {
299+ "backendNodeId" : sourceNodeID ,
300+ }, & result ); err != nil {
301+ return fmt .Errorf ("resolve source: %w" , err )
302+ }
303+ objectID , _ := result ["object" ].(map [string ]any )["objectId" ].(string )
304+ if objectID == "" {
305+ return fmt .Errorf ("could not resolve source node %d" , sourceNodeID )
306+ }
307+ return chromedp .FromContext (ctx ).Target .Execute (ctx , "Runtime.callFunctionOn" , map [string ]any {
308+ "objectId" : objectID ,
309+ "functionDeclaration" : fmt .Sprintf (`function() { this.setAttribute('data-ptab-nid', '%d'); }` , sourceNodeID ),
310+ }, nil )
311+ }),
312+ chromedp .ActionFunc (func (ctx context.Context ) error {
313+ // Resolve target node and tag it
314+ var result map [string ]any
315+ if err := chromedp .FromContext (ctx ).Target .Execute (ctx , "DOM.resolveNode" , map [string ]any {
316+ "backendNodeId" : targetNodeID ,
317+ }, & result ); err != nil {
318+ return fmt .Errorf ("resolve target: %w" , err )
319+ }
320+ objectID , _ := result ["object" ].(map [string ]any )["objectId" ].(string )
321+ if objectID == "" {
322+ return fmt .Errorf ("could not resolve target node %d" , targetNodeID )
323+ }
324+ return chromedp .FromContext (ctx ).Target .Execute (ctx , "Runtime.callFunctionOn" , map [string ]any {
325+ "objectId" : objectID ,
326+ "functionDeclaration" : fmt .Sprintf (`function() { this.setAttribute('data-ptab-nid', '%d'); }` , targetNodeID ),
327+ }, nil )
328+ }),
329+ chromedp .ActionFunc (func (ctx context.Context ) error {
330+ expr := fmt .Sprintf (`%s(%d, %d)` , js , sourceNodeID , targetNodeID )
331+ return chromedp .FromContext (ctx ).Target .Execute (ctx , "Runtime.evaluate" , map [string ]any {"expression" : expr }, nil )
332+ }),
333+ // Clean up tags
334+ chromedp .ActionFunc (func (ctx context.Context ) error {
335+ cleanup := fmt .Sprintf (`document.querySelectorAll('[data-ptab-nid]').forEach(el => el.removeAttribute('data-ptab-nid')); void 0` )
336+ _ = cleanup
337+ return chromedp .FromContext (ctx ).Target .Execute (ctx , "Runtime.evaluate" , map [string ]any {
338+ "expression" : `document.querySelectorAll('[data-ptab-nid]').forEach(el => el.removeAttribute('data-ptab-nid')); void 0` ,
339+ }, nil )
340+ }),
341+ )
342+ }
343+
260344func HoverByCoordinate (ctx context.Context , x , y float64 ) error {
261345 if x < 0 || y < 0 {
262346 return fmt .Errorf ("x/y coordinates must be >= 0" )
0 commit comments