refactor: add shared fixtures and test.step() patterns

- Add tests/e2e/lib/fixtures.ts with shared test helpers
- Add tests/e2e/fixtures/diagrams.ts with XML test data
- Add expectBeforeAndAfterReload() helper for persistence tests
- Add test.step() for better test reporting in complex tests
- Consolidate mock helpers into fixtures module
- Reduce code duplication across 17 test files
This commit is contained in:
dayuan.jiang
2026-01-05 00:37:40 +09:00
parent 948cc4666d
commit 828bf43e31
19 changed files with 662 additions and 815 deletions

View File

@@ -1,4 +1,16 @@
import { expect, test } from "@playwright/test"
import { SINGLE_BOX_XML } from "./fixtures/diagrams"
import {
expect,
expectBeforeAndAfterReload,
getChatInput,
getIframe,
getIframeContent,
openSettings,
sendMessage,
test,
waitForComplete,
waitForText,
} from "./lib/fixtures"
import { createMockSSEResponse } from "./lib/helpers"
test.describe("History and Session Restore", () => {
@@ -8,55 +20,43 @@ test.describe("History and Session Restore", () => {
status: 200,
contentType: "text/event-stream",
body: createMockSSEResponse(
`<mxCell id="node" value="Test" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="100" height="50" as="geometry"/></mxCell>`,
SINGLE_BOX_XML,
"Created your test diagram.",
),
})
})
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
const chatInput = page.locator('textarea[aria-label="Chat input"]')
await expect(chatInput).toBeVisible({ timeout: 10000 })
// Send a message
await chatInput.fill("Create a test diagram")
await chatInput.press("ControlOrMeta+Enter")
// Wait for response
await expect(
page.locator('text="Created your test diagram."'),
).toBeVisible({
timeout: 15000,
await test.step("create a conversation", async () => {
await sendMessage(page, "Create a test diagram")
await waitForText(page, "Created your test diagram.")
})
// Find and click new chat button
const newChatButton = page.locator('[data-testid="new-chat-button"]')
await expect(newChatButton).toBeVisible({ timeout: 5000 })
await test.step("click new chat button", async () => {
const newChatButton = page.locator(
'[data-testid="new-chat-button"]',
)
await expect(newChatButton).toBeVisible({ timeout: 5000 })
await newChatButton.click()
})
await newChatButton.click()
// Conversation should be cleared
await expect(
page.locator('text="Created your test diagram."'),
).not.toBeVisible({ timeout: 5000 })
await test.step("verify conversation is cleared", async () => {
await expect(
page.locator('text="Created your test diagram."'),
).not.toBeVisible({ timeout: 5000 })
})
})
test("chat history sidebar shows past conversations", async ({ page }) => {
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Look for history/sidebar button that is enabled
const historyButton = page.locator(
'button[aria-label*="History"]:not([disabled]), button:has(svg.lucide-history):not([disabled]), button:has(svg.lucide-menu):not([disabled]), button:has(svg.lucide-sidebar):not([disabled]), button:has(svg.lucide-panel-left):not([disabled])',
)
// History feature may not exist in all versions - skip if not available
const buttonCount = await historyButton.count()
if (buttonCount === 0) {
test.skip()
@@ -64,10 +64,7 @@ test.describe("History and Session Restore", () => {
}
await historyButton.first().click()
// Wait for sidebar/panel to appear or verify page still works
await expect(
page.locator('textarea[aria-label="Chat input"]'),
).toBeVisible({ timeout: 3000 })
await expect(getChatInput(page)).toBeVisible({ timeout: 3000 })
})
test("conversation persists after page reload", async ({ page }) => {
@@ -76,44 +73,30 @@ test.describe("History and Session Restore", () => {
status: 200,
contentType: "text/event-stream",
body: createMockSSEResponse(
`<mxCell id="persist" value="Persistent" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="100" height="50" as="geometry"/></mxCell>`,
SINGLE_BOX_XML,
"This message should persist.",
),
})
})
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
const chatInput = page.locator('textarea[aria-label="Chat input"]')
await expect(chatInput).toBeVisible({ timeout: 10000 })
await test.step("create conversation", async () => {
await sendMessage(page, "Create persistent diagram")
await waitForText(page, "This message should persist.")
})
// Send a message
await chatInput.fill("Create persistent diagram")
await chatInput.press("ControlOrMeta+Enter")
// Wait for response
await expect(
page.locator('text="This message should persist."'),
).toBeVisible({ timeout: 15000 })
// Reload page
await page.reload({ waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
// Verify page is functional after reload
await expect(
page.locator('textarea[aria-label="Chat input"]'),
).toBeVisible({ timeout: 10000 })
// Verify the message persisted after reload
await expect(
page.locator('text="This message should persist."'),
).toBeVisible({ timeout: 10000 })
await expectBeforeAndAfterReload(
page,
"conversation message persists",
async () => {
await expect(getChatInput(page)).toBeVisible({ timeout: 10000 })
await expect(
page.locator('text="This message should persist."'),
).toBeVisible({ timeout: 10000 })
},
)
})
test("diagram state persists after reload", async ({ page }) => {
@@ -122,36 +105,22 @@ test.describe("History and Session Restore", () => {
status: 200,
contentType: "text/event-stream",
body: createMockSSEResponse(
`<mxCell id="saved" value="Saved Diagram" style="rounded=1;fillColor=#d5e8d4;" vertex="1" parent="1"><mxGeometry x="150" y="150" width="140" height="70" as="geometry"/></mxCell>`,
SINGLE_BOX_XML,
"Created a diagram that should be saved.",
),
})
})
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
const chatInput = page.locator('textarea[aria-label="Chat input"]')
await expect(chatInput).toBeVisible({ timeout: 10000 })
await sendMessage(page, "Create saveable diagram")
await waitForComplete(page)
// Generate a diagram
await chatInput.fill("Create saveable diagram")
await chatInput.press("ControlOrMeta+Enter")
await expect(page.locator('text="Complete"')).toBeVisible({
timeout: 15000,
})
// Reload
await page.reload({ waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Diagram state is typically stored - check iframe is still functional
const frame = page.frameLocator("iframe")
const frame = getIframeContent(page)
await expect(
frame
.locator(".geMenubarContainer, .geDiagramContainer, canvas")
@@ -165,88 +134,46 @@ test.describe("History and Session Restore", () => {
status: 200,
contentType: "text/event-stream",
body: createMockSSEResponse(
`<mxCell id="nav" value="Navigation Test" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="50" as="geometry"/></mxCell>`,
SINGLE_BOX_XML,
"Testing browser navigation.",
),
})
})
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
const chatInput = page.locator('textarea[aria-label="Chat input"]')
await expect(chatInput).toBeVisible({ timeout: 10000 })
await sendMessage(page, "Test navigation")
await waitForText(page, "Testing browser navigation.")
// Send a message
await chatInput.fill("Test navigation")
await chatInput.press("ControlOrMeta+Enter")
await expect(
page.locator('text="Testing browser navigation."'),
).toBeVisible({
timeout: 15000,
})
// Navigate to about page
await page.goto("/about", { waitUntil: "networkidle" })
// Go back
await page.goBack({ waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Page should be functional
await expect(chatInput).toBeVisible({ timeout: 10000 })
await expect(getChatInput(page)).toBeVisible({ timeout: 10000 })
})
test("settings are restored after reload", async ({ page }) => {
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Open settings
const settingsButton = page.locator('[data-testid="settings-button"]')
await settingsButton.click()
// Settings dialog should open
await expect(
page.locator('[role="dialog"], [role="menu"], form').first(),
).toBeVisible({ timeout: 5000 })
// Close settings
await openSettings(page)
await page.keyboard.press("Escape")
// Reload
await page.reload({ waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Open settings again
await settingsButton.click()
// Settings should still be accessible
await expect(
page.locator('[role="dialog"], [role="menu"], form').first(),
).toBeVisible({ timeout: 5000 })
await openSettings(page)
})
test("model selection persists", async ({ page }) => {
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Find model selector
const modelSelector = page.locator(
'button[aria-label*="Model"], [data-testid="model-selector"], button:has-text("Claude")',
)
// Model selector feature may not exist in all versions - skip if not available
const selectorCount = await modelSelector.count()
if (selectorCount === 0) {
test.skip()
@@ -255,39 +182,28 @@ test.describe("History and Session Restore", () => {
const initialModel = await modelSelector.first().textContent()
// Reload page
await page.reload({ waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Check model is still selected
const modelAfterReload = await modelSelector.first().textContent()
expect(modelAfterReload).toBe(initialModel)
})
test("handles localStorage quota exceeded gracefully", async ({ page }) => {
await page.goto("/", { waitUntil: "networkidle" })
await page
.locator("iframe")
.waitFor({ state: "visible", timeout: 30000 })
await getIframe(page).waitFor({ state: "visible", timeout: 30000 })
// Fill up localStorage (simulate quota exceeded scenario)
await page.evaluate(() => {
try {
// This might throw if quota is exceeded
const largeData = "x".repeat(5 * 1024 * 1024) // 5MB
const largeData = "x".repeat(5 * 1024 * 1024)
localStorage.setItem("test-large-data", largeData)
} catch {
// Expected to fail on some browsers
}
})
// App should still function
const chatInput = page.locator('textarea[aria-label="Chat input"]')
await expect(chatInput).toBeVisible({ timeout: 10000 })
await expect(getChatInput(page)).toBeVisible({ timeout: 10000 })
// Clean up
await page.evaluate(() => {
localStorage.removeItem("test-large-data")
})