Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
Testing Electron apps with Playwright and GitHub Actions#
Yesterday I figured out (issue 133) how to use Playwright to run tests against my Electron app, and then execute those tests in CI using GitHub Actions, for my datasett-app repo for my Datasette Desktop macOS application.
Installing @playwright/test#
You need to install the @playwright/test package. You can do that like so:
1npm i -D @playwright/testThis adds it to devDependencies in your package.json, something like this:
1 "devDependencies": {2 "@playwright/test": "^1.23.2",Writing a test#
I dropped the following into a test/spec.mjs file:
1import { test, expect } from '@playwright/test';2import { _electron } from 'playwright';3
4test('App launches and quits', async () => {5 const app = await _electron.launch({args: ['main.js']);6 const window = await app.firstWindow();7 await expect(await window.title()).toContain('Loading');8 await app.close();9});The .mjs extension is necessary in order to use import, since it lets Node.js know that this file is a JavaScript module.
The test can be run using playwright test.
I later added it to my package.json section like this:
1 "scripts": {2 "test": "playwright test"3 }Now I can run the Playwright tests using npm test.
Recording video of the tests#
Recording videos of the test runs turns out to be easy: change the _electron.launch() line to look like this:
1 const app = await _electron.launch({2 args: ['main.js'],3 recordVideo: {dir: 'test-videos'}4 });This creates the videos as .webm files in the test-videos directory.
These videos can be opened in Chrome, or can be converted to mp4 using ffmpeg (available on macOS via brew install ffmpeg):
1ffmpeg -i bc74c2a51bd91fe6f6cb815e6b99b6c7.webm bc74c2a51bd91fe6f6cb815e6b99b6c7.mp4Converting to .mp4 means you can drag and drop them onto a GitHub Issues thread and get an embedded video player. Here’s an example I recorded.
Custom timeouts#
Playwright has a default 30s timeout on every action it takes. This turned out to be a bit too short for one of my tests, which installs a Python interpreter and a bunch of Python packages and can take 57s. Here’s how I fixed that so the test could pass:
1test('App launches and quits', async () => {2 // This disables the global 30s timeout3 test.setTimeout(0);4 const app = await _electron.launch({5 args: ['main.js'],6 recordVideo: {dir: 'test-videos'}7 });8 const window = await app.firstWindow();9 // This sets a timeout of 90s for the page to load and the10 // element with id="run-sql-link" to appear in the DOM:11 await window.waitForSelector('#run-sql-link', {12 timeout: 9000013 });14 await app.close();15});Running it in GitHub Actions#
I’m using the macos-latest image in my GitHub Actions workflow. The relevant configuration in my .github/workflows/test.yml file looks like this:
1name: Test2
3on: push4
5jobs:6 test:7 runs-on: macos-latest8 steps:9 - uses: actions/checkout@v310 - name: Configure Node caching11 uses: actions/cache@v312 env:13 cache-name: cache-node-modules14 with:15 path: ~/.npm16 key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}17 restore-keys: |18 ${{ runner.os }}-build-${{ env.cache-name }}-19 ${{ runner.os }}-build-20 ${{ runner.os }}-21 - name: Install Node dependencies22 run: npm install23 - name: Run tests24 run: npm test25 timeout-minutes: 526 - name: Upload test videos27 uses: actions/upload-artifact@v328 with:29 name: test-videos30 path: test-videos/This workflow configures NPM caching to avoid downloading everything every time, installs the dependencies, runs the tests, and then uploads the videos at the end.
Those videos end up attached to the workflow run as an artifact that can be downloaded and viewed locally.