mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-27 00:39:38 +00:00
The `tests` job spent about two of its 2.2 hours in `test:browser`: more than 140 fixture files, each starting its own Vite dev server, run one at a time, many without the protected-teardown wiring or sharing the checkout's default optimizer cache. Every Vite-hosting browser test now uses `createFixtureCache` and `createFixtureShutdown`, and a static `fixture-wiring` test fails when a file hosts a Vite server without that wiring, launches Chromium without the navigation budget, or imports a helper through a specifier that does not resolve. `armFixtureNavigationBudget` lets cold page loads take up to 120 s while action timeouts keep Playwright's defaults. Browser rendering regressions move out of `tests` into a four-way `tests-browser` matrix: each shard runs an interleaved slice serially (`tests/browser/shard.mjs`), and `build` waits for every shard. Locally, `npm run test:browser` still runs one file at a time, since the Git fixtures rebuild one shared Monaco asset directory. The leftover `docs/superpowers/` spec is removed. Validation: the four shards took 26, 32, 37 and 25 minutes and `tests` 12 minutes in the last completed CI run, against about 2 h 14 for `tests` with the browser step; the wiring, navigation-budget and build-order tests pass, and a 20-file slice of heavy fixtures passed 391/391.
113 lines
6.7 KiB
TypeScript
113 lines
6.7 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { after, before, test } from "node:test"
|
|
import { fileURLToPath } from "node:url"
|
|
import { chromium, type Browser, type Page } from "playwright"
|
|
import { createFixtureShutdown } from "./fixture-shutdown"
|
|
import { armFixtureNavigationBudget } from "./fixture-navigation-budget"
|
|
import { createServer, type ViteDevServer } from "vite"
|
|
import solid from "vite-plugin-solid"
|
|
import { prepareGitPrototypeAssets } from "./fixtures/git-history-assets.mjs"
|
|
import { createFixtureCache } from "./fixture-cache"
|
|
|
|
// The real Files panel over the synthetic git-history fixture (`?uninitialized`).
|
|
let server: ViteDevServer, browser: Browser, url: string
|
|
let cache: Awaited<ReturnType<typeof createFixtureCache>>
|
|
before(async () => {
|
|
prepareGitPrototypeAssets()
|
|
cache = await createFixtureCache()
|
|
const shutdown = createFixtureShutdown(cache)
|
|
server = await createServer({ configFile: false, root: fileURLToPath(new URL("../..", import.meta.url)), logLevel: "error",
|
|
cacheDir: cache.cacheDir,
|
|
publicDir: fileURLToPath(new URL("../../src/renderer/public", import.meta.url)),
|
|
plugins: [shutdown.plugin, solid(), { name: "git-init-fixture", configureServer(s) {
|
|
s.middlewares.use("/fixture", async (_req, res) => {
|
|
res.setHeader("Content-Type", "text/html")
|
|
res.end(await s.transformIndexHtml("/fixture", '<html><body><div id="root"></div><script type="module" src="/tests/browser/fixtures/git-history.tsx"></script></body></html>'))
|
|
})
|
|
} }], resolve: { dedupe: ["solid-js"] }, optimizeDeps: { exclude: ["lucide-solid"] },
|
|
server: { host: "127.0.0.1", port: 0, hmr: false, watch: null },
|
|
})
|
|
shutdown.own(server)
|
|
await server.listen()
|
|
url = `http://127.0.0.1:${(server.httpServer!.address() as { port: number }).port}/fixture?uninitialized`
|
|
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
|
|
armFixtureNavigationBudget(browser)
|
|
})
|
|
after(async () => { await browser?.close(); await server?.close(); await cache?.dispose() })
|
|
|
|
const calls = (page: Page, kind: string) => page.evaluate(kind => (window as any).fixture.calls.filter((call: any) => call.kind === kind), kind)
|
|
|
|
async function openUninitializedChanges(page: Page) {
|
|
await page.goto(url)
|
|
await page.getByRole("treeitem", { name: "package.json", exact: true }).waitFor()
|
|
await page.getByRole("button", { name: "Changements", exact: true }).click()
|
|
await page.getByText("Ce dossier n'est pas un dépôt Git.", { exact: true }).waitFor()
|
|
}
|
|
|
|
test("a plain folder's Changes offer Initialize Git, then open on the new repository without reload", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } })
|
|
const errors: string[] = []
|
|
page.on("pageerror", error => errors.push(error.message))
|
|
try {
|
|
await openUninitializedChanges(page)
|
|
assert.equal((await calls(page, "status")).length, 0, "no Git reads for a known non-repository")
|
|
// Commits shows the same non-blocking action.
|
|
await page.getByRole("button", { name: "Commits", exact: true }).click()
|
|
await page.getByRole("button", { name: "Initialiser Git", exact: true }).waitFor()
|
|
assert.equal((await calls(page, "history")).length, 0)
|
|
await page.getByRole("button", { name: "Changements", exact: true }).click()
|
|
|
|
const projectReads = (await calls(page, "project")).length
|
|
await page.getByRole("button", { name: "Initialiser Git", exact: true }).click()
|
|
await page.locator(".git-panel-file-row").first().waitFor()
|
|
assert.deepEqual(await calls(page, "git-init"), [{ kind: "git-init", path: "/CodeNomad" }])
|
|
assert.ok((await calls(page, "status")).length >= 1)
|
|
assert.ok((await calls(page, "project")).length > projectReads, "the native project identity is reloaded after initialization")
|
|
assert.equal(await page.getByText("Ce dossier n'est pas un dépôt Git.", { exact: true }).count(), 0)
|
|
await page.getByText("Dépôt Git initialisé", { exact: true }).waitFor()
|
|
assert.equal(await page.locator(".git-panel-branch strong").textContent(), "feat/git-history")
|
|
assert.deepEqual(errors, [])
|
|
} finally { await page.close() }
|
|
})
|
|
|
|
test("an unsupported runtime keeps the empty state and is reported once, without replay", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } })
|
|
try {
|
|
await openUninitializedChanges(page)
|
|
await page.evaluate(async () => {
|
|
const { serverApi } = await import("/src/lib/api-client.ts" as string)
|
|
const { HttpResponseError } = await import("/src/lib/retryable-file-search.ts" as string)
|
|
serverApi.initializeWorkspaceGit = async (_id: string, directory: string) => {
|
|
;(window as any).fixture.calls.push({ kind: "git-init", path: directory })
|
|
throw new HttpResponseError("VCS provider does not support initialization", 501, null, "git_init_unsupported")
|
|
}
|
|
})
|
|
const worktreeReads = (await calls(page, "worktrees")).length
|
|
await page.getByRole("button", { name: "Initialiser Git", exact: true }).click()
|
|
await page.getByText("Cette version d'OpenCode ne peut pas initialiser de dépôt Git ici.", { exact: true }).waitFor()
|
|
await page.waitForFunction(reads => (window as any).fixture.calls.filter((call: any) => call.kind === "worktrees").length > reads, worktreeReads)
|
|
await page.getByRole("button", { name: "Initialiser Git", exact: true }).and(page.locator(":enabled")).waitFor()
|
|
assert.equal((await calls(page, "git-init")).length, 1)
|
|
assert.equal((await calls(page, "status")).length, 0)
|
|
await page.getByText("Ce dossier n'est pas un dépôt Git.", { exact: true }).waitFor()
|
|
} finally { await page.close() }
|
|
})
|
|
|
|
test("shared workspace refusals without a code are localized, never the server's English text", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } })
|
|
try {
|
|
await openUninitializedChanges(page)
|
|
await page.evaluate(async () => {
|
|
const { serverApi } = await import("/src/lib/api-client.ts" as string)
|
|
const { HttpResponseError } = await import("/src/lib/retryable-file-search.ts" as string)
|
|
serverApi.initializeWorkspaceGit = async (_id: string, directory: string) => {
|
|
;(window as any).fixture.calls.push({ kind: "git-init", path: directory })
|
|
throw new HttpResponseError("Worktree deletion is in progress", 409, null)
|
|
}
|
|
})
|
|
await page.getByRole("button", { name: "Initialiser Git", exact: true }).click()
|
|
await page.getByText("Ce dossier est en cours de suppression.", { exact: true }).waitFor()
|
|
assert.equal(await page.getByText(/Worktree deletion is in progress/).count(), 0)
|
|
assert.equal((await calls(page, "git-init")).length, 1)
|
|
} finally { await page.close() }
|
|
})
|