Files
hermes-agent/tests/install/README.md
T
ethernet 0f903e14a3 test(install-e2e): hermes-desktop-app-update goes live on the script driver
Playwright must own the spawn (it needs the inspection pipe), but
hermes desktop is not just build+launch - stamp checks, integrity
gates, sandbox fixups, and a constructed child environment. So the
driver intercepts the product's own launch: a sitecustomize.py on
PYTHONPATH (opt-in via HERMES_E2E_CAPTURE_LAUNCH) wraps subprocess.run,
captures argv/cwd/env at the spawn site, and fakes success instead of
spawning; launch-from-spec.mjs then _electron.launch-es exactly that
spec and clicks Settings -> About -> Update now. Completion is product
state, not a Playwright event: the handoff result file or the checkout
reaching the expected sha (source installs write no result file).

Ships with the driver, so it works unchanged on every sampled OLD ref
- no product flag, no pre-flag fallback split. Both launch shapes are
matched (npm exec electron / packaged exe under apps/desktop/release);
npm BUILD calls pass through untouched. Exit 0 without a capture fails
the leg: a version that never reached its launch must not pass.

Probe-the-probe: scripts/launch_capture_probe.sh runs control rows
(no opt-in, non-launch argv) and both treatment shapes - all green
locally. Gate flips on the shared run workflow for linux/macos;
windows adopts the same path with the driver restructuring.
2026-08-12 04:20:56 -04:00

6.6 KiB

Install and Update E2E Tests

These tests answer one question: can a user on a released version get to this commit?

Each test leg installs an old released version, then updates it to HEAD. The install and the update run the real user surfaces. The legs do not use mocks and do not use headless proxies of GUI flows.

The layers

The test family has four layers. Each layer has one job.

  1. scripts/sandbox/generate-e2e-matrix.mjs declares the support matrix. It lists every {os, install-method, update-method} pair. It expands the pairs against the sampled release tags. It knows nothing about which pairs CI can run.
  2. .github/workflows/install-e2e.yml is the primary workflow. It picks the release tags, runs the generator, and fans out one matrix job per OS. It also writes the plan chart and the result chart on the run summary.
  3. The run workflows own the capability knowledge. install-e2e-run.yml serves linux and macos with one OS-agnostic driver. install-e2e-windows-run.yml serves windows. A job-level if: gate in each run workflow lists the pairs its driver can run. All other pairs skip natively and show as grey.
  4. The drivers do the work. tests/install/installer-script-e2e.sh is the POSIX driver. tests/install/windows-installer-script-e2e.ps1 is the windows script driver. tests/install/windows-desktop-gui-e2e.ps1 is the windows GUI driver.

To declare a new method, edit the generator. To implement a method, flip the gate in the run workflow and extend a driver.

The isolation trick

The drivers do not touch the network for git operations. Each driver makes a bare clone of the checkout at serve.git. Then it points every git process at this clone. The mechanism is a driver-owned GIT_CONFIG_GLOBAL file with url.<file://serve.git>.insteadOf rewrites for both canonical repository URLs.

The driver parks the main branch of serve.git at the old release. The installer runs and lands on the old release. Then the driver moves main to HEAD. An update becomes available in the same way that it does for a real user.

The installer script is not downloaded. The install leg runs the copy from the old git ref. This is the copy that a user of that version executed. The update leg runs the copy from HEAD.

What one leg does

Each leg with the script drivers has these phases:

  1. Stage: make the bare clone, park main at the old release.
  2. Install: run the old release's own installer script. Make sure that the checkout is at the old commit and that hermes --version works.
  3. Desktop smoke: run hermes desktop --build-only from the installed CLI. This proves that the installed version can build the desktop app. If the installed version does not have this flag, the phase reports a skip and continues.
  4. Update: move main to HEAD. Apply one update method. Make sure that the checkout is at HEAD and that hermes --version works.
  5. Desktop smoke again, at HEAD.

The windows GUI driver replaces phases 2 and 4. It downloads the published Hermes-Setup.exe, clicks through the installer window with AutoHotkey, and clicks "Update now" in the running app with Playwright.

Old versions

A leg can install a release from months back. The driver must not assume that the old version has today's CLI surface. The rule: probe, do not assume.

  • For the installer, read the flag from the old ref's own script text.
  • For the installed CLI, ask the binary with --help.
  • If a flag is not found, omit the flag. This is not an error.

The install methods

  • installer-script: the platform's one-liner (curl | bash on linux and macos, irm | iex on windows).
  • installer-script+desktop: the same one-liner with its desktop stage opted in (--include-desktop / -IncludeDesktop). The stage builds the desktop app during the install. On windows it also registers Start Menu and Desktop shortcuts. On linux and macos it builds the app inside the checkout and registers no OS entry point.
  • desktop-installer@latest: the published GUI installer (Hermes-Setup.exe on windows), clicked through the real window.

The two app-update variants

The desktop app has two launch paths, so the matrix has two app-update methods. Both click "Update now" in the running app. They differ in how the app starts:

  • open-app-update: the app starts from the OS entry point that the install created. On windows these are the Start Menu and Desktop shortcuts to the installed Hermes.exe; the desktop installer always creates them. The installer scripts do not create entry points: their opt-in desktop stage (--include-desktop / -IncludeDesktop) builds the app inside the checkout but does not register it with the OS. So open-app-update legs pair with a desktop-installer install.
  • hermes-desktop-app-update: the app starts with the hermes desktop command. Every install method provides this command, on each OS that ships the desktop app. On linux this is the only app surface: no desktop installer and no packaged desktop artifact exist for linux. The driver captures the product's own launch call (argv, cwd, environment) with e2e-assets/launch-capture/sitecustomize.py and re-executes it under Playwright, which owns the app and clicks the update flow.

Skips

A grey leg is normal. There are two causes:

  • The method pair has no driver yet. The pair is a declared TODO. The gate in the run workflow lists the pairs that run.
  • The starting release predates the surface under test. Example: a release without apps/desktop has no window to launch. The tag annotation tag_has_desktop from the primary workflow marks these releases.

The result chart on the run summary shows each leg as passed, failed, or skipped.

Triggers

The matrix does not run on pull requests. One leg installs real toolchains and takes more than 10 minutes. The triggers are:

  • A schedule, every 12 hours. This finds upstream drift.
  • A release tag push. This is the moment the set of start versions changes.
  • Manual dispatch. You can select the route and the tag count:
gh workflow run install-e2e.yml --ref <branch> -f route=both -f tag-count=2

Artifacts

Each leg uploads its logs as an artifact. Every leg also records the screen for its whole run: the composite action .github/actions/e2e-screen-record installs ffmpeg, records with the OS's capture backend (x11grab on linux, gdigrab on windows, avfoundation on macos), and fails the leg if the recording is missing or has zero frames. Linux runners have no display, so the action starts Xvfb :99 first and exports DISPLAY for every later step — the app under test and the recorder share that display. The windows GUI leg also uploads screenshots and the update result file. Get them with gh run download <run-id>.