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.39k stars 3.63k forks source link

[BUG] frame element locator.dispatchEvent timeout error on drop #28690

Closed ghost closed 10 months ago

ghost commented 10 months ago

Getting a frame.locator.DispatchEvent timeout error when I believe this should work. The button I am attempting to drop on is inside an iframe. I have tried the exact same code on a button on the page (outside the iframe) and it works just fine. I have attached a zip that can reproduce the error.

Originally raised here but was closed but I have updated the attached zip example which should now work OK to be triaged.

System info

Source code

Config file

import { defineConfig, devices } from "@playwright/test";

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: "./tests",
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  expect: { timeout: 3000 },

  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: [["list"], ["html", { open: "never" }]],
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: "file:///D:/Automation/Playwright/TestingStuffOut/default.html",
    actionTimeout: 3000,
    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: "retain-on-failure",
    screenshot: "only-on-failure",
    headless: false,
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
});

Test file

import { test, expect } from "@playwright/test";
import { Listing } from "../src/listing";

test("Test frame element DispatchEvent", async ({ page }) => {
  const listing = new Listing(page);
  await page.goto("");
  await expect(page.locator(`#test`)).toBeVisible();
  await listing.dragAndDrop("Document1.txt");
});

Page object file

import { Page, expect } from "@playwright/test";
import { readFileSync } from "fs";
import { cwd } from "process";

export class Listing {
  readonly locators = {
    btnDragAndDrop: this.page
      .frameLocator(`iframe[name=mk_main]`)
      .locator(`#dragdropicon`),
  };

  constructor(private readonly page: Page) {}

  async dragAndDrop(fileName: string, fileType = "") {
    const filePath = cwd() + `\\data\\${fileName}`;
    const buffer = readFileSync(filePath).toString("base64");

    const dataTransfer = await this.page.evaluateHandle(
      async ({ bufferData, localFileName, localFileType }) => {
        const dt = new DataTransfer();

        const blobData = await fetch(bufferData).then((res) => res.blob());

        const file = new File([blobData], localFileName, {
          type: localFileType,
        });
        dt.items.add(file);
        return dt;
      },
      {
        bufferData: `data:application/octet-stream;base64,${buffer}`,
        localFileName: fileName,
        localFileType: fileType,
      }
    );

    await expect(this.locators.btnDragAndDrop).toBeVisible();
    await this.locators.btnDragAndDrop.dispatchEvent("drop", { dataTransfer });
  }
}

Steps

Expected Drop event should work/fire and my code should continue to work thereafter.

Actual Get a timeout waiting on the locator.DispatchEvent:

Error: locator.dispatchEvent: Timeout 3000ms exceeded.
    Call log:
      - waiting for frameLocator('iframe[name=mk_main]').locator('#dragdropicon')

Reproducable Example

reproducable-example.zip

Extract the contents: npm install npx playwright test

yury-s commented 10 months ago

This is because you are trying to pass JSHandle to an object from one context into another. This is unsupported and we'll fix the code to throw a proper error message. To overcome that, you need to create DataTransfer object in the same iframe where the event target element is. Something like this should work:

  const dataTransfer = await this.page.frame({name: 'mk_main'}).evaluateHandle(
    // same code as before
  );
ghost commented 10 months ago

@yury-s Thank you. can't believe I didn't spot I wasn't using the frame!

All working now :)