diff --git a/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts b/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts index 2d5dc00f7..f93a12601 100644 --- a/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts +++ b/tests/e2e/mock-llm/onboarding/mock-llm-onboarding-regressions.spec.ts @@ -1,4 +1,4 @@ -import test, { expect } from "@playwright/test"; +import test, { expect, type Page } from "@playwright/test"; import { advanceOnboardingToLlmStep, ONBOARDING_BACKEND_STEP, @@ -7,9 +7,107 @@ import { } from "../../support/onboarding-helpers"; import { routeSessionApiKey, SESSION_API_KEY } from "../utils/mock-llm-helpers"; +const READY_CLOUD_MODEL = "openhands/mock-ready-cloud-model"; +const LOCK_TO_CLOUD_WINDOW_KEY = "__AGENT_CANVAS_LOCK_TO_CLOUD__"; + +async function seedLockedCloudCookieBackend(page: Page) { + await page.addInitScript( + ({ lockToCloudKey }) => { + (window as unknown as Record)[lockToCloudKey] = + window.location.origin; + window.localStorage.removeItem("openhands-onboarded"); + window.localStorage.removeItem("openhands-backends"); + window.localStorage.removeItem("openhands-active-backend"); + window.localStorage.removeItem("openhands-backend-health"); + window.sessionStorage.removeItem("openhands-active-backend"); + window.localStorage.setItem("analytics-consent", "false"); + window.localStorage.setItem("openhands-telemetry-consent", "denied"); + window.localStorage.setItem("openhands-telemetry-first-use", "true"); + }, + { lockToCloudKey: LOCK_TO_CLOUD_WINDOW_KEY }, + ); +} + +async function routeReadyCloudProxy(page: Page) { + await page.route("**/api/cloud-proxy", async (route, request) => { + if (request.method() !== "POST") { + await route.fallback(); + return; + } + + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + items: [], + next_page_id: null, + current_org_id: "mock-org", + llm_model: READY_CLOUD_MODEL, + llm_base_url: "", + llm_api_key: null, + llm_api_key_set: true, + search_api_key_set: false, + agent: "CodeActAgent", + language: "en", + user_consents_to_analytics: false, + provider_tokens_set: { github: "" }, + }), + }); + }); +} + test.describe.configure({ mode: "serial" }); test.describe("onboarding recent regressions", () => { + // Regression coverage for PR #1942: a ready Cloud backend already has + // everything onboarding would collect, so the home page should not show + // the first-run modal or persist a synthetic completion marker. + + test("skips first-run onboarding for a ready Cloud backend", async ({ + page, + }) => { + await seedLockedCloudCookieBackend(page); + await routeSessionApiKey(page); + await routeReadyCloudProxy(page); + + await page.goto("/", { waitUntil: "domcontentloaded" }); + + await expect( + page.getByTestId("home-screen"), + "ready Cloud users should land on the home screen", + ).toBeVisible({ timeout: 20_000 }); + await expect( + page.getByTestId("onboarding-modal"), + "ready Cloud users should not see redundant onboarding", + ).toHaveCount(0); + await expect + .poll( + () => + page.evaluate(() => + window.localStorage.getItem("openhands-onboarded"), + ), + { + message: + "ready Cloud suppression should not fake onboarding completion", + }, + ) + .toBeNull(); + await expect + .poll( + () => + page.evaluate(() => { + const raw = window.localStorage.getItem("openhands-backends"); + const backends = raw ? JSON.parse(raw) : []; + return backends.some( + (backend: { id?: unknown; kind?: unknown }) => + backend.id === "locked-cloud" && backend.kind === "cloud", + ); + }), + { message: "test should exercise a locked Cloud backend" }, + ) + .toBe(true); + }); + // Regression coverage for #1085 / PR #1100: errant outside // interactions must not permanently mark onboarding complete.