mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
142 lines
5.0 KiB
TypeScript
142 lines
5.0 KiB
TypeScript
import { test, expect, type Page } from "@playwright/test"
|
|
import { platform } from "node:os"
|
|
|
|
const IS_DARWIN = platform() === "darwin"
|
|
|
|
// Screenshot baselines are captured on Linux CI — font rendering and anti-aliasing
|
|
// differ on macOS, which causes false-positive diffs. Skip the entire suite there.
|
|
if (IS_DARWIN) {
|
|
console.warn("Visual regression tests must be run on CI, skipping on local macOS.")
|
|
test.skip()
|
|
}
|
|
|
|
type Story = {
|
|
id: string
|
|
title: string
|
|
name: string
|
|
}
|
|
|
|
type StoriesIndex = {
|
|
stories?: Record<string, Story>
|
|
entries?: Record<string, Story>
|
|
}
|
|
|
|
const STORYBOOK_URL = "http://localhost:6007"
|
|
|
|
// Fetched once per worker process — cheap HTTP call to the already-running Storybook
|
|
async function fetchStories(): Promise<Story[]> {
|
|
const res = await fetch(`${STORYBOOK_URL}/index.json`).catch(() => fetch(`${STORYBOOK_URL}/stories.json`))
|
|
if (!res.ok) throw new Error(`Storybook index fetch failed: ${res.status} ${res.statusText}`)
|
|
const data = (await res.json()) as StoriesIndex
|
|
const map = data.entries ?? data.stories ?? {}
|
|
return Object.values(map).filter((s) => s.id && !s.id.endsWith("--docs"))
|
|
}
|
|
|
|
async function disableAnimations(page: Page) {
|
|
await page.addStyleTag({
|
|
content: `
|
|
*, *::before, *::after {
|
|
animation: none !important;
|
|
animation-duration: 0s !important;
|
|
animation-delay: 0s !important;
|
|
transition: none !important;
|
|
transition-duration: 0s !important;
|
|
transition-delay: 0s !important;
|
|
}
|
|
`,
|
|
})
|
|
}
|
|
|
|
async function settle(page: Page) {
|
|
const frames = () =>
|
|
page.evaluate(
|
|
() =>
|
|
new Promise<void>((resolve) => {
|
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
|
|
}),
|
|
)
|
|
|
|
await page.evaluate(async () => {
|
|
await document.fonts.ready
|
|
})
|
|
await frames()
|
|
await page.waitForFunction(
|
|
() => {
|
|
const root = document.querySelector("#storybook-root")
|
|
return root && !root.querySelector('pre > code[data-lang]:not([data-lang="mermaid"])')
|
|
},
|
|
undefined,
|
|
{ timeout: 5_000 },
|
|
)
|
|
await frames()
|
|
}
|
|
|
|
// Stories to skip from visual regression (add IDs here if needed)
|
|
// Spinner animation captures at an indeterminate frame, causing flaky diffs.
|
|
// Permission dock config-preloaded has non-deterministic toggle rendering.
|
|
// Sandboxing rows can settle at different scroll heights after settings context updates.
|
|
// Side terminal tabs mount live xterm instances whose websocket error text
|
|
// lands at indeterminate times.
|
|
// The session-dock stability story exists to measure geometry across the
|
|
// working/idle swap and carries a debug toggle button, so it is not a meaningful
|
|
// appearance baseline.
|
|
const SKIP = new Set<string>([
|
|
"chat--chat-view-session-dock-stability",
|
|
"agentmanager--worktree-item-busy",
|
|
"agentmanager--full-screen-diff-agent-edit-scroll",
|
|
"agentmanager--side-terminal-panel-tabs",
|
|
"composite-webview--permission-dock-config-preloaded",
|
|
"settings--sandboxing-allowlist",
|
|
"settings--sandboxing-panel",
|
|
])
|
|
|
|
const DOCS = new Map<string, string[]>([
|
|
[
|
|
"chat--task-header-with-todos",
|
|
[
|
|
"packages/kilo-docs/pages/code-with-ai/features/task-todo-list.md:/docs/img/screenshot-tests/kilo-vscode/visual-regression/chat/task-header-with-todos-chromium-linux.png",
|
|
],
|
|
],
|
|
[
|
|
"composite-webview--todo-write-docs-overview",
|
|
[
|
|
"packages/kilo-docs/pages/code-with-ai/features/task-todo-list.md:/docs/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/todo-write-docs-overview-chromium-linux.png",
|
|
],
|
|
],
|
|
[
|
|
"settings--agent-behaviour-workflows",
|
|
[
|
|
"packages/kilo-docs/pages/customize/workflows.md:/docs/img/screenshot-tests/kilo-vscode/visual-regression/settings/agent-behaviour-workflows-chromium-linux.png",
|
|
],
|
|
],
|
|
])
|
|
|
|
// Generate one test() per story so Playwright's scheduler can distribute
|
|
// them freely across workers — no manual sharding needed.
|
|
// Skip fetching stories on macOS since test.skip() above already marks the file skipped.
|
|
const stories = IS_DARWIN ? [] : (await fetchStories()).filter((s) => !SKIP.has(s.id))
|
|
|
|
for (const story of stories) {
|
|
test(`${story.title} / ${story.name}`, async ({ page }) => {
|
|
for (const ref of DOCS.get(story.id) ?? []) {
|
|
test.info().annotations.push({ type: "docs", description: ref })
|
|
}
|
|
|
|
// Width-suffixed stories cover layouts outside the default sidebar viewport.
|
|
const width = story.id.endsWith("-200") ? 200 : story.id.endsWith("-1280") ? 1280 : 420
|
|
await page.setViewportSize({ width, height: 720 })
|
|
|
|
await page.goto(
|
|
`/iframe.html?id=${story.id}&viewMode=story&globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern`,
|
|
{ waitUntil: "load" },
|
|
)
|
|
await disableAnimations(page)
|
|
await page.waitForSelector("#storybook-root *", { state: "attached" })
|
|
await settle(page)
|
|
|
|
const [component, variant] = story.id.split("--")
|
|
const root = page.locator("#storybook-root")
|
|
await expect(root).toHaveScreenshot(["visual-regression", component!, `${variant!}-chromium-linux.png`])
|
|
})
|
|
}
|