How to Test an Electron App End-to-End
Electron testing with plain-language steps on screenshots instead of WebDriver or Playwright selectors.
Automate and scale manual testing with AI ->
Above: the test below running against draw.io, sped up with idle time removed.
You can do Electron testing end-to-end without WebdriverIO or Playwright by describing each step in plain language. TestDriver matches each description against screenshots of the installed app, so:
- you test the same
.debyour users install, with no test hooks in your code - you don’t write or maintain selectors
- your test reads like the steps you’d give a teammate
TestDriver generates and runs black-box end-to-end tests. It starts your app in a fresh virtual machine, looks at the screen and uses a real mouse and keyboard, so it never needs selectors or access to your code. Each test is a normal Vitest file, and every run is recorded as a replay.
The example installs draw.io, an open-source diagram editor built with Electron, adds a shape to a blank diagram and labels it. The full project is on GitHub: testdriverai/example-electron-app.
Why not WebdriverIO or Playwright
Electron doesn’t maintain its own test tool. Its automated testing docs point to WebdriverIO, Selenium, Playwright’s experimental Electron support, or a custom IPC driver. The first three find elements with selectors into the renderer’s DOM:
// WebdriverIO-style selector (illustrative, not run)
await $('.geSidebar a[title="Rectangle"]').click();
TestDriver describes the same element the way a user sees it:
await testdriver.find("plain rectangle shape, the first shape in the General section of the left shape panel").click();
The selector breaks when the markup changes. The description only changes when the UI does.
Watch the full replay
The replay shows every step with its screenshot and log.
The test
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/vitest/hooks";
const DRAWIO_DEB =
"https://github.com/jgraph/drawio-desktop/releases/download/v32.5.5/drawio-amd64-32.5.5.deb";
describe("draw.io (Electron)", () => {
it("adds a labeled rectangle to a new diagram", async (context) => {
const testdriver = TestDriver(context);
// Download and install the .deb. We start the app ourselves below.
await testdriver.provision.installer({ url: DRAWIO_DEB, launch: false });
// Pull in any missing runtime dependencies, then start draw.io.
await testdriver.exec(
"sh",
"sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y -qq",
240000,
);
await testdriver.exec("sh", "nohup drawio >/tmp/drawio.log 2>&1 &", 10000);
// draw.io opens straight into a blank diagram. Wait for the window to appear.
await testdriver.find('"Untitled Diagram" title at the top left of the draw.io window', {
timeout: 60000,
});
// Add a rectangle from the shape panel. It lands in the middle of the canvas.
await testdriver.find("plain rectangle shape, the first shape in the General section of the left shape panel").click();
// Double-click the rectangle to edit its label, type, then click away to commit.
await testdriver.find("the selected white rectangle in the middle of the canvas").doubleClick();
await testdriver.type("Checkout Service");
await testdriver.find("empty white grid area of the canvas below the rectangle").click();
// Wait for the labeled shape, then check what the app rendered.
await testdriver.find('rectangle on the canvas labeled "Checkout Service"', { timeout: 15000 });
const labeled = await testdriver.assert(
'the draw.io canvas shows a rectangle with the label "Checkout Service" and the label is not in edit mode',
);
expect(labeled).toBeTruthy();
});
});
How it works
Install and launch the release build
provision.installer() downloads the .deb and installs it on a fresh Linux sandbox. The two exec() calls fix any missing dependencies and start the app in the background.
Describe elements by what users see
Each find() takes a description that names the label, the look and the position, such as “the first shape in the General section of the left shape panel”. Canvas content like the rectangle has no DOM element to select, but it’s on screen, so it can be found. See locating elements.
Wait for results, then assert
find() with a timeout keeps looking until the element appears, so the test waits for the window without a fixed sleep. assert() then checks a plain-language statement against the screen and returns true or false. See making assertions.
Run it yourself
Clone the example repo, or set up TestDriver in your own project and run the test:
npx testdriverai init
npx vitest run electron-app.test.mjs
To test your own Electron app:
- Point
provision.installer()at your release.deb(electron-builder and Electron Forge both write one). - Start your binary in the second
exec()call.dpkg -L <package>shows where it’s installed. - On Windows, pass
os: "windows"toTestDriver()and point the installer at your.exeor.msi.
FAQ
Do I need WebdriverIO, Playwright or ChromeDriver? No. TestDriver drives the installed app from the screen, so there’s no driver to match to your Electron version.
Do I need test IDs?
No. Steps are matched on screenshots, so there are no data-testid attributes to add or keep in sync.
Can I run it in CI? Yes. It’s a normal Vitest file and the app runs in TestDriver’s cloud sandbox. See CI/CD.
Automate and scale manual testing with AI
TestDriver uses computer-use AI to test any app - write tests in plain English and run them anywhere.