diff --git a/apps/web/e2e/approval-resume.spec.ts b/apps/web/e2e/approval-resume.spec.ts index 63f6deb..74887f7 100644 --- a/apps/web/e2e/approval-resume.spec.ts +++ b/apps/web/e2e/approval-resume.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { captureScreenshot, completeOnboarding, signup } from "./helpers"; +import { captureScreenshot, completeOnboarding, signup, waitForChatComposer } from "./helpers"; test.describe.configure({ mode: "serial" }); @@ -47,8 +47,9 @@ test("approval input resumes durable work", async ({ page }, testInfo) => { await expect(handledAnswer).toBeVisible(); await expect(page.getByText("Answered: Paris", { exact: true })).toBeVisible(); - await page.reload(); - await expect(resumed).toBeVisible({ timeout: 20_000 }); + await page.reload({ waitUntil: "domcontentloaded" }); + await waitForChatComposer(page); + await expect(resumed).toBeVisible({ timeout: 30_000 }); await expect(handledAnswer).toBeVisible(); await expect(page.getByText("Answered: Paris", { exact: true })).toBeVisible(); await captureScreenshot(page, testInfo, "24-approval-resumed-after-reload"); diff --git a/apps/web/e2e/composer-paste.spec.ts b/apps/web/e2e/composer-paste.spec.ts index 40db103..6574fdf 100644 --- a/apps/web/e2e/composer-paste.spec.ts +++ b/apps/web/e2e/composer-paste.spec.ts @@ -1,15 +1,14 @@ import { expect, type Page, test } from "@playwright/test"; -import { captureScreenshot, completeOnboarding, signup } from "./helpers"; +import { captureScreenshot, completeOnboarding, signup, waitForChatComposer } from "./helpers"; async function openComposer(page: Page) { const stamp = Date.now(); await signup(page, `composer-paste-${stamp}@rakazo.test`, "password12", "Paste Test"); await completeOnboarding(page); - await page.goto("/app"); + await page.goto("/app", { waitUntil: "domcontentloaded" }); await page.waitForURL(/\/app\/[^/]+$/); - const composer = page.getByRole("combobox", { name: /Message/ }); - await expect(composer).toBeVisible(); - return composer; + await waitForChatComposer(page); + return page.getByRole("combobox", { name: /Message/ }); } test("pasting an image into the composer creates an attachment", async ({ page }, testInfo) => { diff --git a/apps/web/e2e/group-chats.spec.ts b/apps/web/e2e/group-chats.spec.ts index 4f1a8b2..e5d449c 100644 --- a/apps/web/e2e/group-chats.spec.ts +++ b/apps/web/e2e/group-chats.spec.ts @@ -8,6 +8,7 @@ import { openNewGroup, rpc, signup, + waitForChatComposer, } from "./helpers"; async function createBot(page: Page, name: string) { @@ -86,9 +87,9 @@ test("create group from + and see two bots in one transcript", async ({ page }, provider: "scripted", apiKey: "fake-group-voice-key", }); - await page.reload(); + await page.reload({ waitUntil: "domcontentloaded" }); await expect(page).toHaveURL(groupUrl); - await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible(); + await waitForChatComposer(page); const groups = await rpc< Array<{ @@ -237,9 +238,7 @@ test("create group from + and see two bots in one transcript", async ({ page }, const cityAsk = page.locator("p").filter({ hasText: /^Which city should I use\?$/ }); if ((await cityAsk.count()) === 0) { await page.reload({ waitUntil: "domcontentloaded" }); - await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible({ - timeout: 15_000, - }); + await waitForChatComposer(page); } await expect(cityAsk).toBeVisible({ timeout: 15_000 }); await page.getByRole("button", { name: "Edit first" }).click(); @@ -289,7 +288,7 @@ test("create group from + and see two bots in one transcript", async ({ page }, await expect(page).toHaveURL(new RegExp(`/app/g/${reviewGroup.id}$`)); await expect(page.getByTestId("transcript")).not.toContainText("Answered: Paris"); releaseReviewSnapshot(); - await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible(); + await waitForChatComposer(page); await page.unroute("**/rpc/threads/get"); await projekte .getByRole("button", { name: /^Draft team/ }) @@ -321,7 +320,7 @@ test("create group from + and see two bots in one transcript", async ({ page }, await captureScreenshot(page, testInfo, "group-settings-mobile"); await rpc(page, "groups/remove", { groupId: reviewGroup.id }); - await page.goto(`/app/g/${reviewGroup.id}`); + await page.goto(`/app/g/${reviewGroup.id}`, { waitUntil: "domcontentloaded" }); await page.waitForURL(/\/app\/(?!g\/)[^/]+$/); - await expect(page.getByRole("combobox", { name: /Message/ })).toBeVisible(); + await waitForChatComposer(page); }); diff --git a/apps/web/e2e/spaces.spec.ts b/apps/web/e2e/spaces.spec.ts index d2746f3..ed3cfa9 100644 --- a/apps/web/e2e/spaces.spec.ts +++ b/apps/web/e2e/spaces.spec.ts @@ -182,10 +182,18 @@ test("deleting the last bot in a space stays in the app after first use", async const [chief] = await rpc>(page, "bots/list", {}); expect(chief).toBeTruthy(); await rpc(page, "bots/archive", { botId: chief!.id }); - await page.reload(); + await page.reload({ waitUntil: "domcontentloaded" }); + await expect(page.getByTestId("shell-root")).toHaveAttribute("data-ready", "true", { + timeout: 30_000, + }); await expect(page).not.toHaveURL(/\/onboarding/); const sidebar = page.locator("aside").first(); + // No active bot thread after archiving the only bot — shell shows Archived, not Message. + await expect(page.getByRole("combobox", { name: "Message" })).toHaveCount(0); + await expect(sidebar.getByRole("button", { name: /^Archived/ })).toBeVisible({ + timeout: 30_000, + }); await openNewSpace(page); const dialog = page.getByRole("dialog", { name: "New space" }); await dialog.getByLabel("Name").fill("Side"); @@ -214,6 +222,8 @@ test("deleting the last bot in a space stays in the app after first use", async .getByRole("button", { name: "Delete", exact: true }) .click(); await expect(sidebar.getByText("Side", { exact: true })).toHaveCount(0); - await sidebar.getByRole("button", { name: /^Archived/ }).click(); + const archivedToggle = sidebar.getByRole("button", { name: /^Archived/ }); + await expect(archivedToggle).toBeVisible({ timeout: 30_000 }); + await archivedToggle.click(); await expect(sidebar.getByText("Chief", { exact: true })).toBeVisible(); });