From 5896738778163bbbcf504d7a37e0a0a54ccffa22 Mon Sep 17 00:00:00 2001 From: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 Date: Tue, 30 Jun 2026 11:26:31 -0400 Subject: [PATCH 1/2] fix(ci): restore E2E flakiness fixes for pgschema, docker-pull, and spec timing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three chronic Desktop E2E Integration failure modes in one pass: Bucket A (regression): restore PGSCHEMA_PLAN_* env vars at both pgschema apply sites. PR #1070 (e3736f08b) added these vars so pgschema uses the already-running docker Postgres instead of downloading an embedded binary from Maven Central at test time. PR #963 (ff824a365, merged the next day) silently clobbered them via a stale-base rebase. The embedded-PG download is the transient 'no version found matching 17.5.0' flake that matches Will's 'flaky for ~2 weeks' window exactly. Bucket B (transient infra): wrap both 'docker compose up' service-start steps in a 3-attempt retry loop with backoff. A single Docker Hub 500 previously killed the entire job before any test ran. Bucket C (spec timing race): stabilize the 'Switch to Default' step in persona-env-vars.spec.ts. The radix dropdown re-renders mid-click (detaches from DOM) when switching back to Default after Anthropic. Extract the defaultOption locator and await expect(defaultOption).toBeVisible() before clicking — Playwright's auto-wait resolves the 'element is not stable' timeout that was failing all 3 retries. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- .github/workflows/ci.yml | 40 ++++++++++++++++++++-- desktop/tests/e2e/persona-env-vars.spec.ts | 7 ++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4942cd2a64..bcf6d989fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -305,7 +305,18 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - name: Start integration services - run: docker compose up -d postgres redis minio minio-init + run: | + for attempt in 1 2 3; do + if docker compose up -d postgres redis minio minio-init; then + break + fi + if [ "$attempt" -eq 3 ]; then + echo "docker compose up failed after 3 attempts" >&2 + exit 1 + fi + echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 + sleep $((attempt * 5)) + done - name: Get pnpm store directory id: pnpm-cache run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" @@ -380,6 +391,13 @@ jobs: PGUSER: buzz PGPASSWORD: buzz_dev PGDATABASE: buzz + # Use the already-running docker postgres for desired-state planning instead of + # downloading an embedded Postgres from Maven Central (transient-fetch flake source). + PGSCHEMA_PLAN_HOST: localhost + PGSCHEMA_PLAN_PORT: "5432" + PGSCHEMA_PLAN_DB: buzz + PGSCHEMA_PLAN_USER: buzz + PGSCHEMA_PLAN_PASSWORD: buzz_dev run: | ./bin/pgschema apply --file schema/schema.sql --auto-approve docker exec -i -e PGPASSWORD=buzz_dev buzz-postgres \ @@ -471,7 +489,18 @@ jobs: with: save-if: ${{ github.event_name != 'pull_request' }} - name: Start integration services - run: docker compose up -d postgres redis minio minio-init + run: | + for attempt in 1 2 3; do + if docker compose up -d postgres redis minio minio-init; then + break + fi + if [ "$attempt" -eq 3 ]; then + echo "docker compose up failed after 3 attempts" >&2 + exit 1 + fi + echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 + sleep $((attempt * 5)) + done - name: Wait for integration services run: | wait_healthy() { @@ -513,6 +542,13 @@ jobs: PGUSER: buzz PGPASSWORD: buzz_dev PGDATABASE: buzz + # Use the already-running docker postgres for desired-state planning instead of + # downloading an embedded Postgres from Maven Central (transient-fetch flake source). + PGSCHEMA_PLAN_HOST: localhost + PGSCHEMA_PLAN_PORT: "5432" + PGSCHEMA_PLAN_DB: buzz + PGSCHEMA_PLAN_USER: buzz + PGSCHEMA_PLAN_PASSWORD: buzz_dev run: | ./bin/pgschema apply --file schema/schema.sql --auto-approve docker exec -i -e PGPASSWORD=buzz_dev buzz-postgres \ diff --git a/desktop/tests/e2e/persona-env-vars.spec.ts b/desktop/tests/e2e/persona-env-vars.spec.ts index 24a28b80ed..d8d72e1041 100644 --- a/desktop/tests/e2e/persona-env-vars.spec.ts +++ b/desktop/tests/e2e/persona-env-vars.spec.ts @@ -334,10 +334,11 @@ test("persona model options follow the selected LLM provider", async ({ const llmProviderMenu = page.getByRole("menu").filter({ has: page.getByRole("menuitemradio", { name: "OpenAI", exact: true }), }); - await llmProviderMenu + const defaultOption = llmProviderMenu .last() - .getByRole("menuitemradio", { name: "Default", exact: true }) - .click(); + .getByRole("menuitemradio", { name: "Default", exact: true }); + await expect(defaultOption).toBeVisible(); + await defaultOption.click(); await expect(model).toBeVisible(); await expect(model).toContainText("Default model"); }); From d6d8cd0e1c1b64c7a0b7183f7b241be421ceb5a1 Mon Sep 17 00:00:00 2001 From: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 Date: Tue, 30 Jun 2026 11:34:43 -0400 Subject: [PATCH 2/2] fix(e2e): stabilize 'Switch to Default' provider step against radix detach race The 'persona model options follow the selected LLM provider' test was failing on the 'Switch to Default' step with 'element is not stable' / 'element was detached from the DOM'. The prior fix added await expect(defaultOption).toBeVisible() before .click(), but Playwright's .click() already auto-waits for visibility and stability, so the assertion did not change the actionability window. A separate visibility assertion can pass on one mounted instance, then the menu item can detach before the subsequent click lands while Radix is still re-rendering the collection after the provider change. Fix: mirror the OpenAI and Anthropic steps in the same test, which both use the bare page.getByRole('menuitemradio').click() pattern and pass reliably. After await llmProvider.click(), call waitForAnimations(page) to let Radix finish animating/re-mounting the menu collection, then locate the 'Default' menuitemradio fresh at click-time. No stale handle crosses the open/re-render boundary. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- desktop/tests/e2e/persona-env-vars.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/desktop/tests/e2e/persona-env-vars.spec.ts b/desktop/tests/e2e/persona-env-vars.spec.ts index d8d72e1041..a6f1f4f419 100644 --- a/desktop/tests/e2e/persona-env-vars.spec.ts +++ b/desktop/tests/e2e/persona-env-vars.spec.ts @@ -331,14 +331,16 @@ test("persona model options follow the selected LLM provider", async ({ // Switch to Default (no explicit provider) — model resets to "Default model". await llmProvider.click(); - const llmProviderMenu = page.getByRole("menu").filter({ - has: page.getByRole("menuitemradio", { name: "OpenAI", exact: true }), - }); - const defaultOption = llmProviderMenu - .last() - .getByRole("menuitemradio", { name: "Default", exact: true }); - await expect(defaultOption).toBeVisible(); - await defaultOption.click(); + // Wait for Radix menu animations to settle before locating the menu item. + // The prior approach held a filtered locator across the open→animate boundary + // and clicked a node that Radix was still re-mounting, producing + // "element is not stable" / "element was detached from the DOM" failures. + // Matching the OpenAI/Anthropic steps above: wait for animations, then + // locate fresh at click-time so no stale reference crosses the re-render. + await waitForAnimations(page); + await page + .getByRole("menuitemradio", { name: "Default", exact: true }) + .click(); await expect(model).toBeVisible(); await expect(model).toContainText("Default model"); });