* Fix red main: mobile group kind, i18n catalogs, project picker check Main CI failed on typecheck, unit tests and web E2E after the last two direct pushes. MobileGroup now carries the optional chat kind the mobile screens read, the Lingui catalogs are re-extracted with German strings for the new voice texts, the project overview plus icon meets the UI scale guard, and the sidebar E2E expects the renamed project lead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R8L4XgqjAemFR2remB5hVY * Format files merged from main Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs * Align E2E with the canvas close button and per-chat drafts The in-app browser now closes from the side panel header, and each chat keeps its own unsent draft. The group chat test waits for the settings save to land before switching chats, so the switch no longer races the panel closing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wy1md9QE1ayJbDCwJ4vAQs --------- Co-authored-by: Claude <noreply@anthropic.com>
163 lines
6.6 KiB
TypeScript
163 lines
6.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
|
|
|
|
test("the in-app browser previews a page and follows it back to the browser", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await signup(page, `in-app-browser-${Date.now()}@rakazo.test`, "password12", "Previewer");
|
|
await completeOnboarding(page);
|
|
// Context-wide so the "Open in browser" tab is served too.
|
|
await page.context().route("https://preview.example/**", (route) =>
|
|
route.fulfill({
|
|
contentType: "text/html",
|
|
body: `<!doctype html><title>Preview</title><body style="font:16px system-ui;padding:24px">
|
|
<h1>Live preview</h1><p>Rendered inside the app.</p>`,
|
|
}),
|
|
);
|
|
|
|
await page.getByRole("button", { name: "Browser" }).click();
|
|
const browser = page.getByTestId("in-app-browser");
|
|
await browser.getByTestId("in-app-browser-mode-local").click();
|
|
const address = browser.getByRole("textbox", { name: "Address" });
|
|
await expect(address).toBeFocused();
|
|
|
|
await address.fill("not an address");
|
|
await address.press("Enter");
|
|
await expect(address).toHaveAttribute("aria-invalid", "true");
|
|
|
|
await address.fill("preview.example/app");
|
|
await address.press("Enter");
|
|
await expect(address).toHaveValue("https://preview.example/app");
|
|
const frame = browser.getByTestId("in-app-browser-frame");
|
|
await expect(frame).toHaveAttribute("src", "https://preview.example/app");
|
|
await expect(
|
|
page.frameLocator('[data-testid="in-app-browser-frame"]').getByRole("heading"),
|
|
).toHaveText("Live preview");
|
|
await captureScreenshot(page, testInfo, "in-app-browser");
|
|
|
|
const popup = page.waitForEvent("popup");
|
|
await browser.getByRole("button", { name: "Open in browser" }).click();
|
|
expect((await popup).url()).toBe("https://preview.example/app");
|
|
|
|
await page.getByTestId("side-panel").getByRole("button", { name: "Close panel" }).click();
|
|
await expect(browser).toHaveCount(0);
|
|
// The last address comes back when the browser reopens.
|
|
await page.getByRole("button", { name: "Browser" }).click();
|
|
await page.getByTestId("in-app-browser-mode-local").click();
|
|
await expect(page.getByTestId("in-app-browser-frame")).toHaveAttribute(
|
|
"src",
|
|
"https://preview.example/app",
|
|
);
|
|
});
|
|
|
|
test("on desktop the page is a native view glued to the panel", async ({ page }) => {
|
|
await page.addInitScript(() => {
|
|
const calls: Array<[string, unknown?]> = [];
|
|
let listener: ((state: unknown) => void) | undefined;
|
|
let state = {
|
|
url: "",
|
|
title: "",
|
|
loading: false,
|
|
canGoBack: false,
|
|
canGoForward: false,
|
|
error: null as string | null,
|
|
};
|
|
const record = (name: string) => async (value?: unknown) => {
|
|
calls.push([name, value]);
|
|
};
|
|
Object.assign(window, { __browserCalls: calls });
|
|
Object.defineProperty(window, "rakazoDesktop", {
|
|
value: {
|
|
platform: "linux",
|
|
window: {
|
|
close: async () => {},
|
|
minimize: async () => {},
|
|
toggleMaximize: async () => {},
|
|
state: async () => ({ minimized: false, maximized: false, fullScreen: false }),
|
|
},
|
|
oauth: { onCallback: () => () => {} },
|
|
update: {
|
|
state: async () => ({ phase: "unsupported", currentVersion: "0.0.0" }),
|
|
check: async () => ({ phase: "unsupported", currentVersion: "0.0.0" }),
|
|
download: async () => ({ phase: "unsupported", currentVersion: "0.0.0" }),
|
|
install: async () => ({ phase: "unsupported", currentVersion: "0.0.0" }),
|
|
},
|
|
browserPreview: {
|
|
state: async () => state,
|
|
onState: (next: (value: unknown) => void) => {
|
|
listener = next;
|
|
return () => {
|
|
listener = undefined;
|
|
};
|
|
},
|
|
navigate: async (url: string) => {
|
|
calls.push(["navigate", url]);
|
|
state = { ...state, url, title: "Dev server", loading: false, canGoBack: true };
|
|
listener?.(state);
|
|
},
|
|
show: record("show"),
|
|
hide: record("hide"),
|
|
back: record("back"),
|
|
forward: record("forward"),
|
|
reload: record("reload"),
|
|
stop: record("stop"),
|
|
openDevTools: record("openDevTools"),
|
|
},
|
|
},
|
|
});
|
|
});
|
|
await signup(page, `native-browser-${Date.now()}@rakazo.test`, "password12", "Desktop");
|
|
await completeOnboarding(page);
|
|
const calls = () =>
|
|
page.evaluate(
|
|
() => (window as unknown as { __browserCalls: Array<[string, unknown?]> }).__browserCalls,
|
|
);
|
|
|
|
await page.getByRole("button", { name: "Browser" }).click();
|
|
const browser = page.getByTestId("in-app-browser");
|
|
await browser.getByTestId("in-app-browser-mode-local").click();
|
|
await browser.getByRole("textbox", { name: "Address" }).fill("localhost:5173");
|
|
await browser.getByRole("textbox", { name: "Address" }).press("Enter");
|
|
await expect.poll(calls).toContainEqual(["navigate", "http://localhost:5173/"]);
|
|
await expect(browser.getByRole("button", { name: "Back" })).toBeEnabled();
|
|
await expect(browser.getByRole("button", { name: "Forward" })).toBeDisabled();
|
|
await expect(browser.getByTestId("in-app-browser-frame")).toHaveCount(0);
|
|
|
|
// The reserved page area and the native view's bounds stay identical.
|
|
const viewport = browser.getByTestId("in-app-browser-viewport");
|
|
await expect
|
|
.poll(async () => {
|
|
const box = (await viewport.boundingBox())!;
|
|
const shown = (await calls()).filter(([name]) => name === "show").at(-1)?.[1];
|
|
return (
|
|
JSON.stringify(shown) ===
|
|
JSON.stringify({
|
|
x: Math.round(box.x),
|
|
y: Math.round(box.y),
|
|
width: Math.round(box.width),
|
|
height: Math.round(box.height),
|
|
})
|
|
);
|
|
})
|
|
.toBe(true);
|
|
|
|
await browser.getByRole("button", { name: "Back" }).click();
|
|
await browser.getByRole("button", { name: "Reload" }).click();
|
|
await browser.getByRole("button", { name: "Developer tools" }).click();
|
|
await expect
|
|
.poll(async () => (await calls()).map(([name]) => name))
|
|
.toEqual(expect.arrayContaining(["back", "reload", "openDevTools"]));
|
|
|
|
// App menus float above the page, so the native view steps aside while one is open.
|
|
const before = (await calls()).length;
|
|
await page.getByTestId("create-menu-trigger").click();
|
|
await expect
|
|
.poll(async () => (await calls()).slice(before).map(([name]) => name))
|
|
.toContain("hide");
|
|
await page.keyboard.press("Escape");
|
|
await expect.poll(async () => (await calls()).at(-1)?.[0]).toBe("show");
|
|
|
|
await page.getByTestId("side-panel").getByRole("button", { name: "Close panel" }).click();
|
|
await expect.poll(async () => (await calls()).at(-1)?.[0]).toBe("hide");
|
|
});
|