mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-26 08:11:53 +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.
364 lines
21 KiB
TypeScript
364 lines
21 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { after, before, test } from "node:test"
|
|
import { fileURLToPath } from "node:url"
|
|
import { mkdir } from "node:fs/promises"
|
|
import { join } from "node:path"
|
|
import { chromium, type Browser } from "playwright"
|
|
import { createFixtureCache } from "./fixture-cache"
|
|
import { createFixtureShutdown } from "./fixture-shutdown"
|
|
import { armFixtureNavigationBudget } from "./fixture-navigation-budget"
|
|
import { createServer, type ViteDevServer } from "vite"
|
|
import solid from "vite-plugin-solid"
|
|
|
|
let server: ViteDevServer, browser: Browser, url: string
|
|
|
|
before(async () => {
|
|
const cache = await createFixtureCache(), shutdown = createFixtureShutdown(cache)
|
|
server = await createServer({
|
|
configFile: false,
|
|
root: fileURLToPath(new URL("../..", import.meta.url)),
|
|
logLevel: "error", cacheDir: cache.cacheDir,
|
|
plugins: [shutdown.plugin, solid(), { name: "plugin-controls-fixture", configureServer(vite) {
|
|
vite.middlewares.use("/fixture", async (_request, response) => {
|
|
response.setHeader("Content-Type", "text/html")
|
|
response.end(await vite.transformIndexHtml("/fixture", '<html><body><div id="root"></div><script type="module" src="/tests/browser/fixtures/plugin-controls.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`
|
|
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
|
|
armFixtureNavigationBudget(browser)
|
|
})
|
|
|
|
after(async () => {
|
|
await browser?.close()
|
|
await server?.close()
|
|
})
|
|
|
|
test("V2 plugin controls load on demand and expose explicit Global and Project switches", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 520, height: 900 }, locale: "en-US" })
|
|
const errors: string[] = []
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
await page.goto(url)
|
|
|
|
await page.waitForTimeout(100)
|
|
assert.equal(await page.evaluate(() => (window as any).fixture.reads()), 0, "a hidden plugin section must not load")
|
|
await page.evaluate(() => (window as any).fixture.show())
|
|
await page.getByText("acme.reviewer", { exact: true }).waitFor()
|
|
if (process.env.CODENOMAD_PLUGIN_SCREENSHOTS) {
|
|
await mkdir(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, { recursive: true })
|
|
await page.screenshot({ path: join(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, "plugins-initial.png"), fullPage: true })
|
|
}
|
|
assert.equal(await page.locator(".plugin-control-row").count(), 3)
|
|
assert.equal(await page.locator('.plugin-control-row input[type="checkbox"]').count(), 6)
|
|
assert.deepEqual(await page.locator(".plugin-control-scope-label").allTextContents(), ["Global", "Project"])
|
|
assert.equal(await page.locator(".plugin-controls-description, .plugin-controls-footer, .plugin-control-sub, .plugin-controls-notice").count(), 0)
|
|
assert.equal(await page.locator(".plugin-controls-header button").count(), 1, "only the compact refresh button appears in the header")
|
|
assert.ok(await page.locator(".plugin-control-identity button").count() > 0, "package rows expose a compact native update/check action")
|
|
const refresh = page.locator(".plugin-controls-header button")
|
|
assert.equal(await refresh.isEnabled(), true)
|
|
assert.equal(await refresh.locator(".animate-spin").count(), 0)
|
|
const readsBeforeRefresh = await page.evaluate(() => (window as any).fixture.reads())
|
|
await page.evaluate(() => (window as any).fixture.holdRead())
|
|
await refresh.click()
|
|
await page.waitForFunction((before) => (window as any).fixture.reads() > before, readsBeforeRefresh)
|
|
assert.equal(await refresh.isDisabled(), true)
|
|
assert.equal(await refresh.getAttribute("aria-busy"), "true")
|
|
assert.equal(await refresh.locator(".animate-spin").count(), 1)
|
|
await page.evaluate(() => (window as any).fixture.releaseRead())
|
|
await page.waitForFunction(() => !document.querySelector<HTMLButtonElement>(".plugin-controls-header button")!.disabled)
|
|
assert.equal(await refresh.locator(".animate-spin").count(), 0)
|
|
assert.equal(await page.locator(".plugin-control-tooltip").count(), 0, "details stay out of the resting surface")
|
|
await page.locator('[data-plugin-id="broken.plugin"] .plugin-control-name').hover()
|
|
await page.locator(".plugin-control-tooltip").waitFor()
|
|
assert.match(await page.locator(".plugin-control-tooltip").innerText(), /Failed — Setup failed safely/)
|
|
await page.mouse.move(500, 800)
|
|
await page.locator(".plugin-control-tooltip").waitFor({ state: "hidden" })
|
|
assert.equal(await page.getByText("Runtime inventory", { exact: true }).count(), 0)
|
|
assert.equal(await page.getByText("Configured entries", { exact: true }).count(), 0)
|
|
assert.equal(await page.getByText("opencode.provider.demo", { exact: true }).count(), 0)
|
|
assert.equal(await page.getByText("opencode.prompt.identity", { exact: true }).count(), 0)
|
|
assert.equal(await page.locator('.plugin-control-row input[type="checkbox"]').first().isDisabled(), false)
|
|
|
|
const inherited = page.locator('[data-plugin-id="broken.plugin"]')
|
|
const inheritedGlobal = inherited.locator('[data-scope="global"] input[type="checkbox"]')
|
|
const inheritedProject = inherited.locator('[data-scope="project"] input[type="checkbox"]')
|
|
assert.equal(await inheritedGlobal.isChecked(), true)
|
|
assert.equal(await inheritedProject.isChecked(), true)
|
|
await inheritedProject.click()
|
|
await page.waitForFunction(() => (window as any).fixture.calls.some((call: any) => (
|
|
call.type === "mutation" && call.pluginId === "broken.plugin"
|
|
&& call.scope === "project" && call.enabled === false
|
|
)))
|
|
assert.equal(await inheritedGlobal.isChecked(), true, "a Project override must not change the Global switch")
|
|
assert.equal(await inheritedProject.isChecked(), false)
|
|
|
|
const sleeping = page.locator('[data-plugin-id="sleeping.plugin"]')
|
|
const globalToggle = sleeping.locator('[data-scope="global"] input[type="checkbox"]')
|
|
const projectToggle = sleeping.locator('[data-scope="project"] input[type="checkbox"]')
|
|
assert.equal(await globalToggle.isChecked(), true)
|
|
assert.equal(await projectToggle.isChecked(), false)
|
|
await projectToggle.click()
|
|
await page.waitForFunction(() => (window as any).fixture.calls.some((call: any) => (
|
|
call.type === "mutation" && call.pluginId === "sleeping.plugin"
|
|
&& call.scope === "project" && call.enabled === true
|
|
)))
|
|
assert.equal(await projectToggle.isChecked(), true)
|
|
|
|
const acmeGlobal = page.locator('[data-plugin-id="acme.reviewer"] [data-scope="global"] input[type="checkbox"]')
|
|
await acmeGlobal.click()
|
|
await page.waitForFunction(() => (window as any).fixture.calls.some((call: any) => (
|
|
call.type === "mutation" && call.pluginId === "acme.reviewer"
|
|
&& call.scope === "global" && call.enabled === false
|
|
)))
|
|
|
|
const switchGeometry = await page.evaluate(() => {
|
|
const track = document.querySelector(".plugin-control-row .MuiSwitch-track")!
|
|
const root = document.querySelector(".plugin-control-row .MuiSwitch-root") as HTMLElement | null
|
|
const box = (root ?? track as HTMLElement).getBoundingClientRect()
|
|
return { radius: getComputedStyle(track).borderRadius, width: Math.round(box.width), height: Math.round(box.height) }
|
|
})
|
|
assert.notEqual(switchGeometry.radius, "0px")
|
|
assert.deepEqual([switchGeometry.width, switchGeometry.height], [40, 24])
|
|
|
|
const readsBeforeSessionChange = await page.evaluate(() => (window as any).fixture.reads())
|
|
await page.evaluate(() => (window as any).fixture.switchSession())
|
|
await page.waitForTimeout(100)
|
|
assert.equal(
|
|
await page.evaluate(() => (window as any).fixture.reads()),
|
|
readsBeforeSessionChange,
|
|
"another session in the same worktree must reuse the snapshot",
|
|
)
|
|
|
|
const readsBeforeActivation = await page.evaluate(() => (window as any).fixture.reads())
|
|
await page.evaluate(() => (window as any).fixture.hide())
|
|
await page.waitForFunction(() => !(window as any).fixture.isActive())
|
|
await page.evaluate(() => (window as any).fixture.activateSleepingPlugin())
|
|
await page.waitForTimeout(100)
|
|
assert.equal(
|
|
await page.evaluate(() => (window as any).fixture.reads()),
|
|
readsBeforeActivation,
|
|
"events must not refresh a hidden plugin section",
|
|
)
|
|
await page.evaluate(() => (window as any).fixture.show())
|
|
await page.waitForFunction((before) => (window as any).fixture.reads() > before, readsBeforeActivation)
|
|
const burstReads = await page.evaluate(() => (window as any).fixture.eventBurst())
|
|
assert.equal(burstReads, 2, "an in-flight event burst must coalesce into one trailing refresh")
|
|
assert.deepEqual(errors, [])
|
|
await page.close()
|
|
})
|
|
|
|
test("failed packages without IDs remain actionable and reconcile into loaded plugin rows", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 380, height: 600 }, locale: "en-US" })
|
|
const errors: string[] = []
|
|
page.on("pageerror", error => errors.push(error.message))
|
|
try {
|
|
await page.goto(url)
|
|
await page.evaluate(() => { (window as any).fixture.failedPackagesOnly(); (window as any).fixture.setNarrow(); (window as any).fixture.show() })
|
|
const target = "opencode-gemini-auth@latest"
|
|
const row = page.locator(`[data-package-target="${target}"]`)
|
|
await row.waitFor()
|
|
assert.equal(await page.locator(".plugin-control-row").count(), 2, "duplicate native targets share one recovery row")
|
|
assert.equal(await page.locator('.plugin-control-row input[type="checkbox"]').count(), 0, "a package target must never be used as an activation ID")
|
|
await row.locator(".plugin-control-name").focus()
|
|
await page.locator(".plugin-control-tooltip").waitFor()
|
|
assert.match(await page.locator(".plugin-control-tooltip").innerText(), /Failed — Package failed before exporting a plugin ID/)
|
|
await row.getByRole("button", { name: `Check updates for ${target}`, exact: true }).click()
|
|
await page.getByRole("menuitem", { name: `Check updates for ${target}`, exact: true }).click()
|
|
const update = row.getByRole("button", { name: `Update ${target}`, exact: true })
|
|
await update.waitFor()
|
|
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true)
|
|
if (process.env.CODENOMAD_PLUGIN_SCREENSHOTS) {
|
|
await mkdir(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, { recursive: true })
|
|
await page.screenshot({ path: join(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, "plugins-failed-packages.png"), fullPage: true })
|
|
}
|
|
await update.click()
|
|
await page.getByRole("menuitem", { name: `Update ${target}`, exact: true }).click()
|
|
await page.locator('[data-plugin-id="recovered.plugin"]').waitFor()
|
|
assert.equal(await row.count(), 0, "successful native loading replaces the package-only row")
|
|
assert.equal(await page.locator(".plugin-control-row").count(), 2)
|
|
assert.deepEqual(await page.evaluate(() => (window as any).fixture.calls.filter((call: any) => ["check", "update"].includes(call.type))), [
|
|
{ type: "check", location: { directory: "/repo" }, target },
|
|
{ type: "update", location: { directory: "/repo" }, targets: [target] },
|
|
])
|
|
assert.deepEqual(errors, [])
|
|
} finally { await page.close() }
|
|
})
|
|
|
|
test("V2 plugin controls disable Project when it resolves to the Global document", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 520, height: 900 }, locale: "en-US" })
|
|
const errors: string[] = []
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
await page.goto(url)
|
|
|
|
await page.evaluate(() => (window as any).fixture.setTargets(["global"]))
|
|
await page.evaluate(() => (window as any).fixture.show())
|
|
await page.getByText("acme.reviewer", { exact: true }).waitFor()
|
|
|
|
const row = page.locator('[data-plugin-id="acme.reviewer"]')
|
|
assert.equal(await row.locator('[data-scope="global"] input[type="checkbox"]').isDisabled(), false)
|
|
assert.equal(await row.locator('[data-scope="project"] input[type="checkbox"]').isDisabled(), true)
|
|
const projectDescription = await row.locator('[data-scope="project"] input').getAttribute("aria-describedby")
|
|
const noticeId = projectDescription?.split(" ").find((id) => id.endsWith("-notice"))
|
|
assert.ok(noticeId)
|
|
assert.equal(await page.locator(`[id="${noticeId}"]`).getAttribute("class"), "sr-only")
|
|
assert.ok(projectDescription?.split(" ").includes(noticeId))
|
|
assert.equal(await page.evaluate((ids) => ids!.split(" ").every(id => Boolean(document.getElementById(id))), projectDescription), true)
|
|
assert.ok(!(await row.locator('[data-scope="global"] input').getAttribute("aria-describedby"))?.split(" ").includes(noticeId))
|
|
|
|
const mutationsBefore = await page.evaluate(() => (window as any).fixture.calls.filter((call: any) => call.type === "mutation").length)
|
|
await row.locator('[data-scope="project"] input[type="checkbox"]').click({ force: true })
|
|
await page.waitForTimeout(200)
|
|
assert.equal(
|
|
await page.evaluate(() => (window as any).fixture.calls.filter((call: any) => call.type === "mutation").length),
|
|
mutationsBefore,
|
|
"a disabled Project switch must not mutate",
|
|
)
|
|
|
|
await page.evaluate(() => (window as any).fixture.setNarrow())
|
|
await page.waitForTimeout(100)
|
|
const narrowLane = await row.locator('[data-scope="global"]').boundingBox()
|
|
assert.ok((narrowLane?.width ?? 48) <= 40, "narrow panels compact the switch lanes")
|
|
await row.locator(".plugin-control-name").focus()
|
|
await page.locator(".plugin-control-tooltip").waitFor()
|
|
assert.match(await page.locator(".plugin-control-tooltip").innerText(), /Project controls are unavailable/)
|
|
await row.locator("input").first().focus()
|
|
await page.locator(".plugin-control-tooltip").waitFor({ state: "hidden" })
|
|
if (process.env.CODENOMAD_PLUGIN_SCREENSHOTS) {
|
|
await mkdir(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, { recursive: true })
|
|
await page.screenshot({ path: join(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, "plugins-unavailable-narrow.png"), fullPage: true })
|
|
}
|
|
assert.deepEqual(errors, [])
|
|
await page.close()
|
|
})
|
|
|
|
test("plugin rows retain focus through passive refresh and guarded pending mutations", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 520, height: 900 } })
|
|
await page.goto(url)
|
|
await page.evaluate(() => (window as any).fixture.show())
|
|
const input = page.locator('[data-plugin-id="acme.reviewer"] [data-scope="global"] input')
|
|
await input.waitFor()
|
|
await input.focus()
|
|
const original = await input.elementHandle()
|
|
await page.evaluate(() => (window as any).fixture.eventBurst())
|
|
assert.equal(await original!.evaluate((node) => node.isConnected && node === document.activeElement), true)
|
|
|
|
await page.evaluate(() => (window as any).fixture.holdMutation())
|
|
await input.press("Space")
|
|
await page.waitForFunction(() => document.activeElement?.getAttribute("aria-busy") === "true")
|
|
assert.equal(await input.isChecked(), true, "pending state retains the authoritative value")
|
|
await input.press("Space")
|
|
assert.equal(await page.evaluate(() => (window as any).fixture.calls.filter((call: any) => call.type === "mutation").length), 1)
|
|
assert.equal(await input.isChecked(), true, "blocked repeat activation must not visually toggle")
|
|
await input.click({ force: true })
|
|
assert.equal(await page.evaluate(() => (window as any).fixture.calls.filter((call: any) => call.type === "mutation").length), 1)
|
|
assert.equal(await input.isChecked(), true, "blocked pointer activation must not visually toggle")
|
|
await page.evaluate(() => (window as any).fixture.releaseMutation())
|
|
await page.waitForFunction(() => document.activeElement?.getAttribute("aria-busy") === "false")
|
|
assert.equal(await original!.evaluate((node) => node.isConnected && node === document.activeElement), true)
|
|
assert.equal(await input.isChecked(), false)
|
|
await input.press("Tab")
|
|
assert.equal(await page.locator('[data-plugin-id="acme.reviewer"] [data-scope="project"] input').evaluate((node) => node === document.activeElement), true)
|
|
await input.focus()
|
|
await page.evaluate(() => { (window as any).fixture.holdMutation(); (window as any).fixture.failMutation() })
|
|
await input.press("Space")
|
|
await page.waitForFunction(() => document.activeElement?.getAttribute("aria-busy") === "true")
|
|
await page.evaluate(() => (window as any).fixture.releaseMutation())
|
|
await page.waitForFunction(() => document.activeElement?.getAttribute("aria-busy") === "false")
|
|
assert.equal(await original!.evaluate((node) => node.isConnected && node === document.activeElement), true)
|
|
assert.equal(await input.isChecked(), false, "failed mutation retains the authoritative value")
|
|
await page.close()
|
|
})
|
|
|
|
test("narrow layouts keep rows compact and reveal complete details on name focus", async () => {
|
|
const page = await browser.newPage({ viewport: { width: 320, height: 1200 }, hasTouch: true })
|
|
await page.goto(url)
|
|
const details = await page.evaluate(() => {
|
|
const fixture = (window as any).fixture
|
|
fixture.setNarrow()
|
|
const details = fixture.setLongDetails()
|
|
fixture.show()
|
|
return details
|
|
})
|
|
const row = page.locator(`[data-plugin-id="${details.id}"]`)
|
|
await row.waitFor()
|
|
for (const direction of ["ltr", "rtl"]) {
|
|
await page.evaluate((locale) => (window as any).fixture.setLocale(locale), direction === "rtl" ? "he" : "fr")
|
|
await page.locator(".plugin-control-scope-label").filter({ hasText: direction === "rtl" ? "פרויקט" : "Projet" }).waitFor()
|
|
await page.evaluate((dir) => { document.documentElement.dir = dir }, direction)
|
|
await row.locator(".plugin-control-name").focus()
|
|
await page.locator(".plugin-control-tooltip").waitFor()
|
|
const tooltip = page.locator(".plugin-control-tooltip")
|
|
assert.ok((await tooltip.innerText()).includes(details.id))
|
|
assert.ok((await tooltip.innerText()).includes(details.source))
|
|
assert.equal(await tooltip.evaluate((node) => node.scrollWidth <= node.clientWidth + 1), true)
|
|
await page.locator('[data-plugin-id="broken.plugin"] .plugin-control-name').focus()
|
|
await page.waitForFunction((error) => [...document.querySelectorAll('[role="tooltip"]')].some((node) => node.textContent?.includes(error)), details.error)
|
|
await row.locator("input").first().focus()
|
|
await page.locator(".plugin-control-tooltip").waitFor({ state: "hidden" })
|
|
assert.ok((await row.boundingBox())!.height <= 36, "long identities must not enlarge a compact row")
|
|
assert.equal(await page.locator("main").evaluate((node) => node.scrollWidth <= node.clientWidth + 1), true)
|
|
if (direction === "ltr") assert.deepEqual(await page.locator(".plugin-control-scope-label").allTextContents(), ["Global", "Projet"])
|
|
if (process.env.CODENOMAD_PLUGIN_SCREENSHOTS) {
|
|
await mkdir(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, { recursive: true })
|
|
await page.screenshot({ path: join(process.env.CODENOMAD_PLUGIN_SCREENSHOTS, `plugins-narrow-${direction}.png`), fullPage: true })
|
|
}
|
|
}
|
|
await page.close()
|
|
})
|
|
|
|
for (const fail of [false, true]) {
|
|
test(`disposed plugin surfaces suppress late mutation feedback, failure=${fail}`, async () => {
|
|
const page = await browser.newPage({ viewport: { width: 520, height: 900 } })
|
|
await page.goto(url)
|
|
await page.evaluate((fail) => {
|
|
const fixture = (window as any).fixture
|
|
fixture.show()
|
|
fixture.holdMutation()
|
|
if (fail) fixture.failMutation()
|
|
}, fail)
|
|
const input = page.locator('[data-plugin-id="acme.reviewer"] [data-scope="project"] input')
|
|
await input.click()
|
|
await page.waitForFunction(() => document.querySelector('input[aria-busy="true"]'))
|
|
await page.evaluate(() => (window as any).fixture.unmount())
|
|
assert.equal(await page.locator(".plugin-controls").count(), 0)
|
|
await page.evaluate(() => (window as any).fixture.releaseMutation())
|
|
// Flush the deferred response and its async presentation continuation.
|
|
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0)))
|
|
assert.deepEqual(await page.evaluate(() => (window as any).fixture.toastHistory()), [])
|
|
await page.close()
|
|
})
|
|
}
|
|
|
|
for (const dispose of [false, true]) {
|
|
test(`queued plugin reads wait for visible demand after hiding, disposed=${dispose}`, async () => {
|
|
const page = await browser.newPage({ viewport: { width: 520, height: 900 } })
|
|
await page.goto(url)
|
|
await page.evaluate(() => (window as any).fixture.show())
|
|
await page.locator('[data-plugin-id="acme.reviewer"]').waitFor()
|
|
await page.evaluate(() => { (window as any).fixture.holdRead(); (window as any).fixture.invalidate() })
|
|
await page.waitForFunction(() => (window as any).fixture.reads() === 2)
|
|
await page.evaluate(() => (window as any).fixture.invalidate())
|
|
await page.evaluate((dispose) => {
|
|
const fixture = (window as any).fixture
|
|
if (dispose) fixture.unmount()
|
|
else fixture.hide()
|
|
fixture.releaseRead()
|
|
}, dispose)
|
|
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0)))
|
|
assert.equal(await page.evaluate(() => (window as any).fixture.reads()), 2, "a queued read must not start after demand is withdrawn")
|
|
await page.evaluate((dispose) => {
|
|
const fixture = (window as any).fixture
|
|
if (dispose) fixture.remount()
|
|
else fixture.show()
|
|
}, dispose)
|
|
await page.waitForFunction(() => (window as any).fixture.reads() === 3)
|
|
await page.locator('[data-plugin-id="acme.reviewer"]').waitFor()
|
|
await page.close()
|
|
})
|
|
}
|