playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[BUG][Electron] filechooser event not emitted when dialog.show(Open|Save)Dialog is called

Open OiNutter opened this issue 3 years ago • 14 comments

Duplicate of #5013. Full details and repro steps available there.

Opening new issue as requested by maintainers on original issue.

This is definitely a blocker for our integration tests as we need to test open and save functionality in our app and we're not able to select the files in the dialogs.

OiNutter avatar Aug 18 '21 09:08 OiNutter

I'm also having trouble with showOpenDialogSync and showSaveDialogSync because the test stopped.
Is there any workaround?

tgdcat avatar Nov 19 '21 05:11 tgdcat

I have create a test repo to trying to mock electron dialog like spectron-fake-dialog does, but failed.

Does not work, but returned mocked is true. https://github.com/wsw0108/electron-playwright-test/blob/master/test/test.js#L74-L81

This console.log does not write to file. https://github.com/wsw0108/electron-playwright-test/blob/master/test/preload.js#L37

Does anyone may have time to help to find out why?

wsw0108 avatar Jan 10 '22 13:01 wsw0108

I am also having this issue and have been looking for a solution.

@wsw0108 I am quite new to JS testing, but I will have a look into your code if I get some time. Please report back!

Daveiano avatar Jan 10 '22 18:01 Daveiano

@Daveiano I have found the fail cause, it's the async thing of electron ipc. The mock works actually.

wsw0108 avatar Jan 11 '22 04:01 wsw0108

Based on spectron-fake-dialog, I wrote a little library to fake electron dialog for playwright/electron, https://github.com/wsw0108/playwright-fake-dialog

wsw0108 avatar Jan 11 '22 05:01 wsw0108

Based on spectron-fake-dialog, I wrote a little library to fake electron dialog for playwright/electron, https://github.com/wsw0108/playwright-fake-dialog

Awesome! Can actually simplify this and just do:

const electronApp = await playwright._electron.launch({ args: ['./main.js']});

// ...

// in test:
electronApp.evaluate(
  async({ dialog }, filePaths) => {
    dialog.showOpenDialog = () => Promise.resolve({ canceled: false, filePaths });
  },
  ['file.txt']
);

MikeJerred avatar Jan 11 '22 13:01 MikeJerred

@MikeJerred Elegant solution.

Can I use your idea to update my repo?

wsw0108 avatar Jan 11 '22 23:01 wsw0108

@MikeJerred Elegant solution.

Can I use your idea to update my repo?

Yep of course

MikeJerred avatar Jan 11 '22 23:01 MikeJerred

@wsw0108 I am getting this error when using playwright-fake-dialog

page.evaluate: ReferenceError: require is not defined
    at eval (eval at evaluate (:3:2389), <anonymous>:2:31)
    at t.default.evaluate (<anonymous>:3:2412)
    at t.default.<anonymous> (<anonymous>:1:44)

    at ipcRendererSendSyncAsync (/var/www/weather-data-center-electron-forge/node_modules/playwright-fake-dialog/index.js:12:15)
    at mock (/var/www/weather-data-center-electron-forge/node_modules/playwright-fake-dialog/index.js:22:10)
    at /var/www/weather-data-center-electron-forge/src/main/renderer-empty.test.ts:93:13
    at fulfilled (/var/www/weather-data-center-electron-forge/src/main/renderer-empty.test.ts:5:58)

Setup is the following:

import { _electron as electron } from "playwright-core";
const { launch, mock } = require('playwright-fake-dialog')

...

beforeAll(async () => {
  // Launch Electron app.
  electronApp = await launch(electron, {
    args: [
      '.'
    ],
    env: {
      ...process.env
    },
  });

  // Get the first window that the app opens, wait if necessary.
  page = await electronApp.firstWindow();

  // Wait for frame actually loaded.
  await page.waitForSelector('main');

  // Direct Electron console to Node terminal.
  page.on('console', console.log);
});

...

it('should import new data', async () => {
  await mock(page, [
    {
      method: 'showOpenDialog',
      value: {
        filePaths: [`${__dirname.replace('src/main', '')}tests/data/upload-data-start-16-08-21-30-09-21-1196-records.csv`]
      }
    }
  ]);

  await page.click('button#import');
});

Any help and suggestions would be very appreciated!

Daveiano avatar Jan 12 '22 00:01 Daveiano

https://github.com/wsw0108/electron-playwright-test/blob/e3c6ff53d9d71360270ddd37305b188b8273d2c5/app/main/index.js#L43

