ci: simplify and de-flake the test suite (#4868)

* chore(ci): audit packaging costs and repeated test execution

Read-only audit: Linux packaging and smokes consume 12-14 minutes; all three CLI shards repeat the 294-test source suite and build the server twice. Preserve the 17 required contexts, move packaging to main, run CLI source tests once, and consolidate repeated E2E setup into journeys. Historical logs and the complete audit were reported outside the repository.

* ci: move packaging to main and remove repeated CLI suites

PR runners were rebuilding distribution artifacts and executing the entire CLI source suite on all three E2E shards. Keep packaging coverage after merge, let the local E2E runner own its dependency build, and execute source tests once in an existing required check. Root typecheck already validates plugin examples.

* test(ci): consolidate journeys and remove repeated startup work

Run user journeys with shared setup, retain one Hub CLI contract, and weight the existing Playwright shards using observed costs. Remove implementation-only and ad-hoc measurement checks. Await root-watch closure and publish creation failure commands atomically so teardown and failure injection have completion boundaries.

* test(hub): remove incidental observation and speculative shutdown changes

Hub control RPCs need a connected client without an agent observation. Preserve the failed-create subscription contract and remove product shutdown changes whose Windows cleanup cause was not reproduced.

* fix(client): clear the scheduled retry when a connect attempt opens a transport

An explicit connect() during reconnect backoff opened a new transport but
left the scheduled retry armed. When it fired on the healthy connection it
disposed the transport and reconnected, dropping requests in flight. This
is why owned-subscriptions-old-daemon could still fail after #4858 added
the explicit reconnect. attemptConnect now owns the rule ensureConnected
already applied; the new unit test fails on the old client.

* test(e2e): keep the completed-block, font-readiness, and live-page guards inside journeys

- agent-stream-ui: the auto-scroll journey anchors on the completed intro
  block by content and requires its Markdown root to stay the same mounted
  node while later blocks stream (#1989 guard, reshaped off the tail block).
- changes-pane: the code-typography journey holds font loads and requires
  no diff geometry before they resolve, then applies the font change.
- agent-timeline-pagination: the live-output page load runs against a
  thirty-minute mock stream and gates the release on the running turn, so
  the status assertion no longer races a ten-second turn.

* test(desktop): install the Electron binary once before the unit suite

Electron 44 downloads its binary on the first Node require("electron")
instead of at npm ci. Vitest workers importing electron-log/main raced
that download and extraction (File exists on dist/electron, then
"Electron failed to install correctly" in a sibling worker) on both
Ubuntu and Windows desktop-tests jobs. Run the package's idempotent
install-electron in one process before vitest forks.

* ci(desktop-packages): pin actions and smoke packaging-path pull requests

Pin checkout, setup-node, and upload-artifact to full commit SHAs. Keep
packaging off ordinary PR CI, but run the Linux package smoke on pull
requests that touch packages/desktop, nix, or the workflow so a packaging
or sandbox regression cannot land on main unchecked. The desktop release
workflow already builds and smokes its own artifacts on all platforms.
This commit is contained in:
Mohamed Boudra
2026-09-15 13:26:41 +02:00
committed by GitHub
parent 0474c3e0af
commit d8dd189b94
38 changed files with 621 additions and 2288 deletions
+13 -62
View File
@@ -201,7 +201,7 @@ jobs:
- name: Install virtual display
if: ${{ runner.os == 'Linux' }}
run: sudo apt-get update && sudo apt-get install -y xvfb xauth rpm xdotool
run: sudo apt-get update && sudo apt-get install -y xvfb xauth xdotool
- name: Install Playwright browser
if: ${{ runner.os == 'Linux' }}
@@ -249,59 +249,6 @@ jobs:
if-no-files-found: ignore
retention-days: 7
- name: Build Linux desktop artifacts
if: ${{ runner.os == 'Linux' }}
# Smoke package contents and installation without spending minutes on xz compression.
run: |
printf '{"extends":"%s/packages/desktop/electron-builder.yml","deb":{"compression":"gz"},"rpm":{"fpm":["--rpm-compression=gzip"]}}' "$GITHUB_WORKSPACE" > "$RUNNER_TEMP/desktop-smoke-builder.json"
npm run build:desktop -- --publish never --linux --x64 --config "$RUNNER_TEMP/desktop-smoke-builder.json"
env:
EP_GH_IGNORE_TIME: true
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke
- name: Smoke installed artifacts with restricted user namespaces
if: ${{ runner.os == 'Linux' }}
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
if unshare --user --map-root-user --net true; then
echo "Expected the launching user's namespace probe to be denied"
exit 1
fi
sudo apt-get install -y ./packages/desktop/release/*.deb
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release disabled
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/restricted
- name: Smoke sandbox-preserving portable launches
if: ${{ runner.os == 'Linux' }}
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
unshare --user --map-root-user --net true
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release enabled
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/userns
- name: Smoke installed RPM with restricted user namespaces
if: ${{ runner.os == 'Linux' }}
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
sudo dpkg --remove paseo
# Ubuntu supplies the runtime libraries under Debian package names.
# RPM still performs the real payload installation and postinstall.
sudo rpm --install --nodeps packages/desktop/release/*.rpm
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release disabled --installed-only
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/rpm
- name: Upload packaged smoke diagnostics
uses: actions/upload-artifact@v4
if: ${{ !cancelled() && runner.os == 'Linux' }}
with:
name: desktop-packaged-smoke-linux-x64
path: ${{ runner.temp }}/desktop-smoke
if-no-files-found: ignore
retention-days: 7
desktop-tests-windows:
name: desktop-tests (windows-latest)
needs: changes
@@ -363,9 +310,6 @@ jobs:
- name: Run plugin SDK tests
run: npm run test --workspace=@getpaseo/plugin
- name: Typecheck plugin examples
run: npm run typecheck --workspace=@getpaseo/plugin
- name: Run protocol tests
run: npm run test --workspace=@getpaseo/protocol
@@ -414,6 +358,9 @@ jobs:
run: npm run test:e2e --workspace=@getpaseo/app -- --shard=${{ env.PLAYWRIGHT_SHARD }}
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Weight file groups by observed duration; raw test counts overload
# early agent/Changes specs. Required check names remain unchanged.
PWTEST_SHARD_WEIGHTS: "111:172:137:160"
- name: Upload test artifacts
uses: actions/upload-artifact@v4
@@ -499,14 +446,18 @@ jobs:
- name: Install dependencies
run: node scripts/npm-retry.mjs ci
- name: Build server stack
run: npm run build:server
- name: Install agent CLIs for provider tests
run: node scripts/npm-retry.mjs install -g @anthropic-ai/claude-code @openai/codex@0.105.0 opencode-ai
- name: Run CLI tests
run: npm run test --workspace=@getpaseo/cli
# The local runner builds its dependencies and shards its own E2E files.
- name: Run CLI E2E tests
run: npm run test:local --workspace=@getpaseo/cli
# Source tests do not read PASEO_CLI_TEST_SHARD. Run them once, on the
# shortest E2E shard, after its runner has built the server stack.
- name: Run CLI source tests
if: ${{ env.PASEO_CLI_TEST_SHARD == '3' }}
run: npm run test:unit --workspace=@getpaseo/cli
cli-tests-2:
name: cli-tests (shard 2/3)
+90
View File
@@ -0,0 +1,90 @@
name: Desktop Packages
on:
push:
branches: [main]
# Packaging leaves ordinary PR CI, but a PR that changes what gets packaged
# or how it is smoked still proves the real artifacts before merge.
pull_request:
branches: [main]
paths:
- "packages/desktop/**"
- "nix/**"
- ".github/workflows/desktop-packages.yml"
concurrency:
group: desktop-packages-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
env:
ONNXRUNTIME_NODE_INSTALL: skip
jobs:
linux:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: node scripts/npm-retry.mjs ci
- name: Install package smoke dependencies
run: sudo apt-get update && sudo apt-get install -y xvfb xauth rpm
- name: Build Linux desktop artifacts
# Smoke package contents and installation without spending minutes on xz compression.
run: |
printf '{"extends":"%s/packages/desktop/electron-builder.yml","deb":{"compression":"gz"},"rpm":{"fpm":["--rpm-compression=gzip"]}}' "$GITHUB_WORKSPACE" > "$RUNNER_TEMP/desktop-smoke-builder.json"
npm run build:desktop -- --publish never --linux --x64 --config "$RUNNER_TEMP/desktop-smoke-builder.json"
env:
EP_GH_IGNORE_TIME: true
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke
- name: Smoke installed artifacts with restricted user namespaces
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
if unshare --user --map-root-user --net true; then
echo "Expected the launching user's namespace probe to be denied"
exit 1
fi
sudo apt-get install -y ./packages/desktop/release/*.deb
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release disabled
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/restricted
- name: Smoke sandbox-preserving portable launches
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
unshare --user --map-root-user --net true
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release enabled
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/userns
- name: Smoke installed RPM with restricted user namespaces
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1
sudo dpkg --remove paseo
# Ubuntu supplies the runtime libraries under Debian package names.
# RPM still performs the real payload installation and postinstall.
sudo rpm --install --nodeps packages/desktop/release/*.rpm
node packages/desktop/e2e/linux-artifact-smoke.js packages/desktop/release disabled --installed-only
env:
PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke/rpm
- name: Upload packaged smoke diagnostics
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: ${{ !cancelled() }}
with:
name: desktop-packaged-smoke-linux-x64
path: ${{ runner.temp }}/desktop-smoke
if-no-files-found: ignore
retention-days: 7
+1 -22
View File
@@ -1,27 +1,6 @@
name: Docker
on:
pull_request:
branches: [main]
paths:
- "docker/**"
- ".dockerignore"
- ".github/workflows/docker.yml"
- "package.json"
- "package-lock.json"
- "patches/**"
- "scripts/**"
- "tsconfig.json"
- "tsconfig.base.json"
- "packages/app/**"
- "packages/cli/**"
- "packages/client/**"
- "packages/expo-two-way-audio/**"
- "packages/highlight/**"
- "packages/plugin/**"
- "packages/protocol/**"
- "packages/relay/**"
- "packages/server/**"
push:
branches: [main]
tags:
@@ -51,7 +30,7 @@ on:
concurrency:
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
cancel-in-progress: false
env:
REGISTRY: ghcr.io
+1 -24
View File
@@ -1,27 +1,8 @@
name: Nix
on:
pull_request:
push:
branches: [main]
paths:
- "nix/**"
- "flake.nix"
- "flake.lock"
- "package.json"
- "package-lock.json"
- "packages/app/**"
- "packages/expo-two-way-audio/**"
- "packages/highlight/**"
- "packages/plugin/**"
- "packages/protocol/**"
- "packages/client/**"
- "packages/server/**"
- "packages/relay/**"
- "packages/cli/**"
- "scripts/build-daemon-web-ui.mjs"
- "scripts/update-nix.sh"
- "scripts/fix-lockfile.mjs"
- ".github/workflows/nix.yml"
permissions:
contents: read
@@ -31,8 +12,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v4
with:
@@ -110,8 +89,6 @@ jobs:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: cachix/install-nix-action@v31
with: