An aborted Playwright run never reaches `afterAll`, so its flows and dashboards stayed in the store until somebody deleted them by hand. A setup-project test now removes them before the next run, which is the only point that is reached even when the run before it died. The suite runs against whatever stack is up — somebody's own instance with real flows in it — so the predicate matches only the shape every fixture has, `test_<spec>_<base36 Date.now()>` with an optional suffix, and only once that timestamp is an hour old. Also correct the coverage-html fallback hint: `make clean` cannot clear a root-owned htmlcov either, for the same reason `coverage html` cannot write it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VfgHWr7AQDiLsTQVu7hpKB
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { expect, test as setup } from "@playwright/test"
|
|
|
|
import { apiUrl, firstSuperuser, firstSuperuserPassword } from "./config.ts"
|
|
|
|
/**
|
|
* Remove what an aborted run left behind.
|
|
*
|
|
* Every spec deletes its own flows and dashboards in `afterAll`, which never
|
|
* fires when a run is interrupted, and nothing else looks for them — so they
|
|
* sit in the store until somebody removes them by hand. Sweeping before a run
|
|
* rather than after one is what makes this work at all: it happens even when
|
|
* the run that made the mess died.
|
|
*
|
|
* The suite runs against whatever stack is up, which is somebody's own
|
|
* instance with real flows in it, so the predicate has to be incapable of
|
|
* matching one. A name qualifies only if it has the shape every fixture has —
|
|
* `test_<spec>_<base36 Date.now()>`, plus an optional `_<suffix>` for a second
|
|
* artefact of the same spec — and only if the timestamp it carries is an hour
|
|
* old, which nothing the run now starting is about to create can be.
|
|
*/
|
|
const FIXTURE = /^test_[a-z]+_([0-9a-z]{8})(?:_[a-z]+)?$/
|
|
const MIN_AGE_MS = 60 * 60 * 1000
|
|
|
|
function isStaleFixture(name: string): boolean {
|
|
const match = FIXTURE.exec(name)
|
|
return (
|
|
match !== null && Number.parseInt(match[1], 36) < Date.now() - MIN_AGE_MS
|
|
)
|
|
}
|
|
|
|
setup("sweep stale fixtures", async ({ request }) => {
|
|
const login = await request.post(`${apiUrl}/api/v1/login/access-token`, {
|
|
form: { username: firstSuperuser, password: firstSuperuserPassword },
|
|
})
|
|
expect(login.ok(), `sweep could not log in: ${login.status()}`).toBeTruthy()
|
|
const { access_token } = await login.json()
|
|
const headers = { Authorization: `Bearer ${access_token}` }
|
|
|
|
for (const kind of ["dashboards", "flows"]) {
|
|
const listed = await request.get(`${apiUrl}/api/v1/${kind}/`, { headers })
|
|
expect(
|
|
listed.ok(),
|
|
`sweep could not list ${kind}: ${listed.status()}`,
|
|
).toBeTruthy()
|
|
const { data } = await listed.json()
|
|
for (const { name } of data) {
|
|
if (!isStaleFixture(name)) continue
|
|
const gone = await request.delete(`${apiUrl}/api/v1/${kind}/${name}`, {
|
|
headers,
|
|
})
|
|
// Best effort. One that will not go — a flow with a run still on it,
|
|
// say — is left for the next sweep rather than failing a suite it has
|
|
// nothing to do with.
|
|
console.log(
|
|
gone.ok()
|
|
? `swept ${kind}/${name}`
|
|
: `left ${kind}/${name} (${gone.status()})`,
|
|
)
|
|
}
|
|
}
|
|
})
|