using 0.1.0 needs above lines in your electron main.js.

If you use html input, just use filechooser from playwright.

wsw0108 avatar Jan 12 '22 01:01 wsw0108

Ah, got you, thank you!

I got it working now with the snippet from @MikeJerred, many thanks for that!

Daveiano avatar Jan 12 '22 01:01 Daveiano

H, i need some help to use the playwright-fake-dialog, do i need any code from "test/preload.js"?(https://github.com/wsw0108/electron-playwright-test/blob/master/test/preload.js).

This is my code

await mock(electronApp, [
   {
     method: 'showOpenDialog',
     value: {
       filePaths: [join(__dirname, 'minimal.pdf')]
     }
   }
 ])
 await newPage.click('[e2e-id="action-bar-create"]');

And i get this ERROR in our Log-File: "fs.api.selectFilesWithOpenDialog (id 46189c0c) - failed due to error: n.a.dialog.showOpenDialog is not a function"

Our Application uses Electron 9, could that be a problem?

FomerMay avatar Mar 22 '22 12:03 FomerMay

Based on spectron-fake-dialog, I wrote a little library to fake electron dialog for playwright/electron, https://github.com/wsw0108/playwright-fake-dialog

Awesome! Can actually simplify this and just do:

const electronApp = await playwright._electron.launch({ args: ['./main.js']});

// ...

// in test:
electronApp.evaluate(
  async({ dialog }, filePaths) => {
    dialog.showOpenDialog = () => Promise.resolve({ canceled: false, filePaths });
  },
  ['file.txt']
);

best method, you can also control dialogs like so:

const dialogControlHandler = electronApp.evaluateHandler(() => {
  let resolve;
  const promise = new Promise(res => { resolve = res; });
  
  return {promise, resolve};
});


const dialogOptions = await electronApp.evaluate(
 async({ dialog },{ dialogControlHandler }) => {
   return new Promise(res => {
     dialog.showMessageBox = (options) => {
       res(options);
       return dialogControlHandler.promise;
     };
   });
  },
 { dialogControlHandler }
);

//Now you have the dialog options avaliable at `dialogOptions` (eg: dialogOptions.title)

//Choose how to resolve the dialog:
dialogControlHandler.evaluate( ({ resolve }) => resolve({response: 1, checkboxchecked: false}) );

dquak avatar Jan 30 '24 10:01 dquak

Investigation:

  • Upstream Electron method ShowOpenDialog calls immediately OS APIs
  • When dealing with normal file pickers, upstream its a noop when intercepted like here in this cp
  • Probably a new event should be introduced so Electron can wait for it and return the value once emitted. Since Electron API would be different. event -> path[] all the time. It's never a file content etc.
  • Similar / different request is about setInputFiles should set path property. There we should extend 'DOM.setFileInputFiles' so it sets the path. https://github.com/microsoft/playwright/issues/10527

mxschmitt avatar Mar 18 '24 10:03 mxschmitt

Hello everyone, do we have any updates on this ^^^?

I am new to Playwright and I need help with a file upload in my Electron app. My app doesn't have an input element with type="file", so I have to use a FileChooser instead.

Here's the scenario: there's a "Browse Files" button in the app. When I click it, a file manager dialog opens. I then select a file and click the "Upload" button in the file manager dialog to upload the file.

In my script, it successfully clicks the "Browse Files" button, and the file manager dialog opens. However, it doesn't select a file or upload it, and the script times out with an error.

Error: page.waitForEvent: Target page, context or browser has been closed =========================== logs =========================== waiting for event "filechooser" ====================================

Versions: "@playwright/test": "^1.45.3", "electron-playwright-helpers": "^1.7.1", "electron": "29.1.0",

Here is my test script:

const browseFilesBtn = await page.locator('button[aria-label="browse files"]')

    try {
      const fileChooserPromise = page.waitForEvent('filechooser');
      await page.click(browseFilesBtn);
      const fileChooser = await fileChooserPromise;
      await fileChooser.setFiles(filepath);
  } catch (e) {
      console.log(e.message);
  }
});

I have tried this one as well:

const [fileChooser] = await Promise.all([
  page.waitForEvent('filechooser'),
  page.click('button[aria-label="browse files"]'),
]);


console.log("File chooser event triggered.");
await fileChooser.setFiles(filePath);
console.log("File selected.");

Thank you all in advance!!

sdet-g33 avatar Jul 30 '24 06:07 sdet-g33