Skip to content

Commit 140f1ae

Browse files
authored
fix(e2e): connect the mobile suite to realtime and give dev-server round trips the server budget (#8937)
* fix(e2e): connect the mobile suite to realtime and give dev-server round trips the server budget - Start the realtime server for the mobile group. Without it every workspace page raised a persistent "Reconnecting..." toast ~2s after load; on the locked-workflow reload it raced the lock toast and, when it arrived second, sat in front of the Unlock Workflow action, so the tap timed out on "subtree intercepts pointer events" - Wait 120s, the suite's budget for a next dev round trip, for the URL after a sidebar link tap, the Files list after it, and the table cell after a reload. next dev spent 16.4s (application code 70ms) serving the /home RSC payload and left the rows request unanswered past 20s - Install the browsers while the app boots * fix(e2e): stop the background browser install on exit and surface install and realtime logs on failure
1 parent bf87c51 commit 140f1ae

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

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

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ server_pid=''
2626
app_tag=''
2727
server_log=''
2828
status_log=''
29+
realtime_pid=''
30+
realtime_tag=''
31+
realtime_log=''
32+
browsers_pid=''
2933

3034
finish() {
3135
local status=$?
@@ -39,6 +43,17 @@ finish() {
3943
tail -n 200 "$server_log"
4044
fi
4145
fi
46+
if [ -n "$realtime_pid" ]; then
47+
bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$realtime_pid" "$realtime_tag" || status=1
48+
wait "$realtime_pid" 2>/dev/null || true
49+
if [ "$status" -ne 0 ]; then
50+
tail -n 100 "$realtime_log"
51+
fi
52+
fi
53+
if [ -n "$browsers_pid" ]; then
54+
kill -- "-$browsers_pid" 2>/dev/null || true
55+
wait "$browsers_pid" 2>/dev/null || true
56+
fi
4257
exit "$status"
4358
}
4459
trap finish EXIT
@@ -58,20 +73,39 @@ start_app() {
5873
E2E_APP="$app_tag" setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
5974
--port "$port" > "$server_log" 2>&1 &
6075
server_pid=$!
76+
wait_ready "$server_pid" "$NEXT_PUBLIC_APP_URL/api/health" "$label app"
77+
}
6178

79+
# start_realtime <name> <port>: the Socket.IO server the app's browser clients connect to, so pages
80+
# render connected rather than under a "Reconnecting..." notification. Start after start_app, which
81+
# exports the app URL the server allows as an origin; NEXT_PUBLIC_SOCKET_URL must already be set
82+
# when the app starts.
83+
start_realtime() {
84+
local name=$1 port=$2
85+
realtime_tag="$name-realtime-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
86+
realtime_log="$report_dir/$name-realtime.log"
87+
(cd ../realtime && PORT="$port" E2E_APP="$realtime_tag" exec setsid bun run start \
88+
> "$realtime_log" 2>&1) &
89+
realtime_pid=$!
90+
wait_ready "$realtime_pid" "http://127.0.0.1:$port/health" "$name realtime server"
91+
}
92+
93+
# wait_ready <pid> <health-url> <label>
94+
wait_ready() {
95+
local pid=$1 url=$2 label=$3
6296
local started=$SECONDS
63-
until curl --fail --silent --max-time 10 "$NEXT_PUBLIC_APP_URL/api/health" > /dev/null; do
64-
if ! kill -0 "$server_pid" 2>/dev/null; then
65-
echo "::error::Local $label app exited during startup."
97+
until curl --fail --silent --max-time 10 "$url" > /dev/null; do
98+
if ! kill -0 "$pid" 2>/dev/null; then
99+
echo "::error::Local $label exited during startup."
66100
exit 1
67101
fi
68102
if [ $((SECONDS - started)) -ge "$ready_timeout_seconds" ]; then
69-
echo "::error::Local $label app did not become ready within $ready_timeout_seconds seconds."
103+
echo "::error::Local $label did not become ready within $ready_timeout_seconds seconds."
70104
exit 1
71105
fi
72106
sleep 2
73107
done
74-
echo "Local $label app ready after $((SECONDS - started))s"
108+
echo "Local $label ready after $((SECONDS - started))s"
75109
}
76110

77111
case "$group" in
@@ -159,8 +193,20 @@ case "$group" in
159193
export NEXT_PUBLIC_FORCE_HOSTED=false
160194
export INTERNAL_API_SECRET=mobile-http-ci-local-secret-at-least-32-characters
161195
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=12288"
162-
bunx --no-install playwright install --with-deps chromium webkit
196+
export NEXT_PUBLIC_SOCKET_URL=http://127.0.0.1:3025 SOCKET_SERVER_URL=http://127.0.0.1:3025
197+
# The browsers install while the app boots, in their own process group so finish() can stop them.
198+
browsers_log="$report_dir/mobile-playwright-install.log"
199+
setsid --wait bunx --no-install playwright install --with-deps chromium webkit > "$browsers_log" 2>&1 &
200+
browsers_pid=$!
163201
start_app mobile 3024 'mobile browser' record-http-status
202+
start_realtime mobile 3025
203+
if ! wait "$browsers_pid"; then
204+
browsers_pid=''
205+
tail -n 100 "$browsers_log"
206+
echo "::error::Playwright browser install failed."
207+
exit 1
208+
fi
209+
browsers_pid=''
164210
MOBILE_E2E_BASE_URL="$NEXT_PUBLIC_APP_URL" \
165211
MOBILE_E2E_DATABASE_URL="$DATABASE_URL" \
166212
MOBILE_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \

‎apps/sim/scripts/test-mobile-e2e.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,20 +684,24 @@ async function exerciseViewport(
684684
await expectContained(page, sidebar)
685685
if (viewport.name === 'phone') await capture(page, `${browserName}-phone-navigation`)
686686
await sidebar.getByRole('link', { name: 'New chat', exact: true }).tap()
687-
await expect(page).toHaveURL(`${baseUrl.origin}/workspace/${workspaceId}/home`)
687+
await expect(page).toHaveURL(`${baseUrl.origin}/workspace/${workspaceId}/home`, {
688+
timeout: 120_000,
689+
})
688690
await expect(page.getByRole('textbox', { name: 'Message', exact: true })).toBeVisible()
689691
await expect(sidebar).toBeHidden()
690692
await page.getByRole('button', { name: 'Open navigation', exact: true }).tap()
691693
await page.getByRole('button', { name: 'Close navigation', exact: true }).tap()
692694
await expect(sidebar).toBeHidden()
693695
await page.getByRole('button', { name: 'Open navigation', exact: true }).tap()
694696
await sidebar.getByRole('link', { name: 'Files', exact: true }).tap()
695-
await expect(page).toHaveURL(new RegExp(`/workspace/${workspaceId}/files`))
697+
await expect(page).toHaveURL(new RegExp(`/workspace/${workspaceId}/files`), {
698+
timeout: 120_000,
699+
})
696700
await expect(sidebar).toBeHidden()
697701
await expect(page.locator('.workspace-content-shell')).toBeVisible()
698702
await expect(
699703
page.getByRole('button', { name: `Actions for ${folderName}`, exact: true })
700-
).toBeVisible()
704+
).toBeVisible({ timeout: 120_000 })
701705
await expectDesktopPreference(context, page)
702706
}
703707
)
@@ -977,7 +981,7 @@ async function exerciseViewport(
977981
await withDocumentNavigation(page, page.url(), () =>
978982
page.reload({ waitUntil: 'commit', timeout: 360_000 })
979983
)
980-
await expect(cell).toHaveText(value)
984+
await expect(cell).toHaveText(value, { timeout: 120_000 })
981985
await page.getByRole('button', { name: 'Column options', exact: true }).first().tap()
982986
await expectContained(page, page.getByRole('menu'))
983987
await page.getByRole('menuitem', { name: 'Edit column', exact: true }).tap()

0 commit comments

Comments
 (0)