Desktop Material

E2E Smoke Tests

This document explains the end-to-end smoke test harness added for GitHub Desktop, what it covers, how it runs locally and in CI, and which repository files make up the implementation.

Desktop Material is Windows-only. The supported packaged smoke lane installs and exercises Windows x64; inherited non-Windows fixture branches are not product or release targets.

Overview

The smoke suite uses Playwright's Electron support to launch the real Desktop application and drive a small set of critical user paths. The focus is not broad UI coverage. The suite is intended to protect the app's highest-risk integration boundaries:

The suite is deliberately small and runs serially in one Electron session. That keeps it practical for CI and useful for iterative development.

What The Suite Covers

The current smoke spec lives in app/test/e2e/app-launch.e2e.ts and covers:

These tests intentionally mix UI assertions with repository-level verification by checking Git state directly in the smoke repository.

File Layout

The main files added or changed for the E2E harness are:

How The App Is Launched

The fixture supports three effective launch modes.

Installed app

If DESKTOP_E2E_APP_PATH is set, the fixture launches the executable at that path. This is the mode used by CI after the app has been packaged and installed.

This matters most on Windows, where Squirrel update behavior depends on running from a real installed application layout.

Packaged app

If DESKTOP_E2E_APP_PATH is not set and DESKTOP_E2E_APP_MODE is not set to unpackaged, the fixture resolves the packaged app path from dist and launches that executable directly.

This is the default E2E mode because CI uses production-like packaged artifacts.

Unpackaged app

If DESKTOP_E2E_APP_MODE=unpackaged, the fixture launches out/main.js instead of a packaged executable.

This mode exists for local iteration. It avoids the need to fully package and sign the app just to run the smoke suite while still using a production webpack bundle and staged resources.

Local Commands

The branch adds two ways to run the suite locally.

Packaged mode

yarn test:e2e:packaged

This builds a packaged production app and then runs the E2E suite.

Unpackaged mode

yarn test:e2e:unpackaged

This builds a production-configured staged app in out/ and runs the same E2E suite against out/main.js.

The unpackaged build path uses DESKTOP_SKIP_PACKAGE=1 so script/build.ts stages the app without invoking the final packaging step.

Default alias

yarn test:e2e

This remains the packaged path and is equivalent to yarn test:e2e:packaged.

Smoke Repository Setup

The suite uses a throwaway local Git repository created in the system temp directory.

app/test/e2e/test-helpers.ts is responsible for:

The tests then verify Desktop's behavior both through the UI and by checking the repository state directly with Git commands.

Updater Testing

Updater behavior is tested through a local HTTP server defined in app/test/e2e/mock-update-server.ts.

Build-time updater URL override

app/app-info.ts now uses DESKTOP_E2E_UPDATES_URL when present. This is what lets E2E builds point the app at the local mock update server instead of the real update service.

CI does not use a shared fixed updater port. The .github/actions/setup-e2e-update-port action asks the operating system for a currently available port on 127.0.0.1, closes the selection probe, and writes the exact http://127.0.0.1:<port>/update value to the job environment. The production build and packaged test process consume that same value, so the URL compiled into the app matches the mock server started at runtime. The mock server rejects non-HTTP, non-loopback, credential-bearing, query/fragment, or non-/update overrides rather than widening its listening surface.

The July 19 local recovery gate also exercised the production bundle through an owned loopback provider and off-screen Win32 desktop. Its ten accepted passes covered launch, Submodule Manager gating, temporary child context, Back, restart, Appearance persistence, compact keyboard operation, dark/auto-fit rendering, all language modes, and representative application regressions. A final post-build 1440×960 child/read-only/Back regression passed before the owned app, provider, CDP listener, credential, desktop, and fixture root were removed. A later fresh-bundle race regression synchronously exercised duplicate Open and Back activation; it preserved one persisted repository and tab, restored the root once, and showed no error. This headless gate complements the installed Windows x64 packaged-E2E job, which passed in correction CI 29696805239 using the same per-job loopback updater URL.

Windows behavior

For Windows, the mock server serves Squirrel.Windows-style responses.

The fake package download stays open rather than completing, which keeps the app in the "Downloading update…" state without requiring a valid signed update package.

Mock server control plane

The mock server exposes a simple control surface under /_control/.

The tests use it to:

CI Design

The e2e-smoke job in .github/workflows/ci-windows.yml runs separately from the main build job.

It currently:

The CI job intentionally tests production-like packaged or installed artifacts. This catches failures that do not show up when running only webpack output.

Windows-Specific Notes

Windows needed a few extra pieces to keep the suite stable.

Videos, Traces, and Diagnostics

The suite writes output to playwright-videos.

Artifacts include:

The workflow uploads that directory as an artifact so CI failures can be inspected after the run completes.

The fixture also forwards renderer console errors and page errors to the job log to make failures easier to diagnose when the app dies before Playwright can make useful assertions.

Screenshot Captures

The smoke suite is for assertions, not evidence images. When a change needs a screenshot of the real built app — especially a multi-tab surface such as the tab overflow dropdown — use the capture fixture documented in App capture fixture:

node script/capture-app.js --out=overflow.png --tabs=14 --size=1100x760 \
  --step=click:.repository-tab-overflow --step=wait:800

It reuses this harness's launch shape (a throwaway --user-data-dir and the same isolated Git environment), drives the same first-run flow, and seeds the repositories directly into the renderer's database rather than driving the Add-repository dialog. That document also records the setup routes that do not work, so they are not rediscovered one attempt at a time.

Limitations And Tradeoffs

When To Use Which Mode

For the July 18–19 recovery changeset, the exact remote Windows result remains a publication receipt rather than a locally inferred success.