microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.95k stars 3.67k forks source link

[Feature]: Precise grab & drop point for page.dragAndDrop #7789

Closed Voodu closed 3 years ago

Voodu commented 3 years ago

Feature request

New dragAndDrop API is nice addition, but it's a bit limited. Precisely, it doesn't allow to specify, at which point should the draggable be grabbed and to which exact location in the container it should be dropped. It's necessary when drag and drop functionality is used for ex. sorting some items.

Up to now, in my tests I was using custom implementation for drag and drop functionality which, apart from having source and target arguments, provided also the ability for setting grabPoint and dropPoint relative to the respective selectors bounding boxes.

To give an idea, it works like that:

dragAndDrop itself

/**
 * Drag and drop one element into another
 *
 * @param {PlaywrightHandle} draggable Handle of the dragged element.
 * @param {PlaywrightHandle} container Handle of the container to which drop the element.
 * @param {Point} draggableGrabPoint Point at which the draggable should be grabbed. Relative to its bounds.
 * @param {Point} containerDropPoint Point in the container to which the draggable should be dropped. Relative to its bounds.
 */
public async dragAndDrop(
    draggable: PlaywrightHandle,
    container: PlaywrightHandle,
    draggableGrabPoint = new Point(),
    containerDropPoint = new Point()
): Promise<void> {
    if (draggable && container) {
        const sourceBox = await draggable.boundingBox();
        const destBox = await container.boundingBox();
        if (sourceBox && destBox) {
            await this.grabAtCoords(sourceBox, draggableGrabPoint);
            await this.dropAtCoords(destBox, containerDropPoint);
        } else {
            throw new Error('No Element');
        }
    }
}

Point class

// PointLocation could be either precise number or relation to the container - its lowest/middle/biggest X/Y value
type PointLocation = number | 'min' | 'mid' | 'max';
class Point {
    public constructor(public x: PointLocation = 'mid', public y: PointLocation = 'mid') {}

    /**
     * Returns X and Y coordinates relative to the given bounding box.
     *
     * @param {BoundingBox} box Reference bounding box
     * @return X and Y coordinates of the point
     */
    public relativeTo(box: BoundingBox): { x: number; y: number } {
        return { x: this.xRelative(box), y: this.yRelative(box) };
    }
   // ...

}

and can be used like that:

const draggedItem = await this.page.waitForSelector('#draggable');
const container = await this.page.waitForSelector('#container');
await this.pageX.dragAndDrop(draggedItem, container, new Point(), new Point('mid', 'bottom'));

I think at least adding the ability to specify some sort of offset from the selector location would be very helpful for more precise drag and drop manipulation

JoelEinbinder commented 3 years ago

How about targetPosition and sourcePosition in the options?

Voodu commented 3 years ago

That sounds good. They definitely shouldn't be mandatory. Center of the selector will be good enough in most cases, so putting them in the options is reasonable