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

[Feature] Headless Electron #13288

Open bpasero opened 2 years ago

bpasero commented 2 years ago

I could not find a way to run Electron tests headless like that is possible with browser tests.

//cc @pavelfeldman

pavelfeldman commented 2 years ago

Electron itself does not have the headless mode. It can probably be simulated via hiding the window, but there is no built-in functionality of swapping the graphics stack like there is in Chromium. What do your existing tests do today?

bpasero commented 2 years ago

What do your existing tests do today?

:-), got me there. Yeah, not headless. I think its fine, I was just thinking that browser and electron should support the same set of options, but its not a big deal for us I would say.

In our CI though we do run headless browser tests and maybe that means a bit faster execution time, but I see the issue that Electron itself does not really support this mode.

pavelfeldman commented 2 years ago

It wasn't a gotcha reply. I was hoping you have an ad-hoc solution, something like hiding the window in test mode, that was working for you.

tanishqkancharla commented 1 year ago

I was able to get headless electron tests working with this snippet: override the default behavior of showing the browser window once it's ready to show:

this.browserWindow.once("ready-to-show", () => {
    if (config.test?.headless) return
    this.browserWindow.show()
})

So you can run the app electron main.js --test-headless=true, parse the process.argv, and when actually ready to display a window, you read the config file, and defer showing the window. This also means you can also test "head-fully" if, for example, playwright is invoked in DEBUG mode:

const isDebug = Boolean(process.env.PWDEBUG)

const config: Partial<Config> = {
  test: {
      headless: !isDebug,
  },
  partition,
}

const args = [rootPath("build/main.js"), ...makeAppCliArgs(config)]

The renderer process is still running, just completely headlessly, without a display, so the tests are all valid. For this to work on CI (on linux), you need xvfb.

Also these would be great comments to go on the docs.

greenwood-vaarst commented 1 year ago

@tanishqkancharla can I ask are you then still able to do

const app = await electron.launch(someConfig);
const page = await app.firstWindow();

or are you creating a different kind of page somehow?

thanks

tanishqkancharla commented 1 year ago

@greenwood-vaarst that's exactly what I'm doing. I use fixtures, and the api comes out pretty clean. Here's a snippet of what that looks like:

export const e2eTest = baseTest.extend<E2ETestFixture, E2EWorkerFixture>({
    testData: [
        async ({ harness }, use, testInfo) => {
                        // ...
            // Run head-full if debugging
            // npx playwright --debug
            const isDebug = Boolean(process.env.PWDEBUG)

            const config: Partial<Config> = {
                test: {
                    MAIN_PORT,
                    RENDERER_PORT,
                    headless: !isDebug,
                    id: testId,
                },
                partition,
            }

            // ...

            const app = await electron.launch({ args, colorScheme: "dark" })
            await app.firstWindow()

            await harness.waitUntilTestIsReady(testId)

            try {
                await use({ app, harness, partition, testId })
            } finally {
                harness.dispatch.unmountTest(testId)
                await app.close()
            }
        },
        { auto: true, timeout: 5000 },
    ],
})

And here's how it gets used:

e2eTest("Finds match", async ({ testData }) => {
        const h = e2eDialect(testData)
        const w = h.window(0)

        await h.createHtmlFile("test.html", `<p>some words</p>`)
        await w.openFile("test.html")
                // ...
})

When I was working on this project, I didn't really use the playwright page object directly very much. Instead I hooked up a third test harness server that connected to both the main and renderer process and communicated with them directly. In the second code snippet, h.createHtmlFile calls a remote handler on the main process and w is an alias to the first window.

greenwood-vaarst commented 1 year ago

Thanks so much for your reply @tanishqkancharla