Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] How to drag and drop files #10667

Open
A-ZC-Lau opened this issue Dec 2, 2021 · 21 comments
Open

[Question] How to drag and drop files #10667

A-ZC-Lau opened this issue Dec 2, 2021 · 21 comments

Comments

@A-ZC-Lau
Copy link

A-ZC-Lau commented Dec 2, 2021

How do I write a drag and drop action with a file?

There's an example in the docs that I don't understand

// Note you can only create DataTransfer in Chromium and Firefox
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await element.dispatchEvent('dragstart', { dataTransfer });

How do I select a file to drag and drop?

@pavelfeldman
Copy link
Member

You would be using Web APIs to create File objects and adding them into the datatransfer items, inside the first evaluate.

@A-ZC-Lau
Copy link
Author

A-ZC-Lau commented Dec 2, 2021

@pavelfeldman Can you provide an example?

@CharlieDigital
Copy link

CharlieDigital commented Dec 21, 2021

Got it.

// Read your file into a buffer.
const buffer = readFileSync('./runtime_config/common/file.pdf');

// Create the DataTransfer and File
const dataTransfer = await scope.page.evaluateHandle((data) => {
    const dt = new DataTransfer();
    // Convert the buffer to a hex array
    const file = new File([data.toString('hex')], 'file.pdf', { type: 'application/pdf' });
    dt.items.add(file);
    return dt;
}, buffer);

// Now dispatch
await page.dispatchEvent('YOUR_TARGET_SELECTOR', 'drop', { dataTransfer });

If you're in TypeScript, add this at the top of your file:

/// <reference lib="dom"/>

carhartl added a commit to digitalservicebund/mitra-frontend that referenced this issue Jan 10, 2022
Test the real drop interaction! For some reason I couldn't make it work
by loading the contents of the respective fixtures into a buffer to
produce a file for the DataTransfer object as shown:

microsoft/playwright#10667 (comment)

Thus using a minimal hardcoded json string (we want to test the drop
interaction here, and nothing else, thus it should be fine).
@Jeromearsene
Copy link

I purpose this for more generic approach.

@punkpeye
Copy link

I released a utility createDataTransfer that abstracts this.

@kovacsdongo
Copy link

kovacsdongo commented Sep 6, 2022

Hi all
I use this code:
`import { readFileSync } from 'fs';
const buffer = readFileSync('./fixtures/2.pdf');

  // Create the DataTransfer and File
  const dataTransfer = await page.evaluateHandle((data) => {
    const dt = new DataTransfer();
    // Convert the buffer to a hex array
    const file = new File([data.toString('hex')], '2.pdf', { type: 'application/pdf' });
    dt.items.add(file);
    return dt;
  }, buffer);
  
  // Now dispatch
  await page.dispatchEvent("//div[@class='flex ng-star-inserted']", 'drop', { dataTransfer });
  `
  
 the suite is pass, but i can not see on the UI or in the result article
  evaluateHandler and dispatchevent also pass

image

(on the webpage there is full page drag& drop function)

@galvin59
Copy link

Hi,
Has someone already managed to port the code above to c# using the .net library ?
Thanks !

@mraspe
Copy link

mraspe commented Nov 14, 2022

Hi, how to drop images (e.g. PNG) in a certain area? Analogous to the example above?

@higab85
Copy link

higab85 commented Dec 30, 2022

Hello, thanks for the info!
I got this working with the Python API too. I'll post below for anyone else who may be interested:

import base64

def drag_and_drop(page:Page, file_path:str, drop_zone_selector: str) -> None:
    with open(file_path, 'rb') as f:
        buffer = f.read()
    pdf_base64 = base64.b64encode(buffer).decode("utf-8")

    # Create the DataTransfer and File
    data_transfer = page.evaluate_handle(
        """
        (data) => {
            const dt = new DataTransfer();
            // Convert the binary string to a hexadecimal string
            const hexString = Uint8Array.from(atob(data), c => c.charCodeAt(0));
            const file = new File([hexString], 'file.pdf', { type: 'application/pdf' });
            dt.items.add(file);
            return dt;
        }
        """, pdf_base64
    )
    # Dispatch the 'drop' event on the target element
    page.dispatch_event(drop_zone_selector, 'drop', {"dataTransfer": data_transfer})

@gklittlejohn
Copy link

This works for me on chromium and firefox but fails on webkit with an error that dt.items is undefined.

@cmolina
Copy link

cmolina commented Mar 9, 2023

This works for me on chromium and firefox but fails on webkit with an error that dt.items is undefined.

