Skip to content

Commit ff3d930

Browse files
committed
improvement(ci): share production builds across HTTP and Electron suites
1 parent fd8616a commit ff3d930

3 files changed

Lines changed: 55 additions & 145 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env bash
22
# Runs one end-to-end suite group over real HTTP, each against its own app: `next dev`, except
3-
# mobile and Project Files in CI, which reuse the production build from `e2e-build`.
3+
# CI groups, which reuse the production build from `e2e-build`.
44
# The job extracts it into apps/sim/.next before running these suites.
55
#
6-
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile> (run from apps/sim)
6+
# Usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo> (run from apps/sim)
77
#
88
# The job provides DATABASE_URL, BETTER_AUTH_SECRET and ENCRYPTION_KEY; each group sets the rest of
99
# its app's environment here. Reports and server logs land in $RUNNER_TEMP/e2e.
@@ -20,7 +20,7 @@
2020
# telemetry flush runs detached and still writes .next/dev).
2121
set -euo pipefail
2222

23-
group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile>}
23+
group=${1:?usage: http-e2e.sh <scim|cli|stop-after|desktop-inbox|project-files|mobile|seo>}
2424
report_dir="$RUNNER_TEMP/e2e"
2525
ready_timeout_seconds=600
2626
mkdir -p "$report_dir"
@@ -34,7 +34,7 @@ realtime_tag=''
3434
realtime_log=''
3535
browsers_pid=''
3636
# `dev` or `start`; a group sets it before start_app.
37-
app_server=dev
37+
app_server=${E2E_APP_SERVER:-dev}
3838

3939
finish() {
4040
local status=$?
@@ -174,7 +174,7 @@ case "$group" in
174174
bun --no-env-file scripts/test-mcp-host-e2e.ts
175175
CLI_LATENCY_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
176176
CLI_LATENCY_E2E_DATABASE_URL="$DATABASE_URL" \
177-
CLI_LATENCY_E2E_RUNS=3 \
177+
CLI_LATENCY_E2E_RUNS=1 \
178178
CLI_LATENCY_E2E_WARMUP=1 \
179179
CLI_LATENCY_E2E_REPORT_PATH="$report_dir/cli-run-latency-report.json" \
180180
bun run test:cli-run-latency:e2e
@@ -246,7 +246,13 @@ case "$group" in
246246
MOBILE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \
247247
MOBILE_E2E_REPORT_PATH="$report_dir/mobile-e2e-report.json" \
248248
bun run test:mobile:e2e
249-
# The same production build answers as a crawler sees it: statuses, robots, sitemap, JSON-LD.
249+
;;
250+
251+
seo)
252+
app_server=start
253+
export NEXT_PUBLIC_FORCE_HOSTED=false
254+
start_app seo 3024 'landing SEO' record-http-status
255+
# Plain HTTP assertions have no browser-specific behavior.
250256
LANDING_SEO_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
251257
LANDING_SEO_E2E_REPORT_PATH="$report_dir/landing-seo-e2e-report.json" \
252258
bun run test:landing-seo:e2e

‎.github/workflows/checks.yml‎

Lines changed: 42 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,10 @@ jobs:
167167
# The suites exercise HTTP behavior rather than a provisioning path, so they run once, against
168168
# the production (migrate) path. Each group's app environment lives in http-e2e.sh.
169169
#
170-
# SCIM's hosted suites use 8 vCPU; the other self-hosted suites fit on 4. The mobile browser
171-
# suite runs in e2e-production below.
170+
# All groups reuse the production build; compiling routes is not part of HTTP acceptance.
172171
e2e:
173172
name: e2e (${{ matrix.group }})
174-
needs: changes
173+
needs: [changes, e2e-build]
175174
if: needs.changes.outputs.full == 'true'
176175
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.runner || 'ubuntu-latest' }}
177176
timeout-minutes: 20
@@ -180,13 +179,15 @@ jobs:
180179
matrix:
181180
include:
182181
- group: scim
183-
runner: blacksmith-8vcpu-ubuntu-2404
182+
runner: blacksmith-4vcpu-ubuntu-2404
184183
- group: cli
185184
runner: blacksmith-4vcpu-ubuntu-2404
186185
- group: stop-after
187186
runner: blacksmith-4vcpu-ubuntu-2404
188187
- group: desktop-inbox
189188
runner: blacksmith-4vcpu-ubuntu-2404
189+
- group: seo
190+
runner: blacksmith-4vcpu-ubuntu-2404
190191
services:
191192
postgres:
192193
image: pgvector/pgvector:pg17
@@ -214,6 +215,7 @@ jobs:
214215
env:
215216
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_test
216217
BETTER_AUTH_SECRET: http-e2e-ci-secret-at-least-32-characters
218+
E2E_APP_SERVER: start
217219
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000'
218220

