# Write Playwright Tests for an Electron App

Yes. An agent writes Playwright tests that launch your built Electron app, check the window and an IPC round trip, run them under xvfb and open a PR.

[Run this on Rush](https://cloud.getrush.ai/run/write-electron-playwright-tests/electron/), in a Project that has your repository.

## What the agent does

Playwright can drive Electron itself, not just a browser: `_electron.launch()` starts the real app, returns the main process as an object you can `evaluate` in, and hands back each `BrowserWindow` as a page. The agent begins by finding what to launch. In electron-react-boilerplate, `package.json` sets `main` to `./release/app/dist/main/main.js`, and `electron.vite.config.ts` writes the main bundle to `release/app/dist/main`, the preload to `release/app/dist/preload/preload.js` and the renderer to `release/app/dist/renderer`, so `npm run build` (which is `electron-vite build`) has to run before any test launches the app. Electron is `^44.4.0` and `engines` requires Node 24.

Next it reads the IPC path end to end, because that is what the tests have to prove works across processes. `src/main/preload.ts` exposes `window.electron.ipcRenderer` with `sendMessage`, `on` and `once`, typed to a single channel, `'ipc-example'`. `src/main/main.ts` registers `ipcMain.on('ipc-example', ...)` and replies with `IPC test: pong`. `src/renderer/index.tsx` sends `['ping']` on load. The existing tests are Jest tests in `src/__tests__/`: `App.test.tsx` renders the component in jsdom and `updates.test.ts` mocks `electron` entirely, so nothing today runs the real main process, preload and renderer together. `.erb/scripts/smoke-test.cjs` checks startup over the remote debugging port, but asserts nothing about IPC.

The agent adds `@playwright/test` as a dev dependency and writes `e2e/app.spec.ts`. It launches with `_electron.launch({ args: ['release/app/dist/main/main.js'] })`, waits for `firstWindow()`, and asserts three things: the window opens with the title `Hello Electron React!` from `src/renderer/index.html`; the renderer loaded, by finding the heading `electron-react-boilerplate` that `App.tsx` renders; and the IPC round trip, by calling `window.electron.ipcRenderer.once` and `sendMessage('ipc-example', ['ping'])` inside the page and expecting `IPC test: pong` back. A fourth check uses `electronApp.evaluate` to read `app.isPackaged` from the main process, so the test fails loudly if it ever launches a packaged build by mistake.

Jest's default pattern would also pick up `*.spec.ts`, so the agent adds `e2e` to `testPathIgnorePatterns` in `jest.config.js` to keep the two runners apart, and adds an `e2e` script. It runs `npm ci`, `npm run build` and then `xvfb-run --auto-servernum npx playwright test` in its sandbox, the same `xvfb-run` the repository's `test.yml` already uses for the smoke test, until all checks pass on a clean build.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Write Playwright tests that launch this Electron app with _electron.launch from @playwright/test, using the built main entry from package.json after npm run build. Check that the first window opens, the renderer loads (the App heading is visible), and an IPC round trip works: from the page, send 'ping' on the ipc-example channel through window.electron.ipcRenderer and expect the main process reply 'IPC test: pong'. Put the tests under e2e/ and keep them out of Jest's testPathIgnorePatterns. Add an e2e script and a step in the existing test workflow that runs it under xvfb-run on Linux. Run them under xvfb in the sandbox until they pass and open a pull request.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The pull request adds `e2e/app.spec.ts`, a `playwright.config.ts` with `testDir: 'e2e'` and a trace kept on failure, `@playwright/test` in `devDependencies` with its lockfile entry, an `"e2e": "playwright test"` script, the `e2e` entry in `jest.config.js`, and a step in `.github/workflows/test.yml` after the existing Linux smoke test: `xvfb-run --auto-servernum npm run e2e`. The Electron app's source does not change.

The description quotes the Playwright summary from the sandbox run and explains each assertion and why it is there, for example that the IPC test goes through the real preload rather than calling `ipcMain` directly. Because the workflow change runs on the pull request, GitHub's Ubuntu runner shows the same tests passing. The session's record keeps the build output, each `playwright test` run and any trace from a failed attempt.

A reviewer checks the timeouts are generous enough for a cold Electron start on a shared runner, that the tests close the app in `afterAll` so no Electron process outlives the job, and whether to run the new step on macOS and Windows too, which the pull request leaves as a suggestion.

## Run it on every IPC channel added

The tests are only as good as the channels they cover. Start this run from a GitHub trigger on `pull_request.opened`, with a prompt that asks for a Playwright test for any channel the pull request adds to the `Channels` type in `preload.ts` or to an `ipcMain` handler, and new IPC arrives with its round-trip test in the same pull request.

If you maintain several Electron apps, a single script can send one request per repository. Each runs in its own sandbox with its own display server, so Electron instances never fight over one X display, and each pull request carries the output from its own run.

## Why run it on Rush?

Headless Electron on Linux is where these tests usually stall: no display, missing shared libraries for Chromium, sandbox flags, and an `xvfb-run` that has to wrap the right command. A Rush sandbox is a Linux machine the agent controls, so it installs what Electron 44 needs, runs the real build and the real app under xvfb, and finds those problems there, not in your CI after you push.

The proof is the run, not a claim. The session's record shows the build, every Playwright run and the traces of failures the agent fixed, and the pull request's workflow repeats the tests on GitHub. Your laptop never opens an Electron window, and several repositories can get their first end-to-end tests at once.

## Frequently asked questions

### Do I need Spectron or WebdriverIO?

No. Spectron is deprecated, and Playwright's built-in Electron support covers launching the app, evaluating in the main process and driving each window. The pull request adds only `@playwright/test`; no browser download is needed for Electron tests, since Playwright drives the Electron binary you already install.

### Why build first instead of testing the dev server?

`npm start` runs `electron-vite dev` with a renderer dev server and watch mode, which is not what users run. Testing the output of `npm run build` exercises the bundled main, preload and renderer files, so a bundling mistake fails the test.

### Will it test my auto-updater or native menus?

Not by default. `updates.ts` already has a Jest test with `electron-updater` mocked, and native menus are outside Playwright's reach. The pull request covers the window, the renderer and IPC, and says what is left out.

### Does it work on macOS and Windows runners?

The tests are cross-platform; only Linux needs `xvfb-run`. The pull request adds the step for Linux, matching how the existing smoke test runs, and suggests the line to add for the macOS and Windows matrix entries.
