test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
import { expect, test } from "@playwright/test"
|
2026-01-04 21:14:08 +09:00
|
|
|
import { createMockSSEResponse } from "./lib/helpers"
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
|
|
|
|
test.describe("History and Session Restore", () => {
|
|
|
|
|
test("new chat button clears conversation", async ({ page }) => {
|
|
|
|
|
await page.route("**/api/chat", async (route) => {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
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>`,
|
|
|
|
|
"Created your test diagram.",
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.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,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Find and click new chat button
|
2026-01-04 23:05:31 +09:00
|
|
|
const newChatButton = page.locator('[data-testid="new-chat-button"]')
|
|
|
|
|
await expect(newChatButton).toBeVisible({ timeout: 5000 })
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
2026-01-04 23:05:31 +09:00
|
|
|
await newChatButton.click()
|
2026-01-04 22:25:50 +09:00
|
|
|
|
|
|
|
|
// Conversation should be cleared
|
|
|
|
|
await expect(
|
|
|
|
|
page.locator('text="Created your test diagram."'),
|
|
|
|
|
).not.toBeVisible({ timeout: 5000 })
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("chat history sidebar shows past conversations", async ({ page }) => {
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.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])',
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-04 23:05:31 +09:00
|
|
|
// History feature may not exist in all versions - skip if not available
|
2026-01-04 22:25:50 +09:00
|
|
|
const buttonCount = await historyButton.count()
|
2026-01-04 23:05:31 +09:00
|
|
|
if (buttonCount === 0) {
|
|
|
|
|
test.skip()
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-04 22:25:50 +09:00
|
|
|
|
|
|
|
|
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 })
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("conversation persists after page reload", async ({ page }) => {
|
|
|
|
|
await page.route("**/api/chat", async (route) => {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
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>`,
|
|
|
|
|
"This message should persist.",
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.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 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 })
|
|
|
|
|
|
2026-01-04 22:25:50 +09:00
|
|
|
// Verify page is functional after reload
|
|
|
|
|
await expect(
|
|
|
|
|
page.locator('textarea[aria-label="Chat input"]'),
|
|
|
|
|
).toBeVisible({ timeout: 10000 })
|
2026-01-04 23:05:31 +09:00
|
|
|
|
|
|
|
|
// Verify the message persisted after reload
|
|
|
|
|
await expect(
|
|
|
|
|
page.locator('text="This message should persist."'),
|
|
|
|
|
).toBeVisible({ timeout: 10000 })
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("diagram state persists after reload", async ({ page }) => {
|
|
|
|
|
await page.route("**/api/chat", async (route) => {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
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>`,
|
|
|
|
|
"Created a diagram that should be saved.",
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.waitFor({ state: "visible", timeout: 30000 })
|
|
|
|
|
|
|
|
|
|
const chatInput = page.locator('textarea[aria-label="Chat input"]')
|
|
|
|
|
await expect(chatInput).toBeVisible({ timeout: 10000 })
|
|
|
|
|
|
|
|
|
|
// 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 })
|
|
|
|
|
|
|
|
|
|
// Diagram state is typically stored - check iframe is still functional
|
|
|
|
|
const frame = page.frameLocator("iframe")
|
|
|
|
|
await expect(
|
|
|
|
|
frame
|
|
|
|
|
.locator(".geMenubarContainer, .geDiagramContainer, canvas")
|
|
|
|
|
.first(),
|
|
|
|
|
).toBeVisible({ timeout: 30000 })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("can restore from browser back/forward", async ({ page }) => {
|
|
|
|
|
await page.route("**/api/chat", async (route) => {
|
|
|
|
|
await route.fulfill({
|
|
|
|
|
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>`,
|
|
|
|
|
"Testing browser navigation.",
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.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("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 })
|
|
|
|
|
|
|
|
|
|
// Page should be functional
|
|
|
|
|
await expect(chatInput).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 })
|
|
|
|
|
|
|
|
|
|
// Open settings
|
2026-01-04 22:25:50 +09:00
|
|
|
const settingsButton = page.locator('[data-testid="settings-button"]')
|
|
|
|
|
await settingsButton.click()
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
|
|
|
|
// Settings dialog should open
|
|
|
|
|
await expect(
|
|
|
|
|
page.locator('[role="dialog"], [role="menu"], form').first(),
|
|
|
|
|
).toBeVisible({ timeout: 5000 })
|
|
|
|
|
|
|
|
|
|
// Close settings
|
|
|
|
|
await page.keyboard.press("Escape")
|
|
|
|
|
|
|
|
|
|
// Reload
|
|
|
|
|
await page.reload({ waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.waitFor({ state: "visible", timeout: 30000 })
|
|
|
|
|
|
|
|
|
|
// Open settings again
|
2026-01-04 22:25:50 +09:00
|
|
|
await settingsButton.click()
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
|
|
|
|
// Settings should still be accessible
|
|
|
|
|
await expect(
|
|
|
|
|
page.locator('[role="dialog"], [role="menu"], form').first(),
|
|
|
|
|
).toBeVisible({ timeout: 5000 })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("model selection persists", async ({ page }) => {
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.waitFor({ state: "visible", timeout: 30000 })
|
|
|
|
|
|
|
|
|
|
// Find model selector
|
|
|
|
|
const modelSelector = page.locator(
|
|
|
|
|
'button[aria-label*="Model"], [data-testid="model-selector"], button:has-text("Claude")',
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-04 23:05:31 +09:00
|
|
|
// Model selector feature may not exist in all versions - skip if not available
|
2026-01-04 22:25:50 +09:00
|
|
|
const selectorCount = await modelSelector.count()
|
2026-01-04 23:05:31 +09:00
|
|
|
if (selectorCount === 0) {
|
|
|
|
|
test.skip()
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-04 22:25:50 +09:00
|
|
|
|
|
|
|
|
const initialModel = await modelSelector.first().textContent()
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
2026-01-04 22:25:50 +09:00
|
|
|
// Reload page
|
|
|
|
|
await page.reload({ waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.waitFor({ state: "visible", timeout: 30000 })
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
|
2026-01-04 22:25:50 +09:00
|
|
|
// Check model is still selected
|
|
|
|
|
const modelAfterReload = await modelSelector.first().textContent()
|
|
|
|
|
expect(modelAfterReload).toBe(initialModel)
|
test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
2026-01-04 20:47:31 +09:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("handles localStorage quota exceeded gracefully", async ({ page }) => {
|
|
|
|
|
await page.goto("/", { waitUntil: "networkidle" })
|
|
|
|
|
await page
|
|
|
|
|
.locator("iframe")
|
|
|
|
|
.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
|
|
|
|
|
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 })
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
await page.evaluate(() => {
|
|
|
|
|
localStorage.removeItem("test-large-data")
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|