219221
steps:
@@ -241,12 +243,39 @@ jobs:
241243
working-directory: packages/db
242244
run: bun run db:migrate
243245

246+
- &download-e2e-build
247+
name: Download the production build
248+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
249+
with:
250+
name: next-build
251+
path: ${{ runner.temp }}
252+
253+
# A hosted build (NEXT_PUBLIC_APP_URL on sim.ai) prerenders every post; the build production
254+
# images use prerenders none. The URL itself is no marker: next.config's redirects name
255+
# www.sim.ai in every build.
256+
- &extract-e2e-build
257+
name: Extract the production build
258+
run: |
259+
tar -I zstd -xf "$RUNNER_TEMP/next-build.tar.zst" -C apps/sim
260+
jq -e '[.routes[].srcRoute] | any(. == "/blog/[slug]" or . == "/library/[slug]") | not' \
261+
apps/sim/.next/prerender-manifest.json > /dev/null ||
262+
{ echo "::error::The build prerendered posts (a hosted build) or has no prerender manifest."; exit 1; }
263+
244264
# No step timeout: the job's bound covers a hang without cutting a slow but healthy suite
245265
# short of writing its report.
246266
- name: Run end-to-end suites
247267
working-directory: apps/sim
248268
run: bash ../../.github/scripts/http-e2e.sh "${{ matrix.group }}"
249269

270+
- name: Upload acceptance timing and coverage reports
271+
if: always()
272+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
273+
with:
274+
name: e2e-reports-${{ matrix.group }}
275+
path: ${{ runner.temp }}/e2e/**/*.json
276+
if-no-files-found: ignore
277+
retention-days: 7
278+
250279
- name: Upload end-to-end reports and server logs
251280
if: failure()
252281
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
@@ -340,21 +369,8 @@ jobs:
340369
working-directory: packages/db
341370
run: bun run db:migrate
342371

343-
- name: Download the production build
344-
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
345-
with:
346-
name: next-build
347-
path: ${{ runner.temp }}
348-
349-
# A hosted build (NEXT_PUBLIC_APP_URL on sim.ai) prerenders every post; the build production
350-
# images use prerenders none. The URL itself is no marker: next.config's redirects name
351-
# www.sim.ai in every build.
352-
- name: Extract the production build
353-
run: |
354-
tar -I zstd -xf "$RUNNER_TEMP/next-build.tar.zst" -C apps/sim
355-
jq -e '[.routes[].srcRoute] | any(. == "/blog/[slug]" or . == "/library/[slug]") | not' \
356-
apps/sim/.next/prerender-manifest.json > /dev/null ||
357-
{ echo "::error::The build prerendered posts (a hosted build) or has no prerender manifest."; exit 1; }
372+
- *download-e2e-build
373+
- *extract-e2e-build
358374

359375
# JOBS=4 caps node-gyp parallelism as docker/app.Dockerfile does (laverdet/isolated-vm#428).
360376
- name: Build native isolate addon for Project file rendering
@@ -393,15 +409,14 @@ jobs:
393409
if-no-files-found: ignore
394410
retention-days: 7
395411

396-
# Desktop tools in the real Electron app against a local app, on its own runner: the
397-
# Electron app, the dev app and its realtime server together outgrow the e2e runner.
412+
# Desktop tools exercise the real Electron app against the shared production build.
398413
# Two runners: the lease-lapse test sleeps through a long import and then a lease (~3 min), as
399414
# long as the rest of the spec together, so on one runner it set the time for every run.
400415
desktop-live:
401416
name: desktop-live (${{ matrix.tests }})
402-
needs: changes
417+
needs: [changes, e2e-build]
403418
if: needs.changes.outputs.desktop_live == 'true'
404-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-latest' }}
419+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
405420
timeout-minutes: 30
406421
strategy:
407422
fail-fast: false
@@ -452,22 +467,8 @@ jobs:
452467
working-directory: packages/db
453468
run: bun run db:migrate
454469