Webkit doesn't support creating DataTransfer objects. You can add this line to your test:

test('my test', async ({ page }) =>
  test.skip(browserName === 'webkit', `Webkit doesn't support creating DataTransfer objects, used for drag and drop`)

  // the rest of your test
})

@heybran
Copy link

heybran commented Mar 15, 2023

Hi all I use this code: `import { readFileSync } from 'fs'; const buffer = readFileSync('./fixtures/2.pdf');

  // Create the DataTransfer and File
  const dataTransfer = await page.evaluateHandle((data) => {
    const dt = new DataTransfer();
    // Convert the buffer to a hex array
    const file = new File([data.toString('hex')], '2.pdf', { type: 'application/pdf' });
    dt.items.add(file);
    return dt;
  }, buffer);
  
  // Now dispatch
  await page.dispatchEvent("//div[@class='flex ng-star-inserted']", 'drop', { dataTransfer });
  `
  
 the suite is pass, but i can not see on the UI or in the result article
  evaluateHandler and dispatchevent also pass

(on the webpage there is full page drag& drop function)

This doesn't seem to work for me, so instead of using page.disptachEvent(), I created a DragEvent inside evaluateHandle to work around that. Adding my solution here in case someone else has similar issue.

  // ...
  await page.evaluateHandle(
    (data) => {
      const drop = new DragEvent("drop", { dataTransfer: data.data_transfer });
      document.querySelector(data.selector).dispatchEvent(drop);
    },
    { data_transfer, selector }
  );

@alexeyKoshelevJava
Copy link

Hello everybody! I need to do the same in Java. Please help me!

@LichieLich
Copy link

Hello everybody! I need to do the same in Java. Please help me!

@alexeyKoshelevJava
It seems like a crutch, but it's works.

  byte [] blob;
  String fileName = "example.png"
  try {
    blob = Files.readAllBytes(Paths.get("path/to/file"));
  } catch (IOException e) {
    throw new RuntimeException(e);
  }

  // Need to convert byte[] to String like "[116, 101, 115, 116, 51, 57, 48, 52, 54]"
  StringJoiner joiner = new StringJoiner(", ", "[", "]");
  for (byte b : blob) {
    joiner.add(String.valueOf(b));
  }

  JSHandle dataTransfer = page.evaluateHandle(
      "dt = new DataTransfer(); "
      + "file = new File([new Blob([new Uint8Array(" + joiner + ")])], '" + fileName + "'); "
      + "dt.items.add(file); "
      + "dt");

  Map<String, Object> arg = new HashMap<>();
  arg.put("dataTransfer", dataTransfer);
  getElementByName(elementName).dispatchEvent("drop", arg);

@blusius
Copy link

blusius commented Aug 25, 2023

Hi all, is anybody have an example for c# (net.) already spent one day trying to port examples above without sucess. My code

var filePath = "C:\\Temp\\file.docx";
byte[] fileBytes = File.ReadAllBytes(filePath);

