fix(e2e): wait for shell-ready after group chat reload (#94)

* fix(e2e): wait for shell-ready after group chat reload

After voice/connect and full page reload on a group route, bootstrap
can still be in flight. Use waitForChatComposer (data-ready + Message
combobox) instead of asserting the composer immediately, matching #87.

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

* fix(e2e): wait for shell before approval transcript after reload

Transcript messages are not painted until bootstrap finishes; match
group-chats and #87 by waiting on data-ready before asserting durable
approval copy survives reload.

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

* style: format approval-resume e2e import

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

* fix(e2e): wait for archived sidebar after space delete

After deleting a space the Archived section can appear only once
bootstrap finishes; wait for shell-ready after archive reload and assert
the Archived toggle before clicking.

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

* fix(e2e): expect archived sidebar when last bot is archived

After archiving the only bot, the shell is ready without a Message
composer. Wait for data-ready and the Archived toggle instead of
waitForChatComposer on reload.

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

* fix(e2e): wait for shell in composer-paste openComposer helper

Third paste spec failed when Message appeared before bootstrap finished;
reuse waitForChatComposer after navigating to /app.

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

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
cynarAI 2026-10-02 15:50:00 +02:00 committed by GitHub
parent a974f09520
commit d1f7034aa5
4 changed files with 27 additions and 18 deletions

View File

@ -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");

View File

@ -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) => {

View File

@ -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);
});

View File

@ -182,10 +182,18 @@ test("deleting the last bot in a space stays in the app after first use", async
const [chief] = await rpc<Array<{ id: string }>>(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();
});