Files
ThothII/frontend/e2e/ui-visual-review.spec.ts
T

433 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from "@playwright/test";
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../src/test/workspace-fixtures";
// These tests never forward API requests to an installation or invoke a real model.
const workspace = "visual-fixture";
const title = "Clinical research workspace";
const model = { provider: "test", id: "review-model", name: "Review model", reasoning: true };
const revision = workspaceRevisionFixture(workspace);
const evidence = {
id: "evidence:cohort", title: "Cohort definition", kind: "domain", language: "en",
purposes: ["sql_generation"], applies_to: { concepts: ["cohort"], tables: [], columns: [] },
payload: { rule: "# Approved cohort\n\nInclude eligible patients. Review the inclusion criteria before generating SQL." },
provenance: { source_file: "research-guide.md", supporting_excerpts: ["Eligibility is evaluated at admission."] },
review_items: [], file: "curated/domain/cohort.md", status: "active", revision: "1",
};
const evidencePage = {
items: [evidence], total: 1, page: 1, page_size: 25, errors: [], initialized: true,
active_revision: "1", pending_revision: null, source_reviews: [],
location: { repository: "/installation/workspaces", workspace: "/installation/workspaces/clinical-research",
runtime_workspace: "/data/workspaces/clinical-research", host: "installation", command: "tht workspace consolidate",
git_commands: ["git status", "git diff"] },
};
async function fixtures(page: Page, mode: "full" | "embedded" = "full") {
const writes: string[] = [];
await page.route("**/config.js", route => route.fulfill({
contentType: "application/javascript",
body: `window.__THOTHII_CONFIG__ = ${JSON.stringify({ backendBaseUrl: "/api", shell: { mode, defaultLocale: "en" } })};
${mode === "embedded" ? `
document.documentElement.setAttribute("data-bs-theme", "light");
const language = document.createElement("select");
language.className = "omics-language-select";
language.dataset.lang = "en";
language.hidden = true;
document.body.prepend(language);
` : ""}`,
}));
await page.route("http://127.0.0.1:5183/api/**", async route => {
const path = new URL(route.request().url()).pathname.replace(/^\/api/, "");
if (route.request().method() !== "GET") {
writes.push(path);
return route.fulfill({ status: 503, json: { message: "Visual fixture: no writes" } });
}
let body: unknown;
if (path === "/auth/config") body = { mode: "mock", localLogin: false, oidcLogin: false };
else if (path === "/me") body = { issuer: "test", subject: "admin", displayName: "Review administrator", isAdmin: true,
roles: ["admin"], permissions: ["session.use", "session.read_all", "session.manage_all", "database.manage", "workspace.manage", "workspace.secrets.manage", "memory.manage", "evidence.manage", "pi.manage"], csrfToken: null, session: null };
else if (path === "/settings") body = { workspace, provider: model.provider, model: model.id, thinking: "high" };
else if (path === "/models") body = { models: [model] };
else if (path === "/workspaces") body = [workspaceSummaryFixture(workspace, { displayName: title })];
else if (path === `/workspaces/${workspace}`) body = { workspace: canonicalWorkspaceFixture(workspace), revision };
else if (path.endsWith("/runtime-configuration")) body = { workspaceId: workspace, revision, configurationState: "ready", requirements: [] };
else if (path.endsWith("/preprocessing")) body = { schemaVersion: 1, workspaceId: workspace, state: "ready", actionable: true, clearable: true,
detail: "Catalog revision 1 is indexed.", metadataRevision: 1, preprocessedMetadataRevision: 1 };
else if (path === "/workspace-registry/status") body = { branch: "main", ahead: 0, behind: 0, degraded: false };
else if (path.endsWith("/memory/pending")) body = [];
else if (path.endsWith("/memory")) body = { items: [], total: 0, page: 1, page_size: 25 };
else if (path.endsWith("/evidence")) body = evidencePage;
else if (path.includes("/evidence/")) body = { ...evidencePage, item: evidence };
else if (path === "/pi-management/status") body = { ready: true, credentials: "present", hostPlatform: "macos", version: "0.80.3",
config: { provider: model.provider, model: model.id, reasoning: "high" } };
else if (path === "/sessions") body = [{ id: "archived-fixture", status: "finalized", question: "How many eligible patients?", name: "Cohort review",
archived: true, active: false, workspace, created_at: "2026-09-01T12:00:00Z", updated_at: null, author: null, group: null, summary: null }];
else if (path.endsWith("/documents")) body = [{ key: "question", title: "Original question", phase: "F1", format: "markdown", content: "# Cohort review\n\nHow many eligible patients were admitted in 2025?" }];
else if (path === "/health/dwh") body = { ok: true, detail: "Available" };
else if (path === "/catalog/databases") body = [];
else if (path === "/catalog/metrics") body = { scope: "global", databaseId: null, tables: 0, columns: 0, sensitiveColumns: 0, relationships: 0,
descriptionTargets: 0, describedTargets: 0, descriptionCoverage: 0, updatedAt: null };
else if (path === "/catalog/metadata-generation/models") body = { models: [], default: null };
else if (path.endsWith("-runs")) body = [];
else return route.fulfill({ status: 404, json: { message: `Missing visual fixture: ${path}` } });
return route.fulfill({ json: body });
});
return writes;
}
async function navigation(page: Page) {
const trigger = page.getByRole("button", { name: "Navigation", exact: true });
if (await trigger.isVisible()) await trigger.click();
}
async function admin(page: Page, name: string) {
await navigation(page);
const toggle = page.getByRole("button", { name: "Administration", exact: true });
if (await toggle.getAttribute("aria-expanded") !== "true") await toggle.click();
await page.getByRole("button", { name, exact: true }).click();
}
async function inspect(page: Page) {
return page.evaluate(() => {
const visible = (e: Element) => {
const box = e.getBoundingClientRect();
return box.width > 0 && box.height > 0 && getComputedStyle(e).visibility !== "hidden" && !e.closest("[hidden]");
};
return {
overflow: document.documentElement.scrollWidth - innerWidth,
serif: [...document.querySelectorAll("h1,h2,h3,h4,button,label")].filter(visible)
.filter(e => /Fraunces|Georgia|Times|ui-serif/.test(getComputedStyle(e).fontFamily)).map(e => e.textContent?.slice(0,60)),
tiny: [...document.querySelectorAll("h1,h2,h3,h4,button,label,small")].filter(visible)
.filter(e => parseFloat(getComputedStyle(e).fontSize) < 12).map(e => e.textContent?.slice(0,60)),
};
});
}
for (const width of [390, 1280]) {
test(`full shell keeps the visual revision across language and theme changes at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 1000 });
const writes = await fixtures(page);
await page.goto("/");
const header = page.locator(".thot-full-header");
await expect(header).toBeVisible();
await expect(header).toHaveCSS("background-color", "rgb(203, 51, 59)");
expect(await header.locator(".thot-full-header__brand span").evaluate(el =>
getComputedStyle(el).color === getComputedStyle(el.closest("header")!).color,
)).toBe(true);
await page.evaluate(() => document.fonts.ready);
expect(await page.evaluate(() => [...document.fonts].some(font =>
font.family.includes("Manrope Variable") && font.status === "loaded",
))).toBe(true);
const layout = await page.locator(".thot-context-layout").boundingBox();
expect(layout!.x).toBeGreaterThanOrEqual(20);
expect(Math.abs(layout!.x - (width - layout!.x - layout!.width))).toBeLessThan(1);
await admin(page, "Database");
await expect(page.getByRole("heading", { name: "Database management", exact: true })).toBeVisible();
await expect(page.locator(".thot-fleet-ledger__header-status")).toHaveCSS("padding-top", "16px");
await page.getByRole("button", { name: /Working context/ }).click();
const fields = page.locator(".thot-context-fields");
const done = fields.getByRole("button", { name: "Done", exact: true });
await expect(done).toBeVisible();
if (width >= 1280) {
const workspaceBox = await fields.getByRole("combobox", { name: "Workspace", exact: true }).boundingBox();
const modelBox = await fields.getByRole("combobox", { name: "Model", exact: true }).boundingBox();
const doneBox = await done.boundingBox();
expect(Math.abs(workspaceBox!.y - modelBox!.y)).toBeLessThan(1);
expect(Math.abs(modelBox!.y - doneBox!.y)).toBeLessThan(1);
expect(doneBox!.x).toBeGreaterThan(modelBox!.x + modelBox!.width);
for (const name of ["My sessions", "All sessions"]) {
await expect(page.getByRole("tab", { name, exact: true })).toHaveCSS("border-bottom-width", "1px");
}
}
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
await page.screenshot({ path: testInfo.outputPath("full-context-light.png"), animations: "disabled" });
await done.click();
await header.getByRole("combobox").selectOption("it");
await expect(page.getByRole("heading", { name: "Gestione database", exact: true })).toBeVisible();
await expect(page.locator(".thot-administration-eyebrow")).toHaveText("Amministrazione");
await expect(page.locator(".thot-context-summary small")).toHaveText("Contesto di lavoro");
await page.getByRole("button", { name: "Usa il tema scuro", exact: true }).click();
await expect(page.locator(".thot-host")).toHaveAttribute("data-theme", "dark");
await expect(header).toHaveCSS("background-color", "rgb(203, 51, 59)");
await page.screenshot({ path: testInfo.outputPath("full-header-dark.png"), animations: "disabled" });
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
await page.reload();
await expect(page.getByRole("heading", { name: "Gestione database", exact: true })).toBeVisible();
await expect(header.getByRole("combobox")).toHaveValue("it");
await expect(page.locator(".thot-host")).toHaveAttribute("data-theme", "dark");
await page.getByRole("button", { name: "Usa il tema chiaro", exact: true }).click();
await header.getByRole("combobox").selectOption("en");
await expect(page.getByRole("heading", { name: "Database management", exact: true })).toHaveCSS("font-size", "24px");
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
expect(writes).toEqual([]);
});
}
test("session tabs have padded labels and matching one-pixel borders on every side", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1280, height: 1000 });
await fixtures(page);
await page.goto("/?thoth_route=administration%2Fdatabase");
const header = page.locator(".thot-full-header");
for (const locale of ["en", "it"]) {
await header.getByRole("combobox").selectOption(locale);
await expect(page.getByRole("button", { name: locale === "it" ? "Sessione" : "Session", exact: true })).toHaveCount(1);
await expect(page.getByRole("button", { name: /^(New session|Return to session|Nuova sessione|Torna alla sessione)$/ })).toHaveCount(0);
const tabs = page.locator('[role="tablist"]').getByRole("tab");
for (const theme of ["light", "dark"]) {
if (theme === "dark") await header.getByRole("button", { name: locale === "it" ? "Usa il tema scuro" : "Use dark theme", exact: true }).click();
for (const selected of [0, 1]) {
await tabs.nth(selected).click();
await expect(tabs.nth(selected)).toHaveAttribute("aria-selected", "true");
for (const tab of await tabs.all()) {
await expect(tab).toHaveCSS("border-bottom-width", "1px");
const borders = await tab.evaluate(el => {
const css = getComputedStyle(el);
return { widths: [css.borderTopWidth, css.borderRightWidth, css.borderBottomWidth, css.borderLeftWidth],
colors: [css.borderTopColor, css.borderRightColor, css.borderBottomColor, css.borderLeftColor],
paddingX: css.paddingLeft, paddingY: css.paddingTop,
fits: el.scrollHeight <= el.clientHeight };
});
expect(borders.widths).toEqual(["1px", "1px", "1px", "1px"]);
expect(new Set(borders.colors).size).toBe(1);
expect(borders).toMatchObject({ paddingX: "11px", paddingY: "3px", fits: true });
}
}
await page.mouse.move(0, 0);
await page.screenshot({ path: testInfo.outputPath(`session-tabs-${locale}-${theme}.png`), animations: "disabled" });
if (theme === "dark") await header.getByRole("button", { name: locale === "it" ? "Usa il tema chiaro" : "Use light theme", exact: true }).click();
}
}
});
for (const locale of ["en", "it"]) {
test(`login keeps only the main title in ${locale}`, async ({ page }, testInfo) => {
await fixtures(page);
await page.route("**/config.js", route => route.fulfill({ contentType: "application/javascript",
body: `window.__THOTHII_CONFIG__ = ${JSON.stringify({ backendBaseUrl: "/api", shell: { mode: "full", defaultLocale: locale } })};` }));
await page.route("**/api/auth/config", route => route.fulfill({ json: { mode: "local", localLogin: true, oidcLogin: false } }));
await page.route("**/api/me", route => route.fulfill({ status: 401, json: { code: "unauthenticated" } }));
await page.goto("/");
await expect(page.getByRole("heading", { name: locale === "it" ? "Accedi a ThothII" : "Sign in to ThothII" })).toBeVisible();
await expect(page.getByText(locale === "it" ? "Accesso a ThothII" : "ThothII access", { exact: true })).toHaveCount(0);
await expect(page.getByText(locale === "it" ? "Usa l’account della tua installazione per continuare." : "Use your installation account to continue.", { exact: true })).toHaveCount(0);
await page.screenshot({ path: testInfo.outputPath(`login-${locale}.png`), animations: "disabled" });
});
}
test("language selector has no pointer ring and retains keyboard focus in both themes", async ({ page }, testInfo) => {
await fixtures(page);
await page.goto("/");
const language = page.getByRole("combobox", { name: "Interface language" });
for (const theme of ["light", "dark"]) {
if (theme === "dark") await page.getByRole("button", { name: "Use dark theme", exact: true }).click();
await language.click();
await page.keyboard.press("Escape");
await expect(language).toBeFocused();
await expect(language).toHaveCSS("outline-style", "none");
await expect(language).toHaveCSS("box-shadow", "none");
await page.screenshot({ path: testInfo.outputPath(`language-pointer-${theme}.png`), animations: "disabled" });
await page.keyboard.press("Tab");
await page.keyboard.press("Shift+Tab");
await expect(language).toBeFocused();
await expect(language).toHaveCSS("outline-style", "solid");
}
});
for (const route of ["/", "/?thoth_route=administration%2Fdatabase"]) {
test(`Core prompt remains readable after loading ${route}`, async ({ page }, testInfo) => {
const writes = await fixtures(page);
await page.goto(route);
if (route !== "/") {
await navigation(page);
await page.getByRole("button", { name: "Session", exact: true }).click();
}
const prompt = page.getByRole("textbox", { name: "New question", exact: true });
await expect(prompt).toBeVisible();
await prompt.focus();
const readable = async () => {
await expect.poll(() => prompt.evaluate(el => {
const style = getComputedStyle(el);
return el.clientHeight >= parseFloat(style.lineHeight) + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
})).toBe(true);
};
await readable();
await expect(prompt).toHaveCSS("outline-style", "none");
await page.screenshot({ path: testInfo.outputPath("Prompt-focused.png") });
// Full-mode gutters can wrap the empty placeholder on a narrow viewport.
// Compare clearing with that same viewport's initial height, not a single-line constant.
await page.setViewportSize({ width: 390, height: 900 });
await readable();
const emptyHeight = await prompt.evaluate(el => el.clientHeight);
await prompt.fill("First line");
await prompt.press("Shift+Enter");
await prompt.press("a");
await expect(prompt).toHaveValue("First line\na");
await expect.poll(() => prompt.evaluate(el => el.clientHeight)).toBeGreaterThan(40);
const question = "Compare eligible patients across the available cohorts. ".repeat(30);
await prompt.fill(question);
await page.setViewportSize({ width: 390, height: 900 });
await readable();
await expect.poll(() => prompt.evaluate(el => el.clientHeight)).toBe(160);
expect(await prompt.evaluate(el => el.scrollHeight > el.clientHeight)).toBe(true);
await admin(page, "Database");
await navigation(page);
await page.getByRole("button", { name: "Session", exact: true }).click();
await expect(prompt).toHaveValue(question);
await readable();
await page.screenshot({ path: testInfo.outputPath("Prompt-multiline-mobile.png") });
await prompt.fill("");
await readable();
await expect.poll(() => prompt.evaluate(el => el.clientHeight)).toBe(emptyHeight);
expect(writes).toEqual(route === "/" ? ["/runtime/prewarm"] : ["/runtime/prewarm", "/runtime/prewarm"]);
});
}
for (const width of [390, 649, 768, 1280, 1600]) {
test(`all surfaces retain shared typography at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 1000 });
const writes = await fixtures(page);
await page.goto("/");
await expect(page.getByTestId("app-shell")).toBeVisible();
await expect(page.getByRole("button", { name: /Working context/ })).toContainText(title);
for (const name of ["Workspace", "Database", "Memory", "Evidence", "Pi configuration"]) {
await admin(page, name);
const heading = page.getByRole("heading", { name: `${name === "Pi configuration" ? "Pi" : name} management`, exact: true });
await expect(heading).toBeVisible();
await expect(heading).toHaveCSS("font-size", "24px");
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
if (name === "Memory") await expect(page.getByText("No memory cards yet.", { exact: false })).toBeVisible();
if (name === "Database") {
const statuses = page.locator(".thot-fleet-ledger__header-status .thot-fleet-ledger__status-item");
await expect(statuses).toHaveCount(3);
const catalog = await statuses.nth(0).boundingBox();
const operations = await statuses.nth(1).boundingBox();
const metadata = await statuses.nth(2).boundingBox();
const summary = await page.locator(".thot-fleet-ledger__kpi-section > h2").boundingBox();
const summaryBlock = await page.locator(".thot-fleet-ledger__kpi-section").boundingBox();
const navigationBlock = await page.locator(".thot-fleet-ledger__navigation").boundingBox();
const gridBlock = await page.locator(".thot-fleet-ledger__content").boundingBox();
const databasesLabel = await page.locator(".thot-fleet-ledger__navigation").getByText("Databases", { exact: true }).boundingBox();
const tablesLabel = await page.locator(".thot-fleet-ledger__kpi-label").getByText("Tables", { exact: true }).boundingBox();
expect(Math.abs(summaryBlock!.x - gridBlock!.x)).toBeLessThan(1);
expect(Math.abs(navigationBlock!.x - gridBlock!.x)).toBeLessThan(1);
expect(Math.abs(summary!.x - databasesLabel!.x)).toBeLessThan(1);
expect(Math.abs(summary!.x - tablesLabel!.x)).toBeLessThan(1);
const gridLabel = await page.locator(".thot-fleet-grid-toolbar").getByText("Workspace databases", { exact: true }).boundingBox();
expect(Math.abs(summary!.x - gridLabel!.x)).toBeLessThan(1);
expect(Math.abs(summaryBlock!.width - gridBlock!.width)).toBeLessThan(1);
expect(Math.abs(navigationBlock!.width - gridBlock!.width)).toBeLessThan(1);
const contextTrigger = page.getByRole("button", { name: /Working context/ });
await contextTrigger.click();
const contextBody = page.locator(".thot-context-body");
await expect(contextBody).toBeVisible();
await expect(contextBody).not.toContainText("Your last choices take precedence");
await expect(contextBody).not.toContainText("Installation default");
await expect(contextBody).not.toContainText("Last choice");
const contextBlock = await contextBody.boundingBox();
const workspaceSelect = await contextBody.getByRole("combobox", { name: "Workspace", exact: true }).boundingBox();
expect(Math.abs(contextBlock!.x - summaryBlock!.x)).toBeLessThan(1);
expect(Math.abs(workspaceSelect!.x - summary!.x)).toBeLessThan(1);
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
await page.screenshot({ path: testInfo.outputPath("Context-expanded.png") });
await contextBody.getByRole("button", { name: "Done", exact: true }).click();
expect(Math.abs(catalog!.x - summary!.x)).toBeLessThan(1);
const workArea = await page.locator(".thot-fleet-ledger__workspace").boundingBox();
if (workArea!.width >= 768) {
const coverage = await page.locator(".thot-fleet-ledger__kpi-label").filter({ hasText: "Description coverage" }).boundingBox();
expect(Math.abs(metadata!.x - coverage!.x)).toBeLessThan(1);
expect(Math.abs(operations!.x - (catalog!.x + metadata!.x) / 2)).toBeLessThan(1);
}
for (const status of await statuses.all()) {
const label = await status.locator(".thot-fleet-ledger__status-label > span:first-child").boundingBox();
const mark = await status.locator(".thot-fleet-ledger__status-mark").boundingBox();
const value = await status.locator(".thot-fleet-ledger__status-value").boundingBox();
expect(mark!.x - (label!.x + label!.width)).toBeGreaterThanOrEqual(7);
expect(Math.abs(label!.x - value!.x)).toBeLessThan(1);
}
}
if (name === "Evidence") {
const search = await page.getByRole("textbox", { name: "Search Evidence" }).boundingBox();
expect(search!.width).toBeGreaterThan(180);
}
await page.screenshot({ path: testInfo.outputPath(`${name.replaceAll(" ", "-")}.png`) });
}
await page.getByRole("button", { name: "Host maintenance", exact: true }).click();
await expect(page.getByRole("tab", { name: "macOS", exact: true })).toHaveAttribute("aria-selected", "true");
await expect(page.getByRole("region", { name: "Pi operator workflow" })).toHaveCSS("font-size", "16px");
await page.screenshot({ path: testInfo.outputPath("Pi-guide.png") });
await navigation(page);
await page.getByRole("tab", { name: "All sessions", exact: true }).click();
await expect(page.locator(".thot-wordmark--sidebar")).toHaveCSS("font-size", "32px");
await page.getByRole("button", { name: /Archive \(1\)/ }).click();
await page.getByRole("button", { name: "Cohort review", exact: true }).click();
const documents = page.getByRole("complementary", { name: "Session summary" });
await expect(documents).toBeVisible();
await expect(documents.getByRole("heading", { name: "Original question" })).toBeVisible();
const bounds = await documents.boundingBox();
expect(bounds!.width).toBeGreaterThanOrEqual(Math.min(width, 300));
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
await page.screenshot({ path: testInfo.outputPath("Sessions.png") });
await page.getByRole("button", { name: "Close panel", exact: true }).click();
await expect(page.getByRole("textbox", { name: "New question", exact: true })).toBeVisible();
await expect(page.locator(".thot-wordmark--core")).toHaveCSS("font-size", "48px");
await page.screenshot({ path: testInfo.outputPath("Core.png") });
expect(writes).toEqual([]);
});
}
test("theme follows the app, not an unrelated OS preference; keyboard tabs and dirty guard remain", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1280, height: 900 });
const writes = await fixtures(page);
await page.emulateMedia({ colorScheme: "dark" });
await page.goto("/");
await expect(page.getByTestId("app-shell")).toBeVisible();
await expect(page.getByRole("banner", { name: "Application header" })).toBeVisible();
await admin(page, "Pi configuration");
const testButton = page.getByRole("button", { name: "Test catalog default", exact: true });
await expect(testButton).toHaveCSS("background-color", "oklch(0.9985 0.0006 17.2)");
await page.getByRole("button", { name: "Use dark theme", exact: true }).click();
await expect(page.locator(".thot-context-trigger")).toHaveCSS("color", "oklch(0.931 0 90)");
await expect(testButton).toHaveCSS("background-color", "oklch(0.62 0.008 23.2 / 0.3)");
await page.screenshot({ path: testInfo.outputPath("Pi-dark.png") });
const guideButton = page.getByRole("button", { name: "Host maintenance", exact: true });
await guideButton.click();
const mac = page.getByRole("tab", { name: "macOS", exact: true });
await mac.focus(); await page.keyboard.press("ArrowRight");
await expect(page.getByRole("tab", { name: "Windows", exact: true })).toBeFocused();
await expect(page.getByRole("tab", { name: "Windows", exact: true })).toHaveAttribute("aria-selected", "true");
await admin(page, "Memory");
await page.getByRole("button", { name: "New card", exact: true }).click();
await page.getByRole("textbox", { name: "Title", exact: true }).fill("Keep this draft");
await page.getByRole("button", { name: "Session", exact: true }).click();
await expect(page.getByRole("textbox", { name: "Title", exact: true })).toHaveValue("Keep this draft");
await page.screenshot({ path: testInfo.outputPath("Memory-dark-unsaved.png") });
await page.getByRole("button", { name: "Cancel", exact: true }).click();
await page.getByRole("button", { name: "Session", exact: true }).click();
await expect(page.getByRole("textbox", { name: "New question", exact: true })).toBeVisible();
expect(writes).toEqual(["/runtime/prewarm"]);
});
test("host header and left rail leave the application a bounded working area", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 1280, height: 1000 });
await fixtures(page, "embedded");
await page.goto("/");
await expect(page.getByTestId("app-shell")).toBeVisible();
await expect(page.locator(".thot-full-header")).toHaveCount(0);
// Geometry fixture, not a replacement for acceptance inside the real Omics deployment.
await page.addStyleTag({ content: `
body { margin: 0; }
#root { margin-left: 240px; margin-top: 72px; --thoth-app-height: calc(100dvh - 72px); }
.px-4 { padding-left: 2.25rem !important; padding-right: 2.25rem !important; }
` });
await admin(page, "Workspace");
const shell = await page.getByTestId("app-shell").boundingBox();
expect(shell!.x).toBe(240);
expect(shell!.x + shell!.width).toBeLessThanOrEqual(1280);
expect(shell!.y + shell!.height).toBeLessThanOrEqual(1000);
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
await page.screenshot({ path: testInfo.outputPath("Host-bounded-Workspace.png") });
await admin(page, "Evidence");
const search = await page.getByRole("textbox", { name: "Search Evidence" }).boundingBox();
expect(search!.width).toBeGreaterThan(180);
expect(await inspect(page)).toEqual({ overflow: 0, serif: [], tiny: [] });
});