How to Automate Electron Apps with Selenium
Automate an Electron app’s renderer with Selenium WebDriver. Configure ChromeDriver, point Selenium at the Electron binary, and troubleshoot version and connection issues.
Selenium WebDriver can automate an Electron app’s renderer UI through ChromeDriver. The key setup differences from ordinary browser automation are starting a ChromeDriver server, connecting Selenium to that server’s actual address, and setting goog:chromeOptions.binary to the Electron executable you want to test.
Electron’s automated testing guide describes Selenium usage as otherwise similar to automating a normal website. The Node.js example below follows that approach. Use versions of electron-chromedriver and Electron that match your project, and replace the sample executable path with the built app for your operating system.
Prerequisites and version alignment
- An Electron application that can be built and launched on the machine running the test.
- Node.js and npm.
selenium-webdriverfor the WebDriver client andelectron-chromedriverfor the Electron-oriented ChromeDriver package.- The path to the Electron executable inside the build under test.
The Electron-maintained electron/chromedriver repository says the package downloads ChromeDriver for Electron and tracks Electron’s major version. Check the package releases and your Electron version together rather than copying an old sample version. The Electron guide’s printed ChromeDriver version is an example from that guide, not a current recommendation.
Selenium Manager can automate driver and browser management for Selenium bindings by default, according to the Selenium documentation. Electron’s guide still requires you to identify the Electron binary and connect to the ChromeDriver server. The reviewed documentation does not promise that Selenium Manager resolves Electron-specific driver compatibility or launches the app for you.
Install the packages
In your project, install the Selenium client and Electron’s ChromeDriver package:
npm install --save-dev selenium-webdriver electron-chromedriver
Check the installed package’s instructions for starting its ChromeDriver executable in your environment. Keep the Electron and driver releases compatible, and use the package’s current release rather than treating version strings in an older guide as current.
Start ChromeDriver and connect Selenium
Start ChromeDriver as a separate process before building the WebDriver client. Electron’s example listens on port 9515; your test must use the address and port where your process actually listens.
The following is a complete basic test script. Set ELECTRON_BINARY to the executable for your build. It connects, waits for the document body, performs a small interaction, and closes the session even if an assertion fails.
const assert = require('node:assert/strict')
const webdriver = require('selenium-webdriver')
async function main() {
const binary = process.env.ELECTRON_BINARY
if (!binary) {
throw new Error('Set ELECTRON_BINARY to the Electron app executable')
}
const driver = new webdriver.Builder()
.usingServer('http://localhost:9515')
.withCapabilities({
'goog:chromeOptions': {
binary
}
})
.forBrowser('chrome')
.build()
try {
await driver.get('file:///path/to/your/app/index.html')
await driver.wait(
webdriver.until.elementLocated(webdriver.By.css('body')),
10000,
'App document body did not appear'
)
const title = await driver.getTitle()
assert.equal(title, 'My Electron App')
} finally {
await driver.quit()
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
Change the navigation target to the entry point or route appropriate to your app. Many Electron apps load a local file in development or a packaged renderer; use the URL that the app actually serves. The title assertion is only an example: replace it with checks for your own app’s visible state and behavior.
For a macOS bundle, the executable is commonly inside the app bundle at a path shaped like /path/to/MyApp.app/Contents/MacOS/MyApp. That is a path pattern, not a universal value. Windows and Linux builds use different paths; point at the executable for the build under test.
Use WebDriver to test renderer behavior
After the session connects, use ordinary Selenium WebDriver commands to inspect and operate the renderer. Prefer explicit waits for a meaningful condition over fixed sleeps, which make tests slower and can still race under load.
const button = await driver.wait(
webdriver.until.elementLocated(webdriver.By.css('[data-testid="save"]')),
10000
)
await driver.wait(webdriver.until.elementIsVisible(button), 5000)
await button.click()
await driver.wait(
webdriver.until.elementLocated(webdriver.By.css('[role="status"]')),
10000
)
Use stable selectors such as test IDs where your app supports them. For interaction tests, wait for a user-visible result after clicking or typing, not merely for the command to return. Selenium here automates the renderer through WebDriver; if tests need Electron main-process APIs or app lifecycle controls, assess whether a framework with documented Electron-specific support fits better.
Configuration details and edge cases
ChromeDriver address and port
The argument to usingServer() must be reachable from the test process and match ChromeDriver’s listening address and port. http://localhost:9515 is Electron’s guide example. If you change the driver port, update the Selenium URL too. When running tests in containers, localhost refers to the container itself; use an address reachable from the Selenium process.
Electron binary selection
Set goog:chromeOptions.binary to the actual Electron executable, not just the containing directory or an unrelated system Chrome binary. Use the build you intend to test. A development executable and a packaged app can load different files and configuration, so make that choice explicit.
Browser name and historical examples
The Electron guide’s current-style builder example uses .forBrowser('chrome') with the Chrome options binary set to Electron. It also notes that .forBrowser('electron') applied to selenium-webdriver versions at or below 3.6.0. Treat that as historical compatibility context; do not copy it into a current setup without checking the API for your installed package.
Cleanup and test isolation
Always call driver.quit() in a finally block so failures do not leave sessions and app processes behind. For repeatable tests, ensure each run starts from a known app state and does not depend on data left by a prior run. Keep test data and selectors specific to the scenario being checked.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection refused or session creation times out | ChromeDriver is not running, the port differs, or the Selenium process cannot reach the configured host. | Start ChromeDriver first. Confirm its listening address and port, then make usingServer() match. In containers, use a host address reachable from the test container. |
| ChromeDriver cannot start the session | The Electron binary path is missing or points to a directory, or the driver and Electron versions are incompatible. | Set the full executable path in goog:chromeOptions.binary. Check the Electron release against the matching electron-chromedriver release. |
| The app opens but Selenium finds no elements | The test navigated to the wrong renderer page, the app has not finished rendering, or the selector does not match the app. | Use the app’s real entry URL, wait for a specific element or state, and verify selectors against the rendered DOM. |
| Tests pass locally but fail in CI | The build path, display/runtime environment, startup timing, or reachable driver address differs in CI. | Log the resolved binary path and driver endpoint, ensure the CI job builds the expected app, and wait on observable readiness conditions instead of relying on timing assumptions. |
| Old example code rejects the browser name or builder setup | The sample targets an older Selenium API. | Use the current package API for the installed selenium-webdriver version and the documented Chrome browser setup with the Electron binary option. |
| ChromeDriver process remains after a failed test | The test did not close the WebDriver session on an error path. | Put driver.quit() in a finally block and ensure the test process reaches cleanup. |
Performance, reliability, and cost
Keep the test suite efficient by waiting for specific conditions, avoiding unnecessary fixed delays, and reusing setup only when tests remain isolated. A mismatch between the app build and ChromeDriver can fail before any test assertion, so verify versions and paths early in CI. The cited documentation provides no benchmark or general runtime guarantee; measure your own suite under its target environment.
The components described here are software packages and local test processes. The cited sources do not establish a usage price for this setup, so account for the compute and CI resources your own environment uses rather than assuming a third-party per-capture charge.
Choosing Selenium or an Electron-focused alternative
Selenium is a reasonable fit when the main need is renderer UI automation and the project can manage ChromeDriver startup, address configuration, and the Electron binary path. Electron’s testing guide also documents other choices:
- WebdriverIO: Electron’s guide describes it as able to launch and shut down the application and expose Electron APIs to tests. Consider it when app lifecycle or main-process access is part of the test requirement.
- Playwright: Electron’s guide describes its Electron support as experimental and based on Electron’s Chrome DevTools Protocol support. Check its current status and compatibility before choosing it for a new suite.
- Spectron: Spectron’s repository is marked deprecated. It is relevant as legacy context for existing test suites, but should not be the default for new work.
Choose based on whether tests need to launch and shut down the app, access main-process APIs, and support the project’s Electron release. Framework status and version compatibility should be checked against the project’s current dependencies.
Or skip the browser setup
If your task is to capture a website as an image or PDF rather than automate an Electron app’s renderer, ScreenshotNeo provides a one-call website screenshot API and MCP server. See the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and each response reports the page verdict and billing status. Its MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does this automate Electron’s main process?
This Selenium flow drives the renderer UI through WebDriver. If a test needs direct main-process APIs or app lifecycle control, evaluate an Electron-focused framework such as the WebdriverIO option documented by Electron.
Is port 9515 required?
No. It is the port shown in Electron’s example. Use the actual address and port where ChromeDriver listens, and configure Selenium to connect to that endpoint.
Should a new test suite use Spectron?
No. Its repository labels the project deprecated. Keep it only as context when maintaining an existing suite and evaluate a currently supported approach for new coverage.
Can ScreenshotNeo capture a local Electron window?
ScreenshotNeo is a website screenshot API. Its documented purpose here is capturing websites from a URL, so this article’s Selenium setup is the relevant route for automating an Electron app’s renderer.


