Every leg installed a release tag and updated to HEAD, so nothing ran HEAD's installer on an empty machine (where all four 2026-09-23 install.ps1 breaks lived) and nothing exercised the updater we ship today -- tag legs run the OLD build's updater handing off to HEAD. The generator appends a HEAD -> NEXT start after the sampled tags, so the column runs wherever the update legs run (dispatch and stable-release). NEXT is a reserved update ref: the drivers mint a child of the install commit that adds one marker file, written to the object store only, which the local bare clone carries into serve.git. On Windows the HEAD leg takes every git.exe dir off PATH and installs no remote get-url shim: Get-PinnedGit returns any git on PATH, so either one skipped pinned-git staging. launch-from-spec's HEAD observer now uses the driver's real git so it cannot poll '' forever on that leg.
286 lines
12 KiB
YAML
286 lines
12 KiB
YAML
# Reusable runner for ONE macOS install/update combination.
|
|
#
|
|
# Two driver arms, one runs per dispatch (the other natively skips):
|
|
#
|
|
# e2e (script arms) tests/install/installer-script-e2e.sh - the
|
|
# OS-agnostic git-redirect driver shared with
|
|
# linux. installer-script(+desktop) installs,
|
|
# script/updater/hermes-desktop-app-update
|
|
# updates.
|
|
# gui-e2e (desktop arm) tests/install/macos-desktop-e2e.sh - the
|
|
# published Hermes-Setup.dmg, mounted and run,
|
|
# then the app driven by Playwright for the
|
|
# app-update methods.
|
|
#
|
|
# Method pairs without a driver arm yet NATIVELY SKIP (grey check, no
|
|
# runner): the capability knowledge lives here, next to the drivers.
|
|
|
|
name: install-e2e macos leg
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
install-method:
|
|
description: 'How OLD gets installed. Supported: installer-script, installer-script+desktop (curl | bash one-liner, optionally with --include-desktop) and desktop-installer@latest (the published Hermes-Setup.dmg).'
|
|
required: true
|
|
type: string
|
|
update-method:
|
|
description: 'How the install updates to HEAD. Script installs support hermes-update / installer-script / installer-script+desktop / hermes-desktop-app-update; dmg installs support open-app-update / hermes-desktop-app-update.'
|
|
required: true
|
|
type: string
|
|
install-ref:
|
|
description: 'What to install before updating: a branch, a tag, or a SHA reachable from main.'
|
|
required: false
|
|
type: string
|
|
default: refs/heads/main
|
|
update-ref:
|
|
description: 'What to update TO: HEAD (the commit under test), or NEXT -- a synthetic child of install-ref that the driver mints, so a leg that installs HEAD still has an update to take (HEAD''s own updater).'
|
|
required: false
|
|
type: string
|
|
default: HEAD
|
|
tag-has-desktop:
|
|
description: "Whether install-ref ships the desktop app (apps/desktop). The caller annotates this from the tag's own tree; desktop-method legs from pre-desktop releases natively skip."
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
leg-id:
|
|
description: 'Artifact-safe matrix leg id (from generate-e2e-matrix.mjs legId). Names this leg''s logs + player artifacts so the report job can link a row to its zip.'
|
|
required: true
|
|
type: string
|
|
dmg-url:
|
|
description: 'Bootstrap dmg to install OLD with. Default: the latest published one — what a user downloads today.'
|
|
required: false
|
|
type: string
|
|
default: https://hermes-assets.nousresearch.com/Hermes-Setup.dmg
|
|
bundle-manifest-url:
|
|
description: 'URL of the JSON schema-1 bundle manifest for the packaged-app arm. Empty (default) means the bundled arm does not run — native packaged coverage only runs when the caller pins a real signed OLD/NEW pair.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
bundle-manifest-sha256:
|
|
description: Pinned transition manifest digest for stable acceptance
|
|
type: string
|
|
default: ''
|
|
bundle-arch:
|
|
description: 'Architecture of the bundled pair: arm64 (macos-15) or x64 (macos-15-intel).'
|
|
required: false
|
|
type: string
|
|
default: arm64
|
|
timeout-minutes:
|
|
description: 'Job timeout. App-update legs do a full Electron build.'
|
|
required: false
|
|
type: number
|
|
default: 60
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# ---- arm 1: script installs (the shared OS-agnostic driver) --------------
|
|
e2e:
|
|
name: install & update
|
|
if: >-
|
|
(inputs.install-method == 'installer-script'
|
|
|| (inputs.install-method == 'installer-script+desktop' && inputs.tag-has-desktop))
|
|
&& (contains(fromJSON('["hermes-update", "installer-script"]'), inputs.update-method)
|
|
|| (contains(fromJSON('["installer-script+desktop", "hermes-desktop-app-update"]'), inputs.update-method) && inputs.tag-has-desktop))
|
|
uses: ./.github/workflows/install-e2e-run.yml
|
|
with:
|
|
install-method: ${{ inputs.install-method }}
|
|
update-method: ${{ inputs.update-method }}
|
|
install-ref: ${{ inputs.install-ref }}
|
|
update-ref: ${{ inputs.update-ref }}
|
|
tag-has-desktop: ${{ inputs.tag-has-desktop }}
|
|
leg-id: ${{ inputs.leg-id }}
|
|
runner: macos-latest
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
# ---- arm 2: the published dmg, then Playwright drives the app ------------
|
|
gui-e2e:
|
|
# Short static name on purpose: name expressions render UNEXPANDED on
|
|
# skipped jobs.
|
|
name: Hermes-Setup.dmg
|
|
if: >-
|
|
inputs.install-method == 'desktop-installer@latest' && inputs.tag-has-desktop
|
|
&& contains(fromJSON('["open-app-update", "hermes-desktop-app-update", "hermes-update", "installer-script", "installer-script+desktop"]'), inputs.update-method)
|
|
runs-on: macos-latest
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
steps:
|
|
# Full history: the driver bare-clones this checkout as the repo the
|
|
# installer/updater talk to.
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 0
|
|
|
|
- name: Set up driver tools only
|
|
uses: ./.github/actions/setup-pm
|
|
with:
|
|
toolchain: all
|
|
packages: ffmpeg
|
|
cache: false
|
|
cache-python: false
|
|
cache-node: false
|
|
|
|
- name: Install locked chat driver dependencies
|
|
shell: bash
|
|
run: npm ci --workspace tests-js --include-workspace-root --omit=dev --ignore-scripts --no-audit --no-fund
|
|
|
|
- name: Start screen recording
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: start
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Stage serve repo (main -> ${{ inputs.install-ref }})
|
|
run: |
|
|
set -euo pipefail
|
|
tests/install/macos-desktop-e2e.sh --phase stage \
|
|
--update-method '${{ inputs.update-method }}' \
|
|
--install-ref '${{ inputs.install-ref }}' \
|
|
--update-ref '${{ inputs.update-ref }}' \
|
|
--dmg-url '${{ inputs.dmg-url }}'
|
|
env:
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Install ${{ inputs.install-ref }} via Hermes-Setup.dmg
|
|
run: |
|
|
set -euo pipefail
|
|
tests/install/macos-desktop-e2e.sh --phase install \
|
|
--update-method '${{ inputs.update-method }}' \
|
|
--install-ref '${{ inputs.install-ref }}' \
|
|
--dmg-url '${{ inputs.dmg-url }}'
|
|
env:
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Update ${{ inputs.install-ref }} -> ${{ inputs.update-ref }} (${{ inputs.update-method }})
|
|
run: |
|
|
set -euo pipefail
|
|
tests/install/macos-desktop-e2e.sh --phase update \
|
|
--update-method '${{ inputs.update-method }}' \
|
|
--install-ref '${{ inputs.install-ref }}' \
|
|
--dmg-url '${{ inputs.dmg-url }}'
|
|
env:
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Stop screen recording
|
|
if: always()
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: stop
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Remux recording for browser playback
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -f "${{ runner.temp }}/e2e-logs/recording.mkv" ]; then
|
|
ffmpeg -y -hide_banner -loglevel error -i "${{ runner.temp }}/e2e-logs/recording.mkv" \
|
|
-c copy "${{ runner.temp }}/e2e-logs/recording.mp4"
|
|
fi
|
|
|
|
# Artifact names cannot contain '/'; install-ref may be a full ref.
|
|
- name: Build artifact name
|
|
id: artifact
|
|
if: always()
|
|
run: |
|
|
echo "name=install-e2e-logs-${{ inputs.leg-id }}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Upload logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ steps.artifact.outputs.name }}
|
|
path: ${{ runner.temp }}/e2e-logs
|
|
retention-days: 14
|
|
if-no-files-found: ignore
|
|
|
|
# ---- arm 3: the real signed bundle pair, in-app update ------------------
|
|
# Runs ONLY when the caller passes a bundle-manifest-url: the parent's
|
|
# matrix job owns resolving + pinning the OLD/NEW signed release pair.
|
|
# The driver itself re-guards on Darwin + GITHUB_ACTIONS and refuses to
|
|
# run anywhere native coverage would be fake.
|
|
bundled-e2e:
|
|
name: packaged bundle (open-app-update)
|
|
if: inputs.install-method == 'packaged-app' && inputs.update-method == 'open-app-update'
|
|
runs-on: ${{ inputs.bundle-arch == 'x64' && 'macos-15-intel' || 'macos-15' }}
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
|
|
- name: Set up driver tools only
|
|
uses: ./.github/actions/setup-pm
|
|
with:
|
|
toolchain: all
|
|
packages: ffmpeg
|
|
cache: false
|
|
cache-python: false
|
|
cache-node: false
|
|
|
|
- name: Install locked chat driver dependencies
|
|
shell: bash
|
|
run: npm ci --workspace tests-js --include-workspace-root --omit=dev --ignore-scripts --no-audit --no-fund
|
|
|
|
- name: Start screen recording
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: start
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Install OLD signed bundle (${{ inputs.install-ref }})
|
|
run: |
|
|
set -euo pipefail
|
|
bash tests/install/macos-bundled-e2e.sh --phase install \
|
|
--manifest-url "$BUNDLE_MANIFEST" --arch "$BUNDLE_ARCH"
|
|
env:
|
|
BUNDLE_MANIFEST: ${{ inputs.bundle-manifest-url }}
|
|
BUNDLE_MANIFEST_SHA256: ${{ inputs.bundle-manifest-sha256 }}
|
|
BUNDLE_ARCH: ${{ inputs.bundle-arch }}
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Update to NEW via About -> Update now (${{ inputs.bundle-arch }})
|
|
run: |
|
|
set -euo pipefail
|
|
bash tests/install/macos-bundled-e2e.sh --phase update \
|
|
--manifest-url "$BUNDLE_MANIFEST" --arch "$BUNDLE_ARCH"
|
|
env:
|
|
BUNDLE_MANIFEST: ${{ inputs.bundle-manifest-url }}
|
|
BUNDLE_MANIFEST_SHA256: ${{ inputs.bundle-manifest-sha256 }}
|
|
BUNDLE_ARCH: ${{ inputs.bundle-arch }}
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
- name: Stop screen recording
|
|
if: always()
|
|
uses: ./.github/actions/e2e-screen-record
|
|
with:
|
|
mode: stop
|
|
output: ${{ runner.temp }}/e2e-logs/recording.mkv
|
|
|
|
- name: Remux recording for browser playback
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -f "${{ runner.temp }}/e2e-logs/recording.mkv" ]; then
|
|
ffmpeg -y -hide_banner -loglevel error -i "${{ runner.temp }}/e2e-logs/recording.mkv" \
|
|
-c copy "${{ runner.temp }}/e2e-logs/recording.mp4"
|
|
fi
|
|
|
|
- name: Build artifact name
|
|
id: artifact
|
|
if: always()
|
|
run: |
|
|
echo "name=install-e2e-logs-${{ inputs.leg-id }}-bundle-${{ inputs.bundle-arch }}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Upload logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ steps.artifact.outputs.name }}
|
|
path: ${{ runner.temp }}/e2e-logs
|
|
retention-days: 14
|
|
if-no-files-found: ignore
|