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. * * `make test-frontend` runs against a stack of its own, but the suite can be * pointed at any (PLAYWRIGHT_BASE_URL), and that may be 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__`, plus an optional `_` 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()})`, ) } } })