import { readFileSync } from 'node:fs' import { join, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { parse } from 'yaml' const projectDir = resolve(import.meta.dirname, '../..') const prWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8')) const e2eWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/e2e.yml'), 'utf8')) const filterStep = prWorkflow.jobs['e2e-paths'].steps.find( (step) => step.name === 'Filter changed E2E specs' ) const verifyStep = prWorkflow.jobs.verify.steps.find( (step) => step.name === 'Require successful checks' ) describe('PR E2E gate contract', () => { it('keeps E2E advisory while the suite is red on main', () => { // Why: pin the deliberate choice so it reads as intentional rather than as // the "forgot to wire the gate" bug this file originally caught. Gating on a // suite that fails every scheduled run would block the PRs that fix it. // Flipping to blocking means updating this expectation too — see the comment // on verify's Require-successful-checks step for the exact wiring. expect(prWorkflow.jobs.verify.needs).not.toContain('e2e') expect(verifyStep.env.E2E).toBeUndefined() expect(verifyStep.run).not.toContain('$E2E') }) it('passes only changed specs to the reusable E2E workflow', () => { // Why: without this the job could lose its filter and run on every PR — the // cost the path filter exists to avoid — while the gate assertions above // stay green. expect(prWorkflow.jobs.e2e.needs).toBe('e2e-paths') expect(prWorkflow.jobs.e2e.if).toBe("needs.e2e-paths.outputs.should_run == 'true'") expect(prWorkflow.jobs['e2e-paths'].outputs.should_run).toBe( '${{ steps.filter.outputs.should_run }}' ) expect(prWorkflow.jobs['e2e-paths'].outputs.test_files).toBe( '${{ steps.filter.outputs.test_files }}' ) expect(prWorkflow.jobs.e2e.with.test_files).toBe('${{ needs.e2e-paths.outputs.test_files }}') }) it('enforces every job verify depends on', () => { // Why: derive from verify.needs rather than hardcoding, so adding a required // job without adding it to the strict loop fails here instead of silently // leaving that job unenforced. This is what caught GIT_COMPATIBILITY and // SHELL_CONTRACTS being absent from an earlier hardcoded list. const strictLoop = verifyStep.run.slice(0, verifyStep.run.indexOf('done')) for (const job of prWorkflow.jobs.verify.needs) { const envVar = job.toUpperCase() expect(verifyStep.env[envVar]).toBe(`\${{ needs.${job}.result }}`) expect(strictLoop).toContain(`"$${envVar}"`) } }) it('selects modified Playwright specs without running deleted tests', () => { expect(filterStep.run).toContain('--diff-filter=AMCR') expect(filterStep.run).toContain("'^tests/e2e/.*\\.spec\\.ts$'") expect(filterStep.run).not.toContain('tests/playwright\\.') }) it('uses one runner for changed specs and keeps full runs sharded', () => { expect(e2eWorkflow.jobs.e2e.if).toBe("inputs.test_files == ''") expect(e2eWorkflow.jobs['changed-e2e'].if).toBe("inputs.test_files != ''") expect(e2eWorkflow.jobs['changed-e2e'].strategy).toBeUndefined() expect(e2eWorkflow.jobs['ssh-docker-watcher-isolation'].if).toBe("inputs.test_files == ''") const changedRun = e2eWorkflow.jobs['changed-e2e'].steps.find( (step) => step.name === 'Run changed E2E specs' ) expect(changedRun.env.TEST_FILES_JSON).toBe('${{ inputs.test_files }}') expect(changedRun.run).toContain('pnpm run test:e2e "${TEST_FILES[@]}" --workers=1') }) it('keeps dedicated E2E workflows out of pull request CI', () => { const dedicatedWorkflows = [ 'golden-e2e-experiment.yml', 'linux-wayland-gpu-sandbox.yml', 'terminal-ime-e2e.yml', 'win-crash-survival-e2e.yml', 'windows-terminal-restart-e2e.yml' ] for (const file of dedicatedWorkflows) { const workflow = parse(readFileSync(join(projectDir, '.github/workflows', file), 'utf8')) expect(workflow.on.pull_request, file).toBeUndefined() } }) it('scopes detection to the PR range so base drift cannot false-trigger', () => { expect(filterStep.run).toContain('--merge-base "$BASE" "$HEAD"') expect(filterStep.run).toContain('set -euo pipefail') }) })