455-
# Turbopack's dev cache turns the spec's route warm-up from a cold compile (~4 min) into a
456-
# restore. It is content-addressed, so a pull request's changed modules still recompile; the
457-
# key carries the installed Next version so an upgrade starts from an empty cache, and the
458-
# event and fork segments keep untrusted runs off the cache trusted runs read.
459-
- name: Resolve Turbopack dev cache key
460-
id: next-cache
461-
run: echo "key=${GITHUB_REPOSITORY}-next-dev-desktop-live-${GITHUB_EVENT_NAME}${FORK_SUFFIX}-$(jq -r .version node_modules/next/package.json)" >> "$GITHUB_OUTPUT"
462-
env:
463-
FORK_SUFFIX: ${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
464-
465-
- name: Mount Turbopack dev cache
466-
uses: ./.github/actions/cache
467-
with:
468-
provider: ${{ vars.CI_PROVIDER }}
469-
key: ${{ steps.next-cache.outputs.key }}
470-
path: ./apps/sim/.next/dev
470+
- *download-e2e-build
471+
- *extract-e2e-build
471472

472473
# Chat switches, Stop, sign-out, approval, the dormant-executor round trip, and background
473474
# runs across a chat switch and a network cut. The spec runs the recording proxy (the app's
@@ -496,37 +497,12 @@ jobs:
496497
# every process it started has exited.
497498
realtime_tag="desktop-realtime-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
498499
server_tag="desktop-tools-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
499-
# Keeps the restored cache under the cap the dev scripts apply locally.
500-
(cd apps/sim && bun run dev:cache:cap)
501500
start_sim() {
502-
(cd apps/sim && E2E_APP="$server_tag" exec setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
501+
(cd apps/sim && E2E_APP="$server_tag" LOG_LEVEL=INFO exec setsid node ../../node_modules/next/dist/bin/next start --hostname 127.0.0.1 \
503502
--port 3021 >> "$next_log" 2>&1) &
504503
server_pid=$!
505504
}
506-
# A corrupted cache aborts Turbopack instead of falling back. Only then is the shared cache
507-
# dropped: a failing test must not cost every later run its warm cache.
508-
cache_broken() {
509-
grep -qiE 'cache corruption|turbopack.*panic|panicked' "$next_log" 2>/dev/null
510-
}
511-
# The cache directory is a mount point: empty it rather than remove it. Absolute, because
512-
# the EXIT trap runs after the step has moved into apps/desktop.
513-
clear_cache() {
514-
find "$GITHUB_WORKSPACE/apps/sim/.next/dev" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
515-
}
516-
# A sticky disk keeps nothing from a failed job, so a clear here sticks only when the job
517-
# passes. The marker names the cache for desktop-live-cache-reset, which empties it after
518-
# a failed job; written on each panic, it survives the restart truncating the log.
519-
mark_cache_broken() {
520-
jq -r .version "$GITHUB_WORKSPACE/node_modules/next/package.json" > "$report_dir/turbopack-cache-broken"
521-
}
522-
# SIGINT first: `next dev` SIGKILLs its server 100ms after SIGTERM, which discards a cache
523-
# write in flight. stop-session.sh then removes anything still running.
524-
# SIGINT is best-effort; the cleanup always runs, since workers and the detached telemetry
525-
# flush can outlive a server that has already exited.
526505
stop_sim() {
527-
if kill -INT "$server_pid" 2>/dev/null; then
528-
for _ in $(seq 1 30); do kill -0 "$server_pid" 2>/dev/null || break; sleep 1; done
529-
fi
530506
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
531507
}
532508
(cd apps/realtime && PORT=3023 SIM_DB_ROLE=realtime ALLOWED_ORIGINS="$NEXT_PUBLIC_APP_URL" \
@@ -538,11 +514,6 @@ jobs:
538514
stop_sim || status=1
539515
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$realtime_pid" "$realtime_tag" || status=1
540516
wait "$server_pid" "$realtime_pid" 2>/dev/null || true
541-
if cache_broken; then
542-
echo "::warning::Turbopack reported a broken dev cache; clearing it for the next run."
543-
mark_cache_broken
544-
clear_cache
545-
fi
546517
exit "$status"
547518
}
548519
trap finish EXIT
@@ -554,21 +525,10 @@ jobs:
554525
# which a Linux run does not use.
555526
(cd apps/desktop && bun run scripts/build.ts)
556527
started=$SECONDS
557-
retried=0
558528
until curl --fail --silent --max-time 10 http://127.0.0.1:3021/api/health > /dev/null &&
559529
curl --fail --silent --max-time 10 http://127.0.0.1:3023/health > /dev/null; do
560530
if ! kill -0 "$server_pid" 2>/dev/null; then
561531
tail -n 200 "$next_log"
562-
if [ "$retried" = 0 ] && cache_broken; then
563-
echo "::warning::Turbopack rejected the restored dev cache; restarting from an empty cache."
564-
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
565-
mark_cache_broken
566-
clear_cache
567-
: > "$next_log"
568-
retried=1
569-
start_sim
570-
continue
571-
fi
572532
exit 1
573533
fi
574534
kill -0 "$realtime_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-realtime.log"; exit 1; }
@@ -595,63 +555,6 @@ jobs:
595555
if-no-files-found: ignore
596556
retention-days: 7
597557

598-
# Empties a dev cache Turbopack panicked on in a failed desktop-live run. The run's own clear is
599-
# discarded with the rest of its failed job, so every later run would restore the same broken
600-
# cache and fail; emptied in this passing job, the empty cache is what the next run mounts. A
601-
# failure the cache did not cause leaves no marker, and the warm cache stays.
602-
desktop-live-cache-reset:
603-
name: desktop-live cache reset
604-
needs: desktop-live
605-
if: ${{ always() && needs.desktop-live.result == 'failure' }}
606-
runs-on: *runner-2vcpu
607-
timeout-minutes: 5
608-
steps:
609-
- name: Download desktop-live results
610-
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
611-
with:
612-
pattern: desktop-live-e2e-results-*
613-
path: ${{ runner.temp }}/desktop-live
614-
merge-multiple: true
615-
616-
# The marker carries only the Next version; the rest of the key comes from this run's own
617-
# context, as in desktop-live, so a fork's marker can name no cache but its own.
618-
- name: Resolve the broken cache's key
619-
id: broken
620-
env:
621-
MARKER: ${{ runner.temp }}/desktop-live/turbopack-cache-broken
622-
FORK_SUFFIX: ${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
623-
run: |
624-
if [ ! -s "$MARKER" ]; then
625-
echo "No broken dev cache reported; keeping the warm cache."
626-
exit 0
627-
fi
628-
next_version="$(head -n 1 "$MARKER")"
629-
if ! [[ "$next_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.-]*$ ]]; then
630-
echo "::error::The broken cache marker holds no Next version: '$next_version'"
631-
exit 1
632-
fi
633-
echo "key=${GITHUB_REPOSITORY}-next-dev-desktop-live-${GITHUB_EVENT_NAME}${FORK_SUFFIX}-${next_version}" >> "$GITHUB_OUTPUT"
634-
635-
- *checkout-mirror
636-
- *checkout-plain
637-
638-
# A desktop-live leg that passed may have saved this run's actions/cache entry already; the
639-
# suffix gives the emptied cache its own, newer entry for the next run's prefix restore.
640-
- name: Mount the broken dev cache
641-
if: steps.broken.outputs.key != ''
642-
uses: ./.github/actions/cache
643-
with:
644-
provider: ${{ vars.CI_PROVIDER }}
645-
key: ${{ steps.broken.outputs.key }}
646-
path: ./apps/sim/.next/dev
647-
save-suffix: -reset
648-
649-
- name: Empty the dev cache
650-
if: steps.broken.outputs.key != ''
651-
run: |
652-
mkdir -p apps/sim/.next/dev
653-
find apps/sim/.next/dev -mindepth 1 -maxdepth 1 -exec rm -rf {} +
654-
655558
# Lint, audits, type-check and the schema sync check: about two minutes of mostly cached work,
656559
# kept off the test shards so neither waits on the other.
657560
lint:
@@ -985,7 +888,7 @@ jobs:
985888
e2e-build:
986889
name: build (e2e)
987890
needs: changes
988-
if: needs.changes.outputs.full == 'true'
891+
if: needs.changes.outputs.full == 'true' || needs.changes.outputs.desktop_live == 'true'
989892
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'linux-x64-8-core' }}
990893
timeout-minutes: 25
991894

‎apps/sim/lib/mothership/tools/client/computer-tool-unload.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
88
import { copilotConfirmBodySchema } from '@/lib/api/contracts/copilot'
99
import { executeComputerToolOnClient } from '@/lib/mothership/tools/client/computer-tool-execution'
1010
import { clearUserData } from '@/stores'
11+
import '@/stores/reset-all-stores'
1112

1213
const native = vi.hoisted(() => ({ execute: vi.fn(), cancel: vi.fn() }))
1314
vi.mock('@/lib/computer-use/transport', () => ({

0 commit comments

Comments
 (0)