diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 92e33a18d19..c23aa0cb679 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -55,9 +55,13 @@ jobs: env: VITE_EXPOSE_STORE: 'true' run: | - npx electron-vite build --mode e2e - pnpm run build:web-from-renderer - pnpm run build:relay + status=0 + pnpm run build:relay & + relay_pid=$! + npx electron-vite build --mode e2e || status=1 + pnpm run build:web-from-renderer || status=1 + wait "$relay_pid" || status=1 + exit "$status" - name: Upload E2E build output uses: actions/upload-artifact@v7 @@ -71,9 +75,29 @@ jobs: retention-days: 1 if-no-files-found: error + # Build Electron-native dependencies once per workflow. Consumer shards restore + # this immutable cache instead of compiling the same ABI concurrently. + prepare-native-cache: + name: prepare Electron native cache + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Install native build tools + run: sudo apt-get update && sudo apt-get install -y build-essential python3 + + - uses: ./.github/actions/install-node-dependencies + with: + native-runtime: electron + e2e: name: e2e ${{ matrix.shard_name }} - needs: build + needs: [build, prepare-native-cache] if: inputs.test_files == '' runs-on: ubuntu-latest timeout-minutes: 30 @@ -159,7 +183,7 @@ jobs: changed-e2e: name: changed e2e specs - needs: build + needs: [build, prepare-native-cache] if: inputs.test_files != '' runs-on: ubuntu-latest # Why 45: pr.yml now maps SSH source edits onto Docker-backed specs, so this lane can @@ -227,8 +251,7 @@ jobs: ssh-docker-watcher-isolation: name: ssh docker watcher isolation - needs: build - # Why ssh_source_changed first: this lane used to trigger on SSH source only as a side + needs: [build, prepare-native-cache] # effect of one route listing a startup-readiness spec — pruning that spec would have # silently retired the whole lane. The signal is now derived from the SSH routes directly. # The two spec clauses stay for their honest purpose: changed-e2e hands these specs to this diff --git a/config/scripts/release-cut-token-permissions.test.mjs b/config/scripts/release-cut-token-permissions.test.mjs index 24f138723c9..2478087ba8a 100644 --- a/config/scripts/release-cut-token-permissions.test.mjs +++ b/config/scripts/release-cut-token-permissions.test.mjs @@ -8,6 +8,7 @@ const EXPECTED_MATRIX = { '.github/workflows/e2e.yml#build': { contents: 'read' }, '.github/workflows/e2e.yml#changed-e2e': { contents: 'read' }, '.github/workflows/e2e.yml#e2e': { contents: 'read' }, + '.github/workflows/e2e.yml#prepare-native-cache': { contents: 'read' }, '.github/workflows/e2e.yml#ssh-docker-watcher-isolation': { contents: 'read' }, '.github/workflows/homebrew-bump.yml#bump-cask': { contents: 'read' }, '.github/workflows/release-mac-build.yml#build-mac': { contents: 'write' }, diff --git a/config/scripts/release-e2e-dispatch-contract.test.mjs b/config/scripts/release-e2e-dispatch-contract.test.mjs index d48c7b3fa30..5fd4503d4e3 100644 --- a/config/scripts/release-e2e-dispatch-contract.test.mjs +++ b/config/scripts/release-e2e-dispatch-contract.test.mjs @@ -46,6 +46,28 @@ describe('release E2E dispatch contract', () => { expect(refInput.required).toBe(false) }) + it('overlaps the relay bundle with the Electron build', () => { + const buildStep = e2eWorkflow.jobs.build.steps.find((step) => step.name === 'Build E2E outputs') + + expect(buildStep.run).toContain('pnpm run build:relay &') + expect(buildStep.run).toContain('relay_pid=$!') + expect(buildStep.run).toContain('wait "$relay_pid"') + expect(buildStep.run).toContain('pnpm run build:web-from-renderer') + }) + + it('primes the Electron native cache before every E2E consumer', () => { + const primer = e2eWorkflow.jobs['prepare-native-cache'] + expect(primer.steps).toBeDefined() + expect( + primer.steps.find((step) => step.uses === './.github/actions/install-node-dependencies').with + ).toEqual({ + 'native-runtime': 'electron' + }) + for (const jobName of ['e2e', 'changed-e2e', 'ssh-docker-watcher-isolation']) { + expect(e2eWorkflow.jobs[jobName].needs, jobName).toEqual(['build', 'prepare-native-cache']) + } + }) + it('includes the paired-runtime web client in the shared E2E build artifact', () => { const buildStep = e2eWorkflow.jobs.build.steps.find((step) => step.name === 'Build E2E outputs') @@ -71,7 +93,7 @@ describe('release E2E dispatch contract', () => { const downloadStep = job.steps.find((step) => step.name === 'Download E2E build output') const runStep = job.steps.find((step) => step.name === runStepName) - expect(job.needs).toBe('build') + expect(job.needs).toEqual(['build', 'prepare-native-cache']) expect(downloadStep.with.name).toBe('e2e-build-out') expect(downloadStep.with.path).toBe('out/') expect(runStep.run).toContain('ORCA_RELAY_PATH="$GITHUB_WORKSPACE/out/relay"')