There's no built-in interaction for that just yet, but Serenity/JS is quite easy to extend, so you could create a custom interaction (and maybe even submit it as a pull request?).
Below are the steps I'd take to create a custom interaction.
1. Research the protractor way
To start with, think about how you'd implement this functionality using the plain-old Protractor?
Protractor's API documentation suggests the following options:
// Dragging one element to another.
browser.actions().
mouseDown(element1).
mouseMove(element2).
mouseUp().
perform();
// You can also use the `dragAndDrop` convenience action.
browser.actions().
dragAndDrop(element1, element2).
perform();
// Instead of specifying an element as the target, you can specify an offset
// in pixels. This example double-clicks slightly to the right of an element.
browser.actions().
mouseMove(element).
mouseMove({x: 50, y: 0}).
doubleClick().
perform();
As you can see, all the above examples rely on the browser.actions()
API, so we'll need to find a way of getting hold of that.
But before diving there, let's try to design our new interaction from the outside-in, and think about the interface we'd like to have.
2. Define a DSL you'd like to use
Let's say I wanted to have a Serenity/JS, Screenplay-style interaction based on the second example from the Protractor docs:
browser.actions().
dragAndDrop(element1, element2).
perform();
providing the following interface:
actor.attemptsTo(
DragAndDrop(element1).onto(element2);
)
This means that I could define my interaction's DSL as follows:
import { Target } from 'serenity-js/lib/screenplay-protractor';
export const DragAndDrop = (draggable: Target) => ({
onto: (dropzone: Target) => ...
})
This will give me the syntax of DragAndDrop(draggable).onto(dropzone)
that I wanted to have.
The next step is for DragAndDrop(draggable).onto(dropzone)
call to return an actual interaction.
3. Define the interaction
You can define an interaction using the following short-hand syntax:
import { Interaction } from 'serenity-js/lib/screenplay-protractor';
Interaction.where(`#actor drags ${draggable} onto ${dropzone}`, actor => {
// interaction body
});
Serenity/JS provides an "ability" to BrowseTheWeb
.
This ability is a Screenplay Pattern-style wrapper around the protractor
object, which means that you can use it to access protractor-specific APIs.
So provided that you've given your actor the ability to BrowseTheWeb
:
import { Actor, BrowseTheWeb } from 'serenity-js/lib/screenplay-protractor';
const Alice = Actor.named('Alice').whoCan(BrowseTheWeb.using(protractor.browser));
you can access it in your interaction body
:
Interaction.where(`#actor drags ${draggable} onto ${dropzone}`, actor => {
return BrowseTheWeb.as(actor).actions().
dragAndDrop(..., ...).
perform();
});
One more missing step is that protractor's browser.actions.dragAndDrop(..., ...)
method expects you to provide an instance of a WebElement
, rather than a Serenity/JS-specific Target
.
This means that we need to resolve the Target
before we pass it on:
Interaction.where(`#actor drags ${draggable} onto ${dropzone}`, actor => {
const browse = BrowseTheWeb.as(actor),
draggableElement = browse.locate(draggable),
dropzoneElement = browse.locate(dropzone);
return browse.actions().
dragAndDrop(draggableElement, dropzoneElement).
perform();
});
4. Putting it all together
Given all the above, the resulting implementation could look as follows:
import { Actor, BrowseTheWeb, Interaction, Target } from 'serenity-js/lib/screenplay-protractor';
export const DragAndDrop = (draggable: Target) => ({
onto: (dropzone: Target) => Interaction.where(
`#actor drags ${draggable} onto ${dropzone}`,
actor => {
const browse = BrowseTheWeb.as(actor),
draggableElement = browse.locate(draggable),
dropzoneElement = browse.locate(dropzone);
return browse.actions().
dragAndDrop(draggableElement, dropzoneElement).
perform();
})
})
HTML5 Drag and Drop and Chrome
Please note that the above implementation might not work in Chromedriver with HTML5 drag and drop unless this defect is fixed.
Alternatively, you can install the html-dnd module and implement a Screenplay-style task as follows (you'll need Serenity/JS 1.9.3 or later):
import { Execute, Target, Task } from 'serenity-js/lib/screenplay-protractor';
const dragAndDropScript = require('html-dnd').code; // tslint:disable-line:no-var-requires
export const DragAndDrop = (draggable: Target) => ({
onto: (dropzone: Target) => Task.where(`#actor drags ${draggable} onto ${dropzone}`,
Execute.script(dragAndDropScript).withArguments(draggable, dropzone),
),
});
Hope this helps and thanks for joining the Serenity/JS community :-)
Jan