diff --git a/.github/workflows/computer-e2e.yml b/.github/workflows/computer-e2e.yml index e6b16fab7a7..ff475405a5e 100644 --- a/.github/workflows/computer-e2e.yml +++ b/.github/workflows/computer-e2e.yml @@ -140,16 +140,6 @@ jobs: - name: Windows daemon workspace-close repro if: runner.os == 'Windows' run: node config/scripts/windows-daemon-workspace-close-repro.mjs - - if: runner.os == 'Linux' - env: - ORCA_COMPUTER_E2E: '1' - ACCESSIBILITY_ENABLED: '1' - run: xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts - - if: runner.os == 'Windows' - env: - ORCA_COMPUTER_E2E: '1' - run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts - mac-native-owner-smoke: if: github.event_name == 'pull_request' runs-on: macos-15 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ac5fd76bc87..5455d0b7fb8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,6 +13,10 @@ on: description: Ref to check out (defaults to the calling workflow's ref) required: false type: string + test_files: + description: JSON array of changed specs; empty runs the full suite + required: false + type: string workflow_dispatch: inputs: ref: @@ -84,6 +88,7 @@ jobs: e2e: name: e2e ${{ matrix.shard_name }} needs: build + if: inputs.test_files == '' runs-on: ubuntu-latest timeout-minutes: 30 strategy: @@ -183,9 +188,67 @@ jobs: retention-days: 7 if-no-files-found: ignore + changed-e2e: + name: changed e2e specs + needs: build + if: inputs.test_files != '' + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Install native build and headless UI tools + run: sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Use external node-gyp to avoid pnpm's bundled copy + run: | + npm install -g node-gyp@11.5.0 + echo "npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js" >> "$GITHUB_ENV" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Download E2E build output + uses: actions/download-artifact@v8 + with: + name: e2e-build-out + path: out/ + + - name: Run changed E2E specs + env: + TEST_FILES_JSON: ${{ inputs.test_files }} + run: | + mapfile -t TEST_FILES < <(jq -r '.[]' <<<"$TEST_FILES_JSON") + xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 \ + pnpm run test:e2e "${TEST_FILES[@]}" --workers=1 + + - name: Upload Playwright traces + if: failure() + uses: actions/upload-artifact@v7 + with: + name: playwright-traces-changed + path: test-results/ + retention-days: 7 + if-no-files-found: ignore + ssh-docker-watcher-isolation: name: ssh docker watcher isolation needs: build + if: inputs.test_files == '' runs-on: ubuntu-latest # Why 35: the terminal parking + retention specs below add two more # docker-rig tests capped at 240s each on top of the watcher isolation pair. diff --git a/.github/workflows/golden-e2e-experiment.yml b/.github/workflows/golden-e2e-experiment.yml index 7c302d8843f..89c1c86096f 100644 --- a/.github/workflows/golden-e2e-experiment.yml +++ b/.github/workflows/golden-e2e-experiment.yml @@ -1,18 +1,6 @@ name: Golden E2E Experiment on: - pull_request: - paths: - - '.github/workflows/golden-e2e-experiment.yml' - - 'package.json' - - 'tests/e2e/golden-core-flows.spec.ts' - - 'tests/e2e/fixtures/terminal-emoji-table.md' - - 'tests/e2e/helpers/**' - - 'tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts' - - 'tests/e2e/terminal-webgl-atlas-budget.spec.ts' - - 'config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch' - - 'src/renderer/src/components/sidebar/SidebarHeader.tsx' - - 'src/renderer/src/lib/pane-manager/**' workflow_dispatch: inputs: ref: @@ -44,9 +32,7 @@ jobs: - name: Checkout uses: actions/checkout@v6 with: - # Why: pull_request merge refs disappear after merge, but experiment - # reruns still need a stable commit to checkout. - ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.ref }} + ref: ${{ inputs.ref || github.ref }} - name: Install native build tools if: runner.os == 'Linux' diff --git a/.github/workflows/linux-wayland-gpu-sandbox.yml b/.github/workflows/linux-wayland-gpu-sandbox.yml index fc297326d14..dd893c6eae1 100644 --- a/.github/workflows/linux-wayland-gpu-sandbox.yml +++ b/.github/workflows/linux-wayland-gpu-sandbox.yml @@ -1,20 +1,6 @@ name: Linux Wayland GPU Sandbox on: - pull_request: - paths: - - .github/workflows/linux-wayland-gpu-sandbox.yml - - config/scripts/linux-wayland-renderer-diagnostics.mjs - - config/scripts/linux-wayland-terminal-exercise.mjs - - config/scripts/linux-wayland-validation-watchdog.mjs - - config/scripts/verify-linux-wayland-gpu-sandbox.mjs - - src/main/startup/configure-process.ts - - src/main/startup/configure-process.test.ts - - src/preload/api-types.ts - - src/preload/index.ts - - src/renderer/src/components/terminal-pane/pty-connection.ts - - src/renderer/src/lib/pane-manager/terminal-webgl-auto-policy.ts - - src/renderer/src/web/web-preload-api.ts workflow_dispatch: jobs: @@ -85,34 +71,6 @@ jobs: done test -S "$XDG_RUNTIME_DIR/wayland-1" - - name: Reproduce terminal input freeze without the workaround - id: reproduce_base - if: github.event_name == 'pull_request' - # Why: still collect fixed-path Wayland evidence when the base repro - # stops reproducing; the final gate below fails the job in that case. - continue-on-error: true - timeout-minutes: 10 - run: | - set -euo pipefail - git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}" - # Why: once the PR base includes this workaround, the partial - # checkout cannot reconstruct an unfixed Wayland negative control. - if git show "${{ github.event.pull_request.base.sha }}:src/main/startup/configure-process.ts" | grep -Eq "appendSwitch\\(['\"]disable-gpu-sandbox['\"]\\)"; then - echo "The base build already contains the Linux Wayland GPU sandbox workaround; skipping unfixed-path reproduction." - exit 0 - fi - # Why: keep the new verifier scripts, but run them against the - # unfixed production terminal/GPU path instead of a hybrid checkout. - git checkout "${{ github.event.pull_request.base.sha }}" -- \ - src/main/startup/configure-process.ts \ - src/preload/api-types.ts \ - src/preload/index.ts \ - src/renderer/src/components/terminal-pane/pty-connection.ts \ - src/renderer/src/lib/pane-manager/terminal-webgl-auto-policy.ts \ - src/renderer/src/web/web-preload-api.ts - rm -rf out - node config/scripts/verify-linux-wayland-gpu-sandbox.mjs --mode=expect-repro - - name: Verify terminal input under Wayland GPU sandbox workaround if: ${{ !cancelled() }} timeout-minutes: 10 @@ -122,12 +80,6 @@ jobs: rm -rf out node config/scripts/verify-linux-wayland-gpu-sandbox.mjs - - name: Require base reproduction - if: ${{ github.event_name == 'pull_request' && steps.reproduce_base.outcome != 'success' }} - run: | - echo "The unfixed base build did not reproduce the Wayland terminal input failure." - exit 1 - - name: Upload Weston log if: always() uses: actions/upload-artifact@v7 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 261c8d18adb..9ae0aa2a659 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -325,11 +325,10 @@ jobs: - name: Smoke packaged CLI run: node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/win-unpacked - # Why: regression specs under tests/e2e/** used to merge green without ever - # running — e2e.yml only fired on schedule/release (#10518). Path-filter so - # ordinary PRs stay light; any E2E suite change still gets a full shard run. + # Why: PR E2E is advisory and only validates changed specs; scheduled and + # release runs retain full-suite coverage. e2e-paths: - name: detect e2e path changes + name: detect changed e2e specs runs-on: ubuntu-latest if: github.event.pull_request.draft != true # Why: detector only needs to read the checkout; do not inherit repo defaults. @@ -337,6 +336,7 @@ jobs: contents: read outputs: should_run: ${{ steps.filter.outputs.should_run }} + test_files: ${{ steps.filter.outputs.test_files }} steps: - name: Checkout uses: actions/checkout@v6 @@ -344,25 +344,22 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Filter E2E-relevant paths + - name: Filter changed E2E specs id: filter run: | set -euo pipefail BASE="${{ github.event.pull_request.base.sha }}" HEAD="${{ github.event.pull_request.head.sha }}" - # Why: capture first so a failed git diff does not look like "no matches" - # (pipeline status in `if` is not aborted by set -e). Merge-base limits the - # list to files this PR introduced, not base-branch drift. - CHANGED="$(git diff --name-only --merge-base "$BASE" "$HEAD")" - # Why: tests/playwright.config.ts sits beside tests/e2e/, not inside it, so - # it needs its own pattern — a bare `playwright.` prefix matches no tracked - # file and would silently skip E2E when the runner config changes. - if printf '%s\n' "$CHANGED" | grep -E '^(tests/e2e/|tests/playwright\.|\.github/workflows/e2e\.yml$)' >/dev/null; then + CHANGED="$(git diff --name-only --diff-filter=AMCR --merge-base "$BASE" "$HEAD")" + TEST_FILES="$(printf '%s\n' "$CHANGED" | grep -E '^tests/e2e/.*\.spec\.ts$' || true)" + TEST_FILES_JSON="$(printf '%s\n' "$TEST_FILES" | jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))')" + echo "test_files=$TEST_FILES_JSON" >> "$GITHUB_OUTPUT" + if [ "$TEST_FILES_JSON" != '[]' ]; then echo "should_run=true" >> "$GITHUB_OUTPUT" - echo "E2E path changes detected" + echo "Changed E2E specs: $TEST_FILES_JSON" else echo "should_run=false" >> "$GITHUB_OUTPUT" - echo "No E2E path changes" + echo "No changed E2E specs" fi e2e: @@ -373,6 +370,8 @@ jobs: permissions: contents: read uses: ./.github/workflows/e2e.yml + with: + test_files: ${{ needs.e2e-paths.outputs.test_files }} verify: if: always() diff --git a/.github/workflows/terminal-ime-e2e.yml b/.github/workflows/terminal-ime-e2e.yml index 7cef7d06cc5..31318bdff12 100644 --- a/.github/workflows/terminal-ime-e2e.yml +++ b/.github/workflows/terminal-ime-e2e.yml @@ -1,26 +1,6 @@ name: Terminal IME E2E on: - pull_request: - paths: - - '.github/workflows/terminal-ime-e2e.yml' - - 'config/patches/@xterm__xterm@6.1.0-beta.287.patch' - - 'config/scripts/run-terminal-ibus-hangul-e2e.mjs' - - 'config/scripts/terminal-ime-e2e-workflow.test.mjs' - - 'package.json' - - 'pnpm-lock.yaml' - - 'src/renderer/src/components/terminal-pane/keyboard-handlers.ts' - - 'src/renderer/src/components/terminal-pane/keyboard-handlers-ime.test.tsx' - - 'src/renderer/src/components/terminal-pane/pty-connection.ts' - - 'src/renderer/src/components/terminal-pane/pty-connection.test.ts' - - 'src/renderer/src/components/terminal-pane/terminal-ime-*' - - 'src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts' - - 'src/renderer/src/components/terminal-pane/xterm-bypass-policy.ts' - - 'src/renderer/src/components/terminal-pane/xterm-bypass-policy.test.ts' - - 'tests/e2e/chinese-ime-chat-input-repro.spec.ts' - - 'tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts' - - 'tests/e2e/terminal-ibus-hangul-native.spec.ts' - - 'tests/e2e/terminal-ime-*.ts' workflow_dispatch: schedule: - cron: '30 9 * * *' diff --git a/.github/workflows/win-crash-survival-e2e.yml b/.github/workflows/win-crash-survival-e2e.yml index 7dc5f0e219b..cd7e0245136 100644 --- a/.github/workflows/win-crash-survival-e2e.yml +++ b/.github/workflows/win-crash-survival-e2e.yml @@ -6,56 +6,10 @@ name: Windows Crash-Survival E2E # silent-installs it, opens a terminal, force-kills ONLY the app main (no # tree-kill), and asserts the daemon + shell stay alive, a relaunch adopts the # same daemon and shell, and post-crash input causes no pwsh FailFast. Targeted -# pull requests keep it as a durable regression gate. A CI runner is the only +# manual runs keep it as a durable regression proof. A CI runner is the only # safe place to install — see the relocation post-mortem. on: - pull_request: - types: - - opened - - synchronize - - reopened - - ready_for_review - paths: - - '.github/workflows/win-crash-survival-e2e.yml' - - 'package.json' - - 'pnpm-lock.yaml' - - 'pnpm-workspace.yaml' - - 'electron.vite.config.ts' - - 'build-plugins/**' - - 'config/electron-builder.config.cjs' - - 'config/patches/**' - - 'config/scripts/ensure-native-runtime.mjs' - - 'config/scripts/rebuild-native-deps.mjs' - - 'config/scripts/verify-packaged-plugin-resources.cjs' - - 'native/**' - # Byte-hashed at package time, and CRLF-sensitive on Windows. - - '.gitattributes' - - 'resources/plugins/**' - - 'resources/win32/**' - - 'src/main/daemon/**' - - 'src/main/index.ts' - - 'src/main/ipc/pty*.ts' - - 'src/main/persistence.ts' - - 'src/main/providers/**' - - 'src/main/pty/**' - - 'src/main/startup/first-window-startup-services.ts' - - 'src/main/window/attach-main-window-services.ts' - - 'src/preload/**' - - 'src/renderer/src/App.tsx' - - 'src/renderer/src/components/terminal-pane/**' - - 'src/renderer/src/hooks/useIpcEvents.ts' - - 'src/renderer/src/lib/pane-manager/**' - - 'src/renderer/src/lib/session-write-subscriber.ts' - - 'src/renderer/src/lib/workspace-session-host-persistence.ts' - - 'src/renderer/src/store/slices/terminals.ts' - - 'src/shared/pty-session-id-format.ts' - - 'tools/win-crash-survival-e2e/**' - - 'tools/win-update-e2e/**' - # Why: source tests and benchmarks do not change the packaged artifact; - # their own verify jobs cover them without spending a Windows build slot. - - '!src/**/*.test.*' - - '!src/**/*.bench.*' workflow_dispatch: inputs: expect: diff --git a/.github/workflows/windows-terminal-restart-e2e.yml b/.github/workflows/windows-terminal-restart-e2e.yml index 898f81bc8eb..6a47b10a1a4 100644 --- a/.github/workflows/windows-terminal-restart-e2e.yml +++ b/.github/workflows/windows-terminal-restart-e2e.yml @@ -1,41 +1,6 @@ name: Windows terminal restart E2E on: - pull_request: - types: - - opened - - synchronize - - reopened - - ready_for_review - paths: - - '.github/workflows/windows-terminal-restart-e2e.yml' - - 'package.json' - - 'pnpm-lock.yaml' - - 'electron.vite.config.ts' - - 'config/patches/**' - - 'config/scripts/ensure-native-runtime.mjs' - - 'config/scripts/rebuild-native-deps.mjs' - - 'tests/playwright.config.ts' - - 'tests/e2e/global-setup.ts' - - 'tests/e2e/global-teardown.ts' - - 'tests/e2e/helpers/**' - - 'tests/e2e/restart-restore-terminal-input.spec.ts' - - 'tests/e2e/restored-terminal-input-readiness.unit.test.ts' - - 'tests/e2e/terminal-probe-input-sequence.ts' - - 'tests/e2e/terminal-probe-input-sequence.unit.test.ts' - - 'tests/e2e/terminal-restart-persistence.spec.ts' - - 'src/main/daemon/**' - - 'src/main/ipc/pty*.ts' - - 'src/main/providers/**' - - 'src/main/pty/**' - - 'src/main/pty-descendant-termination.ts' - - 'src/main/windows-process-tree-kill.ts' - - 'src/main/windows-pty-root-identity.ts' - - 'src/preload/**' - - 'src/renderer/src/components/terminal-pane/**' - - 'src/renderer/src/lib/pane-manager/**' - - 'src/shared/process-table-snapshot.ts' - - 'src/shared/pty-session-id-format.ts' workflow_dispatch: inputs: ref: diff --git a/config/scripts/computer-e2e-workflow.test.mjs b/config/scripts/computer-e2e-workflow.test.mjs index a4230668bc7..21e9df0fc6c 100644 --- a/config/scripts/computer-e2e-workflow.test.mjs +++ b/config/scripts/computer-e2e-workflow.test.mjs @@ -225,20 +225,15 @@ describe('computer-use e2e workflow', () => { ) }) - it('runs Linux computer-use e2e in the PR native-smoke job under Xvfb', () => { + it('does not run computer-use e2e in PR smoke jobs', () => { const workflow = parse( readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8') ) const nativeSmokeRuns = workflow.jobs['native-smoke'].steps .map((step) => step.run) .filter((run) => typeof run === 'string') - const installRun = nativeSmokeRuns.find((run) => run.includes('apt-get install')) - expect(installRun).toContain('gedit') - expect(installRun).toContain('xvfb') - expect(nativeSmokeRuns).toContain( - 'xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts' - ) + expect(nativeSmokeRuns.join('\n')).not.toContain('test:e2e:computer') }) it('builds Electron main output before every computer-use e2e run', () => { @@ -267,7 +262,7 @@ describe('computer-use e2e workflow', () => { } }) - it('runs core Windows computer-use e2e in the PR native-smoke job', () => { + it('keeps computer-use e2e in scheduled jobs only', () => { const workflow = parse( readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8') ) @@ -283,9 +278,8 @@ describe('computer-use e2e workflow', () => { .filter((run) => typeof run === 'string') ] - expect(nativeSmokeRuns).toContain( - 'pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts' - ) + expect(nativeSmokeRuns.join('\n')).not.toContain('test:e2e:computer') + expect(allRuns.join('\n')).toContain('test:e2e:computer') expect(allRuns.join('\n')).not.toContain('test:e2e:computer -- --reporter') }) diff --git a/config/scripts/package-electron-runtime-contract.test.mjs b/config/scripts/package-electron-runtime-contract.test.mjs index 516aac21a23..2c8dc4555c0 100644 --- a/config/scripts/package-electron-runtime-contract.test.mjs +++ b/config/scripts/package-electron-runtime-contract.test.mjs @@ -487,7 +487,7 @@ describe('Electron runtime package contract', () => { expect(uploadStep.with.path).toBe('${{ env.ORCA_E2E_TERMINAL_PERF_REPORT_PATH }}') }) - it('keeps terminal rendering regressions in the fast golden E2E gate', () => { + it('keeps terminal rendering regressions in the manual golden E2E workflow', () => { const packageScripts = packageJson.scripts const goldenWorkflow = parse( readFileSync(join(projectDir, '.github/workflows/golden-e2e-experiment.yml'), 'utf8') @@ -511,7 +511,6 @@ describe('Electron runtime package contract', () => { return steps.find((step) => step.name === `Run golden E2E tests on ${label}`) }) - const pullRequestPaths = goldenWorkflow.on.pull_request.paths const releaseGoldenJob = releaseWorkflow.jobs['terminal-rendering-golden'] const releaseEvidenceJob = releaseWorkflow.jobs['terminal-rendering-release-evidence'] const releaseBuildNeeds = releaseWorkflow.jobs.build.needs @@ -540,11 +539,8 @@ describe('Electron runtime package contract', () => { for (const runStep of goldenRunSteps) { expect(runStep?.run).toContain('pnpm run test:e2e:terminal-rendering-golden') } - expect(pullRequestPaths).toContain('tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts') - expect(pullRequestPaths).toContain('tests/e2e/terminal-webgl-atlas-budget.spec.ts') - expect(pullRequestPaths).toContain('config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch') - expect(pullRequestPaths).toContain('tests/e2e/fixtures/terminal-emoji-table.md') - expect(pullRequestPaths).toContain('src/renderer/src/lib/pane-manager/**') + expect(goldenWorkflow.on.pull_request).toBeUndefined() + expect(goldenWorkflow.on.workflow_dispatch).toBeDefined() expect(releaseBuildNeeds).not.toContain('terminal-rendering-golden') expect(releaseBuildNeeds).not.toContain('terminal-rendering-release-evidence') expect(publishReleaseNeeds).toContain('terminal-rendering-golden') diff --git a/config/scripts/pr-e2e-gate-contract.test.mjs b/config/scripts/pr-e2e-gate-contract.test.mjs index bcedb1b5aaa..860e70c892b 100644 --- a/config/scripts/pr-e2e-gate-contract.test.mjs +++ b/config/scripts/pr-e2e-gate-contract.test.mjs @@ -5,9 +5,10 @@ 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 E2E-relevant paths' + (step) => step.name === 'Filter changed E2E specs' ) const verifyStep = prWorkflow.jobs.verify.steps.find( (step) => step.name === 'Require successful checks' @@ -25,7 +26,7 @@ describe('PR E2E gate contract', () => { expect(verifyStep.run).not.toContain('$E2E') }) - it('runs E2E only when the detector says the PR touches E2E paths', () => { + 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. @@ -34,6 +35,10 @@ describe('PR E2E gate contract', () => { 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', () => { @@ -49,16 +54,41 @@ describe('PR E2E gate contract', () => { } }) - it('matches the Playwright config where it actually lives', () => { - // Why: the config is tests/playwright.config.ts, beside tests/e2e/ rather - // than inside it. A bare `playwright.` prefix matches no tracked file, so - // editing the runner config would silently skip E2E. - expect(filterStep.run).toContain('tests/playwright\\.') - expect(filterStep.run).not.toMatch(/\(\^?\|\|]tests\/e2e\/\|playwright\\\./) + 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('git diff --name-only --merge-base "$BASE" "$HEAD"') + expect(filterStep.run).toContain('--merge-base "$BASE" "$HEAD"') expect(filterStep.run).toContain('set -euo pipefail') }) }) diff --git a/config/scripts/terminal-ime-e2e-workflow.test.mjs b/config/scripts/terminal-ime-e2e-workflow.test.mjs index 859ba7f168b..96062ebe706 100644 --- a/config/scripts/terminal-ime-e2e-workflow.test.mjs +++ b/config/scripts/terminal-ime-e2e-workflow.test.mjs @@ -10,24 +10,10 @@ describe('terminal IME e2e workflow', () => { readFileSync(join(projectDir, '.github/workflows/terminal-ime-e2e.yml'), 'utf8') ) - it('runs for xterm patch and terminal IME regression changes', () => { - expect(workflow.on.pull_request.paths).toEqual( - expect.arrayContaining([ - 'config/patches/@xterm__xterm@6.1.0-beta.287.patch', - 'config/scripts/run-terminal-ibus-hangul-e2e.mjs', - 'src/renderer/src/components/terminal-pane/keyboard-handlers.ts', - 'src/renderer/src/components/terminal-pane/keyboard-handlers-ime.test.tsx', - 'src/renderer/src/components/terminal-pane/pty-connection.ts', - 'src/renderer/src/components/terminal-pane/pty-connection.test.ts', - 'src/renderer/src/components/terminal-pane/terminal-ime-*', - 'src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts', - 'src/renderer/src/components/terminal-pane/xterm-bypass-policy.ts', - 'src/renderer/src/components/terminal-pane/xterm-bypass-policy.test.ts', - 'tests/e2e/korean-ime-terminal-shift-enter-commit.spec.ts', - 'tests/e2e/terminal-ibus-hangul-native.spec.ts', - 'tests/e2e/terminal-ime-*.ts' - ]) - ) + it('runs only on schedule or manual dispatch', () => { + expect(workflow.on.pull_request).toBeUndefined() + expect(workflow.on.workflow_dispatch).toBeNull() + expect(workflow.on.schedule).toEqual([{ cron: '30 9 * * *' }]) }) it('installs native IBus Hangul and X11 input tools', () => { diff --git a/config/scripts/win-crash-survival-e2e.test.mjs b/config/scripts/win-crash-survival-e2e.test.mjs index c59f98ffb90..8ad77b9136b 100644 --- a/config/scripts/win-crash-survival-e2e.test.mjs +++ b/config/scripts/win-crash-survival-e2e.test.mjs @@ -13,20 +13,11 @@ import { closeApp, resolveElectronMainPid } from '../../tools/win-update-e2e/app import { isPidAlive } from '../../tools/win-update-e2e/daemon-processes.mjs' describe('win-crash-survival-e2e proof contracts', () => { - it('keeps the packaged proof wired as a targeted pull-request gate', () => { + it('keeps the packaged proof manually dispatchable without a PR trigger', () => { const workflow = readFileSync('.github/workflows/win-crash-survival-e2e.yml', 'utf8') - expect(workflow).toMatch(/^ pull_request:/m) + expect(workflow).not.toMatch(/^ pull_request:/m) + expect(workflow).toMatch(/^ workflow_dispatch:/m) expect(workflow).not.toMatch(/^ push:/m) - expect(workflow).toContain("- 'src/main/daemon/**'") - expect(workflow).toContain("- 'src/main/index.ts'") - expect(workflow).toContain("- 'src/main/ipc/pty*.ts'") - expect(workflow).toContain("- 'src/main/startup/first-window-startup-services.ts'") - expect(workflow).toContain("- 'src/main/window/attach-main-window-services.ts'") - expect(workflow).toContain("- 'src/preload/**'") - expect(workflow).toContain("- 'src/renderer/src/components/terminal-pane/**'") - expect(workflow).toContain("- 'src/renderer/src/store/slices/terminals.ts'") - expect(workflow).toContain("- '!src/**/*.test.*'") - expect(workflow).toContain("- '!src/**/*.bench.*'") expect(workflow).toContain('--expect "$env:EXPECT"') expect(workflow).toContain('exit $LASTEXITCODE') expect(workflow).toContain("'!config/**/*.test.*'")