From e26e18062f6a20be5565c11706e55a7b630f9190 Mon Sep 17 00:00:00 2001 From: Johan Suleiko Allansson Date: Mon, 29 Apr 2024 15:58:52 +0200 Subject: [PATCH 1/4] wip dragAndDrop function --- browser/mapping.go | 3 + common/frame.go | 65 ++++++++++++++++++++++ common/frame_options.go | 8 +++ common/locator.go | 14 +++++ common/page.go | 11 ++-- examples/dragAndDrop.js | 64 ++++++++++++++++++++++ tests/locator_test.go | 22 ++++++++ tests/static/locators.html | 109 ++++++++++++++++++++++++++++--------- 8 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 examples/dragAndDrop.js diff --git a/browser/mapping.go b/browser/mapping.go index b8b8b1944..bab88e18c 100644 --- a/browser/mapping.go +++ b/browser/mapping.go @@ -42,6 +42,7 @@ func mapBrowserToGoja(vu moduleVU) *goja.Object { // mapLocator API to the JS module. func mapLocator(vu moduleVU, lo *common.Locator) mapping { return mapping{ + "@@locator": lo, "clear": func(opts goja.Value) error { ctx := vu.Context() @@ -63,6 +64,7 @@ func mapLocator(vu moduleVU, lo *common.Locator) mapping { }), nil }, "dblclick": lo.Dblclick, + "dragTo": lo.DragTo, "check": lo.Check, "uncheck": lo.Uncheck, "isChecked": lo.IsChecked, @@ -385,6 +387,7 @@ func mapFrame(vu moduleVU, f *common.Frame) mapping { } return f.DispatchEvent(selector, typ, exportArg(eventInit), popts) //nolint:wrapcheck }, + "dragAndDrop": f.DragAndDrop, "evaluate": func(pageFunction goja.Value, gargs ...goja.Value) any { return f.Evaluate(pageFunction.String(), exportArgs(gargs)...) }, diff --git a/common/frame.go b/common/frame.go index e0d07a121..13951a721 100644 --- a/common/frame.go +++ b/common/frame.go @@ -580,10 +580,75 @@ func (f *Frame) Click(selector string, opts *FrameClickOptions) error { return nil } +func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts goja.Value) error { + popts := FrameDragAndDropOptions{} + + getPosition := func(apiCtx context.Context, handle *ElementHandle, p *Position) (any, error) { + return p, nil + } + + sourceOpts := &ElementHandleBasePointerOptions{ + ElementHandleBaseOptions: popts.ElementHandleBaseOptions, + Position: popts.SourcePosition, + Trial: popts.Trial, + } + + act := f.newPointerAction( + sourceSelector, DOMElementStateAttached, popts.Strict, getPosition, sourceOpts, + ) + + sourcePosAny, err := call(f.ctx, act, popts.Timeout) + + if err != nil { + return errorFromDOMError(err) + } + + targetOps := &ElementHandleBasePointerOptions{ + ElementHandleBaseOptions: popts.ElementHandleBaseOptions, + Position: popts.SourcePosition, + Trial: popts.Trial, + } + + act = f.newPointerAction( + targetSelector, DOMElementStateAttached, popts.Strict, getPosition, targetOps, + ) + + targetPosAny, err := call(f.ctx, act, popts.Timeout) + + if err != nil { + return errorFromDOMError(err) + } + + sourcePos := &Position{} + convert(sourcePosAny, sourcePos) + + targetPos := &Position{} + convert(targetPosAny, targetPos) + + if err := f.page.Mouse.move(sourcePos.X, sourcePos.Y, &MouseMoveOptions{}); err != nil { + return errorFromDOMError(err) + } + + if err := f.page.Mouse.down(sourcePos.X, sourcePos.Y, &MouseDownUpOptions{}); err != nil { + return errorFromDOMError(err) + } + + if err := f.page.Mouse.move(targetPos.X, targetPos.Y, &MouseMoveOptions{}); err != nil { + return errorFromDOMError(err) + } + + if err := f.page.Mouse.up(targetPos.X, targetPos.Y, &MouseDownUpOptions{}); err != nil { + return errorFromDOMError(err) + } + + return nil +} + func (f *Frame) click(selector string, opts *FrameClickOptions) error { click := func(apiCtx context.Context, handle *ElementHandle, p *Position) (any, error) { return nil, handle.click(p, opts.ToMouseClickOptions()) } + act := f.newPointerAction( selector, DOMElementStateAttached, opts.Strict, click, &opts.ElementHandleBasePointerOptions, ) diff --git a/common/frame_options.go b/common/frame_options.go index b8dba383c..76c09d15e 100644 --- a/common/frame_options.go +++ b/common/frame_options.go @@ -33,6 +33,14 @@ type FrameDblclickOptions struct { Strict bool `json:"strict"` } +type FrameDragAndDropOptions struct { + ElementHandleBaseOptions + SourcePosition *Position `json:"sourcePosition"` + TargetPosition *Position `json:"targetPosition"` + Trial bool `json:"trial"` + Strict bool `json:"strict"` +} + type FrameFillOptions struct { ElementHandleBaseOptions Strict bool `json:"strict"` diff --git a/common/locator.go b/common/locator.go index 20255c8d7..978c57c37 100644 --- a/common/locator.go +++ b/common/locator.go @@ -104,6 +104,20 @@ func (l *Locator) dblclick(opts *FrameDblclickOptions) error { return l.frame.dblclick(l.selector, opts) } +func (l *Locator) DragTo(target *Locator) error { + l.log.Debugf("Locator:DragTo", "fid:%s furl:%q sel:%q target:%q", l.frame.ID(), l.frame.URL(), l.selector, target.selector) + + if err := l.dragTo(target); err != nil { + return fmt.Errorf("dragging %q to %q: %w", l.selector, target.selector, err) + } + + return nil +} + +func (l *Locator) dragTo(target *Locator) error { + panic("not implemented") +} + // Check on an element using locator's selector with strict mode on. func (l *Locator) Check(opts goja.Value) { l.log.Debugf("Locator:Check", "fid:%s furl:%q sel:%q opts:%+v", l.frame.ID(), l.frame.URL(), l.selector, opts) diff --git a/common/page.go b/common/page.go index aa9a0405e..14691b0a2 100644 --- a/common/page.go +++ b/common/page.go @@ -723,11 +723,6 @@ func (p *Page) DispatchEvent(selector string, typ string, eventInit any, opts *F return p.MainFrame().DispatchEvent(selector, typ, eventInit, opts) } -// DragAndDrop is not implemented. -func (p *Page) DragAndDrop(source string, target string, opts goja.Value) { - k6ext.Panic(p.ctx, "Page.DragAndDrop(source, target, opts) has not been implemented yet") -} - func (p *Page) EmulateMedia(opts goja.Value) { p.logger.Debugf("Page:EmulateMedia", "sid:%v", p.sessionID()) @@ -1257,6 +1252,12 @@ func (p *Page) Type(selector string, text string, opts goja.Value) { p.MainFrame().Type(selector, text, opts) } +func (p *Page) DragAndDrop(sourceSelector string, targetSelector string, opts goja.Value) error { + p.logger.Debugf("Page:DragAndDrop", "sid:%v source selector:%s, target selector: %s", p.sessionID(), sourceSelector, targetSelector) + + return p.MainFrame().DragAndDrop(sourceSelector, targetSelector, opts) +} + // Unroute is not implemented. func (p *Page) Unroute(url goja.Value, handler goja.Callable) { k6ext.Panic(p.ctx, "Page.unroute(url, handler) has not been implemented yet") diff --git a/examples/dragAndDrop.js b/examples/dragAndDrop.js new file mode 100644 index 000000000..695a7aed6 --- /dev/null +++ b/examples/dragAndDrop.js @@ -0,0 +1,64 @@ +import { check } from "k6"; +import { browser } from "k6/x/browser"; + +export const options = { + scenarios: { + ui: { + executor: "shared-iterations", + options: { + browser: { + type: "chromium", + }, + }, + }, + }, + thresholds: { + checks: ["rate==1.0"], + }, +}; + +export default async function () { + const page = browser.newPage(); + + page.setContent(` + + + + + +
Drag me!
+
Drop here!
+ + + + + `); + + await page.dragAndDrop("#drag-source", "#drop-target"); + + const dropEl = page.waitForSelector("#drop-target"); + + check(dropEl, { + "source was dropped on target": (e) => + e.innerText() === "Something dropped!", + }); + + page.close(); +} diff --git a/tests/locator_test.go b/tests/locator_test.go index d41ca44a7..9b7ca0a97 100644 --- a/tests/locator_test.go +++ b/tests/locator_test.go @@ -83,6 +83,28 @@ func TestLocator(t *testing.T) { require.True(t, asBool(t, v), "cannot not double click the link") }, }, + { + "DragTo", func(tb *testBrowser, p *common.Page) { + source := p.Locator("#dragSource", nil) + target := p.Locator("#dragTarget", nil) + + err := source.DragTo(target) + require.NoError(t, err) + drag := p.Evaluate(`() => window.drag`) + dragend := p.Evaluate(`() => window.dragend`) + dragover := p.Evaluate(`() => window.dragover`) + dragenter := p.Evaluate(`() => window.dragenter`) + dragleave := p.Evaluate(`() => window.dragleave`) + drop := p.Evaluate(`() => window.drop`) + + require.True(t, asBool(t, drag), "cannot not drag the source") + require.True(t, asBool(t, dragend), "cannot not dragend the source") + require.True(t, asBool(t, dragover), "cannot not dragover the target") + require.True(t, asBool(t, dragenter), "cannot not dragenter the target") + require.True(t, asBool(t, dragleave), "cannot not dragleave the target") + require.True(t, asBool(t, drop), "cannot not drop the target") + }, + }, { "DispatchEvent", func(tb *testBrowser, p *common.Page) { result := func() bool { diff --git a/tests/static/locators.html b/tests/static/locators.html index 394035ea2..7a7d5839b 100644 --- a/tests/static/locators.html +++ b/tests/static/locators.html @@ -1,46 +1,101 @@ - - + Clickable link test - + - + Click Dblclick Click - +
hello
bye

original text

original text

- + +
Drag me
+
Drop here
- + window.result = false; + window.dblclick = false; + window.check = false; + + window.drag = false; + window.dragend = false; + window.dragenter = false; + window.dragleave = false; + window.dragover = false; + window.drop = false; + + document.querySelector("#link").addEventListener("click", (e) => { + window.result = true; + }); + + document.querySelector("#linkdbl").addEventListener("dblclick", (e) => { + window.dblclick = true; + }); + + document + .querySelector("#inputCheckbox") + .addEventListener("change", (e) => { + window.check = e.currentTarget.checked; + }); + + document + .querySelector("#inputText") + .addEventListener("mousemove", (e) => { + window.result = true; + }); + + document + .querySelector("#inputText") + .addEventListener("touchstart", (e) => { + window.result = true; + }); + document.getElementById("dragSource").addEventListener("drag", (e) => { + window.drag = true; + + e.dataTransfer.setData("text", "some data"); + }); + + document.getElementById("dragSource").addEventListener("dragend", (e) => { + window.dragend = true; + }); + + document + .getElementById("dragTarget") + .addEventListener("dragenter", (e) => { + window.dragenter = true; + }); + + document + .getElementById("dragTarget") + .addEventListener("dragleave", (e) => { + window.dragleave = true; + }); + + document + .getElementById("dragTarget") + .addEventListener("dragover", (e) => { + // We need to prevent default for drop event to fire. + e.preventDefault(); + + window.dragover = true; + }); + + document.getElementById("dragTarget").addEventListener("drop", (e) => { + window.drop = true; + }); + + From 8d44157ac23aa2d9638ec2b71eebefe0ada62988 Mon Sep 17 00:00:00 2001 From: Johan Suleiko Allansson Date: Mon, 29 Apr 2024 16:31:14 +0200 Subject: [PATCH 2/4] init timeout when waiting for state --- common/frame.go | 8 +++++--- examples/dragAndDrop.js | 2 +- tests/frame_test.go | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/common/frame.go b/common/frame.go index 13951a721..846c0508d 100644 --- a/common/frame.go +++ b/common/frame.go @@ -581,7 +581,9 @@ func (f *Frame) Click(selector string, opts *FrameClickOptions) error { } func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts goja.Value) error { - popts := FrameDragAndDropOptions{} + popts := FrameDragAndDropOptions{ + ElementHandleBaseOptions: *NewElementHandleBaseOptions(f.defaultTimeout()), + } getPosition := func(apiCtx context.Context, handle *ElementHandle, p *Position) (any, error) { return p, nil @@ -594,7 +596,7 @@ func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts g } act := f.newPointerAction( - sourceSelector, DOMElementStateAttached, popts.Strict, getPosition, sourceOpts, + sourceSelector, DOMElementStateVisible, popts.Strict, getPosition, sourceOpts, ) sourcePosAny, err := call(f.ctx, act, popts.Timeout) @@ -610,7 +612,7 @@ func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts g } act = f.newPointerAction( - targetSelector, DOMElementStateAttached, popts.Strict, getPosition, targetOps, + targetSelector, DOMElementStateVisible, popts.Strict, getPosition, targetOps, ) targetPosAny, err := call(f.ctx, act, popts.Timeout) diff --git a/examples/dragAndDrop.js b/examples/dragAndDrop.js index 695a7aed6..044bed77a 100644 --- a/examples/dragAndDrop.js +++ b/examples/dragAndDrop.js @@ -53,7 +53,7 @@ export default async function () { await page.dragAndDrop("#drag-source", "#drop-target"); - const dropEl = page.waitForSelector("#drop-target"); + const dropEl = await page.waitForSelector("#drop-target"); check(dropEl, { "source was dropped on target": (e) => diff --git a/tests/frame_test.go b/tests/frame_test.go index d2a8e606d..eead9f94a 100644 --- a/tests/frame_test.go +++ b/tests/frame_test.go @@ -157,3 +157,48 @@ func TestFrameTitle(t *testing.T) { p.SetContent(`Some title`, nil) assert.Equal(t, "Some title", p.MainFrame().Title()) } + +func TestFrameDragAndDrop(t *testing.T) { + t.Parallel() + + p := newTestBrowser(t).NewPage(nil) + + p.SetContent(` + + + + + +
Drag me!
+
Drop here!
+ + + + + `, nil) + + p.MainFrame().DragAndDrop("#drag-source", "#drop-target", nil) + + h, err := p.WaitForSelector("#drop-target", nil) + + require.NoError(t, err) + + assert.Equal(t, "Something dropped!", h.InnerText()) +} From c287e0cf62fa7d81e9e37451f84e5d28a4550605 Mon Sep 17 00:00:00 2001 From: Johan Suleiko Allansson Date: Mon, 29 Apr 2024 16:49:03 +0200 Subject: [PATCH 3/4] fix events not firing correctly --- common/frame.go | 8 ++++---- examples/dragAndDrop.js | 6 ++++++ tests/frame_test.go | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/common/frame.go b/common/frame.go index 846c0508d..c6ed80c37 100644 --- a/common/frame.go +++ b/common/frame.go @@ -627,19 +627,19 @@ func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts g targetPos := &Position{} convert(targetPosAny, targetPos) - if err := f.page.Mouse.move(sourcePos.X, sourcePos.Y, &MouseMoveOptions{}); err != nil { + if err := f.page.Mouse.move(sourcePos.X, sourcePos.Y, NewMouseMoveOptions()); err != nil { return errorFromDOMError(err) } - if err := f.page.Mouse.down(sourcePos.X, sourcePos.Y, &MouseDownUpOptions{}); err != nil { + if err := f.page.Mouse.down(sourcePos.X, sourcePos.Y, NewMouseDownUpOptions()); err != nil { return errorFromDOMError(err) } - if err := f.page.Mouse.move(targetPos.X, targetPos.Y, &MouseMoveOptions{}); err != nil { + if err := f.page.Mouse.move(targetPos.X, targetPos.Y, NewMouseMoveOptions()); err != nil { return errorFromDOMError(err) } - if err := f.page.Mouse.up(targetPos.X, targetPos.Y, &MouseDownUpOptions{}); err != nil { + if err := f.page.Mouse.up(targetPos.X, targetPos.Y, NewMouseDownUpOptions()); err != nil { return errorFromDOMError(err) } diff --git a/examples/dragAndDrop.js b/examples/dragAndDrop.js index 044bed77a..b7b46a1b9 100644 --- a/examples/dragAndDrop.js +++ b/examples/dragAndDrop.js @@ -34,14 +34,20 @@ export default async function () { const dropTarget = document.getElementById('drop-target'); dragSource.addEventListener('dragstart', (event) => { + console.log('dragstart'); + event.dataTransfer.setData('text/plain', 'Something dropped!'); }); dropTarget.addEventListener('dragover', (event) => { + console.log("dragover"); + event.preventDefault(); }); dropTarget.addEventListener('drop', (event) => { + console.log("drop"); + event.preventDefault(); const data = event.dataTransfer.getData('text/plain'); event.target.innerText = data; diff --git a/tests/frame_test.go b/tests/frame_test.go index eead9f94a..1ae9d4853 100644 --- a/tests/frame_test.go +++ b/tests/frame_test.go @@ -196,6 +196,7 @@ func TestFrameDragAndDrop(t *testing.T) { p.MainFrame().DragAndDrop("#drag-source", "#drop-target", nil) + p.MainFrame().WaitForTimeout(2000) h, err := p.WaitForSelector("#drop-target", nil) require.NoError(t, err) From 3443ad712cadeef85d75e5af4b13b5b90ece4770 Mon Sep 17 00:00:00 2001 From: Johan Suleiko Allansson Date: Mon, 29 Apr 2024 17:03:45 +0200 Subject: [PATCH 4/4] parse basic opts + promisify --- browser/mapping.go | 31 +++++++++++++++++++++++++++++-- common/frame.go | 26 +++++++++++--------------- common/frame_options.go | 21 +++++++++++++++++++++ common/page.go | 2 +- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/browser/mapping.go b/browser/mapping.go index bab88e18c..664295e80 100644 --- a/browser/mapping.go +++ b/browser/mapping.go @@ -96,6 +96,14 @@ func mapLocator(vu moduleVU, lo *common.Locator) mapping { } } +func parseDragAndDropOptions(ctx context.Context, opts goja.Value, defaultTimeout time.Duration) (*common.FrameDragAndDropOptions, error) { + copts := common.NewFrameDragAndDropOptions(defaultTimeout) + if err := copts.Parse(ctx, opts); err != nil { + return nil, fmt.Errorf("parsing drag and drop options: %w", err) + } + return copts, nil +} + func parseFrameClickOptions( ctx context.Context, opts goja.Value, defaultTimeout time.Duration, ) (*common.FrameClickOptions, error) { @@ -387,7 +395,17 @@ func mapFrame(vu moduleVU, f *common.Frame) mapping { } return f.DispatchEvent(selector, typ, exportArg(eventInit), popts) //nolint:wrapcheck }, - "dragAndDrop": f.DragAndDrop, + "dragAndDrop": func(sourceSelector string, targetSelector string, opts goja.Value) (*goja.Promise, error) { + popts, err := parseDragAndDropOptions(vu.Context(), opts, f.Timeout()) + if err != nil { + return nil, err + } + + return k6ext.Promise(vu.Context(), func() (any, error) { + err := f.DragAndDrop(sourceSelector, targetSelector, popts) + return nil, err //nolint:wrapcheck + }), nil + }, "evaluate": func(pageFunction goja.Value, gargs ...goja.Value) any { return f.Evaluate(pageFunction.String(), exportArgs(gargs)...) }, @@ -580,7 +598,16 @@ func mapPage(vu moduleVU, p *common.Page) mapping { } return p.DispatchEvent(selector, typ, exportArg(eventInit), popts) //nolint:wrapcheck }, - "dragAndDrop": p.DragAndDrop, + "dragAndDrop": func(sourceSelector, targetSelector string, opts goja.Value) (*goja.Promise, error) { + popts, err := parseDragAndDropOptions(vu.Context(), opts, p.Timeout()) + if err != nil { + return nil, err + } + return k6ext.Promise(vu.Context(), func() (any, error) { + err := p.DragAndDrop(sourceSelector, targetSelector, popts) + return nil, err //nolint:wrapcheck + }), nil + }, "emulateMedia": p.EmulateMedia, "emulateVisionDeficiency": p.EmulateVisionDeficiency, "evaluate": func(pageFunction goja.Value, gargs ...goja.Value) any { diff --git a/common/frame.go b/common/frame.go index c6ed80c37..b34bb4077 100644 --- a/common/frame.go +++ b/common/frame.go @@ -580,42 +580,38 @@ func (f *Frame) Click(selector string, opts *FrameClickOptions) error { return nil } -func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts goja.Value) error { - popts := FrameDragAndDropOptions{ - ElementHandleBaseOptions: *NewElementHandleBaseOptions(f.defaultTimeout()), - } - +func (f *Frame) DragAndDrop(sourceSelector string, targetSelector string, opts *FrameDragAndDropOptions) error { getPosition := func(apiCtx context.Context, handle *ElementHandle, p *Position) (any, error) { return p, nil } sourceOpts := &ElementHandleBasePointerOptions{ - ElementHandleBaseOptions: popts.ElementHandleBaseOptions, - Position: popts.SourcePosition, - Trial: popts.Trial, + ElementHandleBaseOptions: opts.ElementHandleBaseOptions, + Position: opts.SourcePosition, + Trial: opts.Trial, } act := f.newPointerAction( - sourceSelector, DOMElementStateVisible, popts.Strict, getPosition, sourceOpts, + sourceSelector, DOMElementStateVisible, opts.Strict, getPosition, sourceOpts, ) - sourcePosAny, err := call(f.ctx, act, popts.Timeout) + sourcePosAny, err := call(f.ctx, act, opts.Timeout) if err != nil { return errorFromDOMError(err) } targetOps := &ElementHandleBasePointerOptions{ - ElementHandleBaseOptions: popts.ElementHandleBaseOptions, - Position: popts.SourcePosition, - Trial: popts.Trial, + ElementHandleBaseOptions: opts.ElementHandleBaseOptions, + Position: opts.SourcePosition, + Trial: opts.Trial, } act = f.newPointerAction( - targetSelector, DOMElementStateVisible, popts.Strict, getPosition, targetOps, + targetSelector, DOMElementStateVisible, opts.Strict, getPosition, targetOps, ) - targetPosAny, err := call(f.ctx, act, popts.Timeout) + targetPosAny, err := call(f.ctx, act, opts.Timeout) if err != nil { return errorFromDOMError(err) diff --git a/common/frame_options.go b/common/frame_options.go index 76c09d15e..28b96fc9d 100644 --- a/common/frame_options.go +++ b/common/frame_options.go @@ -274,6 +274,27 @@ func (o *FrameDblclickOptions) Parse(ctx context.Context, opts goja.Value) error return nil } +func NewFrameDragAndDropOptions(defaultTimeout time.Duration) *FrameDragAndDropOptions { + return &FrameDragAndDropOptions{ + ElementHandleBaseOptions: *NewElementHandleBaseOptions(defaultTimeout), + SourcePosition: nil, + TargetPosition: nil, + Trial: false, + Strict: false, + } +} + +func (o *FrameDragAndDropOptions) Parse(ctx context.Context, opts goja.Value) error { + if err := o.ElementHandleBaseOptions.Parse(ctx, opts); err != nil { + return err + } + + // TODO: parse additional options + + o.Strict = parseStrict(ctx, opts) + return nil +} + func NewFrameFillOptions(defaultTimeout time.Duration) *FrameFillOptions { return &FrameFillOptions{ ElementHandleBaseOptions: *NewElementHandleBaseOptions(defaultTimeout), diff --git a/common/page.go b/common/page.go index 14691b0a2..da908c06d 100644 --- a/common/page.go +++ b/common/page.go @@ -1252,7 +1252,7 @@ func (p *Page) Type(selector string, text string, opts goja.Value) { p.MainFrame().Type(selector, text, opts) } -func (p *Page) DragAndDrop(sourceSelector string, targetSelector string, opts goja.Value) error { +func (p *Page) DragAndDrop(sourceSelector string, targetSelector string, opts *FrameDragAndDropOptions) error { p.logger.Debugf("Page:DragAndDrop", "sid:%v source selector:%s, target selector: %s", p.sessionID(), sourceSelector, targetSelector) return p.MainFrame().DragAndDrop(sourceSelector, targetSelector, opts)