Skip to content

Commit af5501b

Browse files
authored
improvement(desktop): Electron E2E for desktop tool lifetimes against a live local Sim (#8697)
* improvement(desktop): Electron E2E for desktop tool lifetimes against a live local Sim Runs the real Electron app against a local Sim (Postgres, Redis, realtime) with a recording proxy and a scripted stand-in worker, covering chat switches, Stop, sign-out, approval-gated and stopped claims, and the flag-off foreground round trip. The spec skips unless a live Sim is configured; the http-e2e job provides one. * improvement(desktop): use shared helpers, confirm Redis MONITOR, stage desktop build on the workspace disk * improvement(desktop): bundle the desktop app without macOS prebuilds in the Linux E2E step * improvement(desktop): report desktop E2E failures inline and trace runner memory * improvement(desktop): run the live desktop E2E in its own job on a larger runner * improvement(desktop): warm the live desktop E2E routes and wait on outcomes instead of time - Compile every route, page and client chunk the tests use in a named warm-up step before any timed hold - Send retries only while no turn reached Sim; every held request and poll is bounded - Stop and sign-out tests wait for the import's own report and assert one exact terminal state - Clicks fail fast with the dev overlay's error instead of a blocked click * improvement(desktop): retype and resend a message the hydrating composer dropped * improvement(desktop): run the live desktop E2E against the merged background executor - The dormancy check accepts the app's own registrations, refused while signed out and answered as not enabled once signed in, and still forbids inbox, doorbell, claim, lease and completion - The app and realtime server run in their own sessions and stop through stop-session.sh * improvement(desktop): release the held read at the moment its view lets go, and report the call's error on a wrong terminal state * improvement(desktop): read the chat body's desktop capabilities with toRecord * improvement(desktop): hold the read's own report across Stop, dismiss dev console notices, and keep failure diagnostics * improvement(desktop): stop the live E2E apps by their E2E_APP tags * improvement(desktop): deliver the claim held across Stop to Sim even if Electron gave up on it, and require its 410 * improvement(desktop): warm every route the live E2E reaches, keep sends honest, and check dormancy layer by layer - Warm-up runs the tests' own flows (read, import, a chat switch during a live turn, Stop, login) and waits until every request the app made has been answered - send() resends only when the UI did not take the message; a taken message whose turn never reaches Sim fails as a lost send - Stop and sign-out tests assert exact outcomes without holding the tool's report (the stopped tool reports nothing since #8732) - Dormancy checks registration, device records, binding, routes and the doorbell as separate soft assertions - CI: per-run E2E_APP tags, retries 0, and a path filter for pull requests * improvement(desktop): wait with the shared sleep helper while the dev app settles * improvement(ci): detect live desktop suite changes with git, covering chat pages and auth routes * improvement(ci): run the live desktop suite unless every change is clearly unrelated, and fail open - The change check skips only docs, the other apps and published content; any change elsewhere, and any failure to fetch or diff the base, runs the suite - settled() discounts only requests a hold is keeping from Sim right now - The warm-up's Stop turn waits for its stream to close instead of on a promise that never settles
1 parent f83d977 commit af5501b

6 files changed

Lines changed: 1634 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
# Prints `changed=false` only when every file a pull request changes is clearly unrelated to the
3+
# live desktop suite, and `changed=true` otherwise, including when the diff cannot be worked out.
4+
#
5+
# Usage: desktop-live-changes.sh <base-sha>
6+
set -u
7+
8+
unrelated='^(apps/docs/|apps/pii/|apps/sim/content/|packages/(python-sdk|ts-sdk)/)|\.mdx?$|(^|/)LICENSE$'
9+
10+
base=${1:-}
11+
run() {
12+
echo "changed=true"
13+
echo "Running the live desktop suite: $1" >&2
14+
exit 0
15+
}
16+
[ -n "$base" ] || run 'no base commit'
17+
git fetch --quiet --depth=1 origin "$base" || run "could not fetch $base"
18+
names=$(git diff --name-only "$base" HEAD) || run "could not diff against $base"
19+
[ -n "$names" ] || run 'no changed files listed'
20+
if printf '%s\n' "$names" | grep -qvE "$unrelated"; then
21+
run 'a change may affect it'
22+
fi
23+
echo "changed=false"
24+
echo 'Skipping the live desktop suite: every change is unrelated to it' >&2

‎.github/workflows/test-build.yml‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,148 @@ jobs:
442442
if-no-files-found: ignore
443443
retention-days: 7
444444

445+
# Pull requests skip the live desktop suite only when every change is clearly unrelated to the
446+
# app it drives (docs, other apps, published content). Anything else, and any failure to work
447+
# out the diff, runs it: a pull request that skipped it wrongly would first fail on staging.
448+
desktop-live-changes:
449+
name: Detect desktop tool changes
450+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
451+
timeout-minutes: 5
452+
outputs:
453+
changed: ${{ github.event_name != 'pull_request' || steps.diff.outputs.changed != 'false' }}
454+
steps:
455+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
456+
if: github.event_name == 'pull_request'
457+
with:
458+
fetch-depth: 2
459+
- name: Diff against the pull request's base
460+
id: diff
461+
if: github.event_name == 'pull_request'
462+
env:
463+
BASE: ${{ github.event.pull_request.base.sha }}
464+
run: bash .github/scripts/desktop-live-changes.sh "$BASE" >> "$GITHUB_OUTPUT"
465+
466+
# Desktop tools in the real Electron app against a local app, on its own runner: the
467+
# Electron app, the dev app and its realtime server together outgrow the http-e2e runner.
468+
desktop-live-e2e:
469+
name: Desktop tools against a local app
470+
needs: desktop-live-changes
471+
if: needs.desktop-live-changes.outputs.changed == 'true'
472+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-latest' }}
473+
timeout-minutes: 30
474+
services:
475+
postgres:
476+
image: pgvector/pgvector:pg17
477+
env:
478+
POSTGRES_USER: postgres
479+
POSTGRES_PASSWORD: postgres
480+
POSTGRES_DB: sim_test
481+
ports:
482+
- 5432:5432
483+
options: >-
484+
--health-cmd "pg_isready -U postgres -d sim_test"
485+
--health-interval 5s
486+
--health-timeout 5s
487+
--health-retries 10
488+
redis:
489+
image: redis:7-alpine
490+
ports:
491+
- 6379:6379
492+
options: >-
493+
--health-cmd "redis-cli ping"
494+
--health-interval 5s
495+
--health-timeout 5s
496+
--health-retries 10
497+
env:
498+
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test
499+
BETTER_AUTH_SECRET: desktop-live-e2e-ci-secret-at-least-32-characters
500+
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000'
501+
502+
steps:
503+
- name: Checkout code
504+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
505+
506+
- name: Setup workspace
507+
uses: ./.github/actions/setup-workspace
508+
with:
509+
provider: ${{ vars.CI_PROVIDER }}
510+
511+
- name: Provision the database through migrations
512+
working-directory: packages/db
513+
run: bun run db:migrate
514+
515+
# Chat switches, Stop, sign-out, approval and the flag-off foreground round trip. The spec
516+
# runs the recording proxy (the app's public origin) and the stand-in worker.
517+
- name: Verify desktop tools in the Electron app against a local app
518+
env:
519+
NEXT_PUBLIC_APP_URL: http://127.0.0.1:3020
520+
BETTER_AUTH_URL: http://127.0.0.1:3020
521+
REDIS_URL: redis://127.0.0.1:6379
522+
SIM_AGENT_API_URL: http://127.0.0.1:3022
523+
NEXT_PUBLIC_SOCKET_URL: http://127.0.0.1:3023
524+
SOCKET_SERVER_URL: http://127.0.0.1:3023
525+
NEXT_PUBLIC_FORCE_HOSTED: 'false'
526+
COPILOT_API_KEY: desktop-tools-e2e-ci-local-copilot-key
527+
COPILOT_TOOL_PERMISSIONS_ENABLED: 'true'
528+
MOTHERSHIP_SIM_TRANSPORT: direct
529+
INTERNAL_API_SECRET: desktop-tools-e2e-ci-local-secret-at-least-32-characters
530+
DISABLE_TELEMETRY: 'true'
531+
NEXT_TELEMETRY_DISABLED: '1'
532+
READY_TIMEOUT_SECONDS: 300
533+
run: |
534+
report_dir="$RUNNER_TEMP/e2e"
535+
mkdir -p "$report_dir"
536+
sudo apt-get update -q
537+
sudo apt-get install -yq xvfb libgtk-3-0t64 libnss3 libasound2t64 libgbm1 libxss1 \
538+
libxtst6 libatk-bridge2.0-0t64 libxkbcommon0 > /dev/null
539+
# Bundle only: `bun run build` also fetches the macOS node-pty prebuilds for packaging,
540+
# which a Linux run does not use.
541+
(cd apps/desktop && bun run scripts/build.ts)
542+
# Each app runs in its own session under an E2E_APP tag, and stop-session.sh returns once
543+
# every process it started has exited.
544+
realtime_tag="desktop-realtime-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
545+
server_tag="desktop-tools-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
546+
(cd apps/realtime && PORT=3023 SIM_DB_ROLE=realtime ALLOWED_ORIGINS="$NEXT_PUBLIC_APP_URL" \
547+
E2E_APP="$realtime_tag" exec setsid bun src/index.ts > "$report_dir/desktop-tools-realtime.log" 2>&1) &
548+
realtime_pid=$!
549+
(cd apps/sim && E2E_APP="$server_tag" exec setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
550+
--port 3021 > "$report_dir/desktop-tools-next.log" 2>&1) &
551+
server_pid=$!
552+
finish() {
553+
status=$?
554+
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag" || status=1
555+
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$realtime_pid" "$realtime_tag" || status=1
556+
wait "$server_pid" "$realtime_pid" 2>/dev/null || true
557+
exit "$status"
558+
}
559+
trap finish EXIT
560+
started=$SECONDS
561+
until curl --fail --silent --max-time 10 http://127.0.0.1:3021/api/health > /dev/null &&
562+
curl --fail --silent --max-time 10 http://127.0.0.1:3023/health > /dev/null; do
563+
kill -0 "$server_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-next.log"; exit 1; }
564+
kill -0 "$realtime_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-realtime.log"; exit 1; }
565+
[ $((SECONDS - started)) -lt "$READY_TIMEOUT_SECONDS" ] || { echo '::error::Local app did not become ready'; exit 1; }
566+
sleep 2
567+
done
568+
cd apps/desktop
569+
SIM_DESKTOP_E2E_SIM_URL=http://127.0.0.1:3021 \
570+
SIM_DESKTOP_E2E_PROXY_PORT=3020 \
571+
SIM_DESKTOP_E2E_AGENT_PORT=3022 \
572+
SIM_DESKTOP_E2E_DATABASE_URL="$DATABASE_URL" \
573+
SIM_DESKTOP_E2E_REDIS_URL="$REDIS_URL" \
574+
SIM_DESKTOP_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
575+
xvfb-run -a -s '-screen 0 1920x1200x24' bunx playwright test e2e/desktop-tools-live-sim.spec.ts \
576+
--output "$report_dir/desktop-tools-results" --retries=0
577+
578+
- name: Upload Electron E2E results and server logs
579+
if: failure()
580+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
581+
with:
582+
name: desktop-live-e2e-results
583+
path: ${{ runner.temp }}/e2e/
584+
if-no-files-found: ignore
585+
retention-days: 7
586+
445587
test-build:
446588
name: Lint and Test
447589
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}

0 commit comments

Comments
 (0)