var dataTransfer = await page.EvaluateHandleAsync(@"(data) => {
    dt = new DataTransfer();
    console.log(data);
    file = new File([data.toString('hex')], 'file.docx', { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    console.log(file);
    dt.items.add(file);
    console.log(dt);
    return dt;
}", fileBytes);

await page.Locator("[id^='fileupload_']").DispatchEventAsync("drop", new { dataTransfer });

@delijah
Copy link

delijah commented Oct 10, 2023

Any progress on this?

@blusius
Copy link

blusius commented Oct 10, 2023

Nope. I left this idea to rest in peace. Maybe some rainy day sun will shine a little bit brighter :)

@Laurens-makel
Copy link

Got it.

// Read your file into a buffer.
const buffer = readFileSync('./runtime_config/common/file.pdf');

// Create the DataTransfer and File
const dataTransfer = await scope.page.evaluateHandle((data) => {
    const dt = new DataTransfer();
    // Convert the buffer to a hex array
    const file = new File([data.toString('hex')], 'file.pdf', { type: 'application/pdf' });
    dt.items.add(file);
    return dt;
}, buffer);

// Now dispatch
await page.dispatchEvent('YOUR_TARGET_SELECTOR', 'drop', { dataTransfer });

If you're in TypeScript, add this at the top of your file:

/// <reference lib="dom"/>

This seems to work for small (< 10MB files), above this threshold the exact same test will timeout on page.evaluateHandle().

Are you aware of this limitation and do you have any suggestions to work around this?

@Loada
Copy link

Loada commented Apr 9, 2024

Hi all, is anybody have an example for c# (net.) already spent one day trying to port examples above without sucess. My code

var filePath = "C:\\Temp\\file.docx";
byte[] fileBytes = File.ReadAllBytes(filePath);

var dataTransfer = await page.EvaluateHandleAsync(@"(data) => {
    dt = new DataTransfer();
    console.log(data);
    file = new File([data.toString('hex')], 'file.docx', { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    console.log(file);
    dt.items.add(file);
    console.log(dt);
    return dt;
}", fileBytes);

await page.Locator("[id^='fileupload_']").DispatchEventAsync("drop", new { dataTransfer });
var sourceFilePath = "someDirectory/someFile.doc";

var fileData = await File.ReadAllBytesAsync(sourceFilePath);

var dataTransfer = await Page.EvaluateHandleAsync(
    $$"""
      (data) => {
          const dt = new DataTransfer();
          let decodedData = atob(data);
          let bytes = new Uint8Array(Array.from(decodedData, char => char.charCodeAt(0)));
          let blob = new Blob([bytes], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
          file = new File([blob], '{{Path.GetFileName(sourceFilePath)}}', {
            type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
          });
          dt.items.add(file);
          return dt;
      }
    """,
    Convert.ToBase64String(fileData));

@olgavotrina
Copy link

olgavotrina commented Apr 12, 2024

Hello everybody! I need to do the same in Java. Please help me!

@alexeyKoshelevJava It seems like a crutch, but it's works.

  byte [] blob;
  String fileName = "example.png"
  try {
    blob = Files.readAllBytes(Paths.get("path/to/file"));
  } catch (IOException e) {
    throw new RuntimeException(e);
  }

  // Need to convert byte[] to String like "[116, 101, 115, 116, 51, 57, 48, 52, 54]"
  StringJoiner joiner = new StringJoiner(", ", "[", "]");
  for (byte b : blob) {
    joiner.add(String.valueOf(b));
  }

  JSHandle dataTransfer = page.evaluateHandle(
      "dt = new DataTransfer(); "
      + "file = new File([new Blob([new Uint8Array(" + joiner + ")])], '" + fileName + "'); "
      + "dt.items.add(file); "
      + "dt");

  Map<String, Object> arg = new HashMap<>();
  arg.put("dataTransfer", dataTransfer);
  getElementByName(elementName).dispatchEvent("drop", arg);

This code works but on UI I've caught an error that "file type is invalid".
So I've changed a lil bit implementation to this one and it works:

public class TestBasePage {
private byte [] fileBytes;
private final Page page;
private final Locator dropZoneSelector;

public TestBasePage(Page page) {
    this.page = page;
    //any drop zone selector
    this.dropZoneSelector = page.getByTestId("BackupOutlinedIcon");
}
public void dragAndDropFile(String fileName, String filePath){
    //get bytes
    try {
        fileBytes = Files.readAllBytes(Paths.get(filePath));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    //encode bytes to base64String
    String encodedString = Base64.encodeBase64String(fileBytes);
    //create dataTransfer
    JSHandle dataTransfer = page.evaluateHandle(
            "(encodedString) => {"
            + "const dt = new DataTransfer();"
            + "const hexString = Uint8Array.from(atob(encodedString), c => c.charCodeAt(0));"
            + "const file = new File([hexString], '" + fileName + "', { type: 'image/jpeg' });"
            + "dt.items.add(file);"
            + "return dt;}", encodedString);
    //dispatch event of "drop" type
    Map<String, Object> arg = new HashMap<>();
    arg.put("dataTransfer", dataTransfer);
    dropZoneSelector.dispatchEvent("drop", arg);
}

}

@aditya-beniwal
Copy link

aditya-beniwal commented Jul 9, 2024

After a lot of issues, this one work best for me

async uploadDocument(fileName : string)
  {

    // Read your file into a buffer.
    const buffer = readFileSync(`./TestFiles/${fileName}`).toString('base64');
    
    // Create the DataTransfer and File
    const dataTransfer = await this.dropDiv.evaluateHandle(( data , { buffer , fileName} ) => {
      const dt = new DataTransfer();
      //convert buffer into hexString
      const hexString = Uint8Array.from(atob(buffer), c => c.charCodeAt(0));
      //create file
      const file = new File([hexString], fileName);
      dt.items.add(file);
      return dt;
    } , {buffer , fileName});

    // Now dispatch
    await this.dropDiv.dispatchEvent('drop', { dataTransfer });

  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests