E2E: longer waits for slow fixture renders, shorter Playwright artifact retention (#174)

* fix(mobile): align Expo SDK 57 patch deps for install --check

Bump @expo/ui, expo, expo-constants, expo-linking, expo-modules-core,
expo-notifications, expo-router, and expo-updates to the versions
expo install --check expects after upstream patch releases.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* ci(e2e): harden flaky waits and trim Playwright artifact uploads

Chart fixture reloads and shell readiness can exceed 30s on loaded
self-hosted runners. Align timeouts with the rest of the suite and wait
for gallery mount before counting species.

Stop uploading per-test trace folders to Actions (HTML report only) with
1-day retention so quota errors stay non-blocking.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* evo-deploy: probe health before checking the timeout

With a short timeout the clock could tick past it before the first
probe, so a healthy service was reported as timed out. Probe first,
then check the clock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-09 10:56:32 +02:00 committed by GitHub
parent b1b76a846e
commit 6c0fba8cc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 17 additions and 13 deletions

View File

@ -165,15 +165,14 @@ jobs:
- name: Upload Playwright artifacts - name: Upload Playwright artifacts
if: always() && inputs.upload_artifacts if: always() && inputs.upload_artifacts
continue-on-error: true continue-on-error: true
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with: with:
name: playwright-artifacts-${{ github.run_id }}-${{ github.run_attempt }} name: playwright-artifacts-${{ github.run_id }}-${{ github.run_attempt }}
path: | path: |
playwright-report playwright-report
playwright-report-www playwright-report-www
apps/web/test-results
test-report/e2e test-report/e2e
retention-days: 7 retention-days: 1
if-no-files-found: warn if-no-files-found: warn
- name: Publish Playwright visual report - name: Publish Playwright visual report
if: always() && inputs.publish_report if: always() && inputs.publish_report

View File

@ -21,7 +21,7 @@ test.describe
await page.goto(`${fixture}?${new URLSearchParams({ href, inspect })}`); await page.goto(`${fixture}?${new URLSearchParams({ href, inspect })}`);
const circle = page.locator("svg circle").first(); const circle = page.locator("svg circle").first();
// Every navigation renders Plot again, which takes seconds on a loaded CI runner. // Every navigation renders Plot again, which takes seconds on a loaded CI runner.
await expect(circle).toBeVisible({ timeout: 30_000 }); await expect(circle).toBeVisible({ timeout: 90_000 });
const assertInert = async () => { const assertInert = async () => {
const links = page.locator("svg a"); const links = page.locator("svg a");
expect(await links.count()).toBeGreaterThan(0); expect(await links.count()).toBeGreaterThan(0);
@ -43,7 +43,7 @@ test.describe
await assertInert(); await assertInert();
await page.getByRole("button", { name: "Expand", exact: true }).click(); await page.getByRole("button", { name: "Expand", exact: true }).click();
const dialog = page.getByRole("dialog"); const dialog = page.getByRole("dialog");
await expect(dialog.locator("svg circle")).toBeVisible({ timeout: 30_000 }); await expect(dialog.locator("svg circle")).toBeVisible({ timeout: 90_000 });
await dialog.locator("svg circle").click(); await dialog.locator("svg circle").click();
await assertInert(); await assertInert();
await expect(page).toHaveURL(`${fixture}?${new URLSearchParams({ href, inspect })}`); await expect(page).toHaveURL(`${fixture}?${new URLSearchParams({ href, inspect })}`);
@ -62,7 +62,7 @@ test.describe
route.fulfill({ body: "Chart destination" }), route.fulfill({ body: "Chart destination" }),
); );
await page.goto(`${fixture}?${new URLSearchParams({ href: "#details" })}`); await page.goto(`${fixture}?${new URLSearchParams({ href: "#details" })}`);
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 30_000 }); await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 90_000 });
for (const href of [ for (const href of [
"https://example.test/chart?a=1&b=2", "https://example.test/chart?a=1&b=2",
"mailto:chart@example.test", "mailto:chart@example.test",
@ -73,7 +73,7 @@ test.describe
"java%73cript:void(0)", "java%73cript:void(0)",
]) { ]) {
await page.goto(`${fixture}?${new URLSearchParams({ href })}`); await page.goto(`${fixture}?${new URLSearchParams({ href })}`);
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 30_000 }); await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 90_000 });
const link = page.locator("svg a").first(); const link = page.locator("svg a").first();
await expect(link).toBeAttached(); await expect(link).toBeAttached();
expect( expect(

View File

@ -337,7 +337,7 @@ export async function openUserSettings(
/** Wait until the shell thread is loaded and the message composer is interactive. */ /** Wait until the shell thread is loaded and the message composer is interactive. */
export async function waitForChatComposer(page: Page) { export async function waitForChatComposer(page: Page) {
await expect(page.getByTestId("shell-root")).toHaveAttribute("data-ready", "true", { await expect(page.getByTestId("shell-root")).toHaveAttribute("data-ready", "true", {
timeout: 30_000, timeout: 60_000,
}); });
await expect(page.getByRole("combobox", { name: /^Message$|^Nachricht$/i })).toBeVisible(); await expect(page.getByRole("combobox", { name: /^Message$|^Nachricht$/i })).toBeVisible();
} }

View File

@ -5,6 +5,10 @@ for (const theme of ["light", "dark"] as const) {
test(`Oo looks render every species with the Oo eyes (${theme})`, async ({ page }, testInfo) => { test(`Oo looks render every species with the Oo eyes (${theme})`, async ({ page }, testInfo) => {
await page.goto(`/e2e/fixtures/oo-gallery.html?theme=${theme}`); await page.goto(`/e2e/fixtures/oo-gallery.html?theme=${theme}`);
const gallery = page.getByTestId("oo-gallery"); const gallery = page.getByTestId("oo-gallery");
await expect(gallery).toBeVisible({ timeout: 60_000 });
await expect(gallery.locator("svg[data-oo-species]").first()).toBeVisible({
timeout: 90_000,
});
await expect(gallery.locator("svg[data-oo-species]")).toHaveCount(12); await expect(gallery.locator("svg[data-oo-species]")).toHaveCount(12);
for (const species of ["cat", "dog", "bunny", "bear", "robot", "ghost", "frog", "penguin"]) { for (const species of ["cat", "dog", "bunny", "bear", "robot", "ghost", "frog", "penguin"]) {
await expect(gallery.locator(`svg[data-oo-species="${species}"]`).first()).toBeVisible(); await expect(gallery.locator(`svg[data-oo-species="${species}"]`).first()).toBeVisible();

View File

@ -291,17 +291,18 @@ wait_for_url() {
log "[dry-run] would wait for $label at $url" log "[dry-run] would wait for $label at $url"
return 0 return 0
fi fi
# Probe before checking the clock so a slow host still gets at least one attempt.
while true; do while true; do
now="$(date +%s)"
if (( now - start >= timeout )); then
log "ERROR: $label health check timed out after ${timeout}s (last: ${last:-unknown})"
return 1
fi
if curl -fsS --max-time 5 "$url" >/dev/null 2>&1; then if curl -fsS --max-time 5 "$url" >/dev/null 2>&1; then
log "$label healthy" log "$label healthy"
return 0 return 0
fi fi
last="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 "$url" 2>/dev/null || echo unreachable)" last="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 "$url" 2>/dev/null || echo unreachable)"
now="$(date +%s)"
if (( now - start >= timeout )); then
log "ERROR: $label health check timed out after ${timeout}s (last: ${last:-unknown})"
return 1
fi
sleep 2 sleep 2
done done
} }