import type { Browser, Page } from "@playwright/test" /** * Talking to the API directly, for the setup and teardown around a spec. * * The specs run against a development stack, so whatever they create has to go * again — the same bargain the backend suite strikes with its throwaway * database. What a spec leaves behind is in someone's flow list tomorrow. */ import { apiUrl } from "../config.ts" const authFile = "playwright/.auth/user.json" /** Call the API as the logged-in user of *page*. */ export async function api( page: Page, path: string, init: Record = {}, ) { const token = await page.evaluate(() => localStorage.getItem("access_token")) return page.request.fetch(`${apiUrl}/api/v1${path}`, { ...init, headers: { Authorization: `Bearer ${token}` }, }) } /** An authenticated page for a `beforeAll`/`afterAll`, which get no `page`. */ export async function apiPage(browser: Browser) { const page = await browser.newPage({ storageState: authFile }) await page.goto("/") return page } /** * Create a flow the way the app does, with a POST, under its key as a pinned * id, so a spec keeps addressing it by the name it chose. The suite signs in * as a superuser, who may pin. The answer is the request's own. */ export function createFlow( page: Page, doc: { name: string; [field: string]: unknown }, ) { return api(page, "/flows/", { method: "POST", data: { id: doc.name, ...doc }, }) } /** * The same for a dashboard. Creating one takes only its key and title, so the * rest of `doc` is saved over the new draft, and the answer is that save's. */ export async function createDashboard( page: Page, doc: { name: string; title?: string; [field: string]: unknown }, ) { const made = await api(page, "/dashboards/", { method: "POST", data: { id: doc.name, name: doc.name, title: doc.title }, }) const rest = Object.keys(doc).filter( (key) => key !== "name" && key !== "title", ) if (!made.ok() || rest.length === 0) return made const { version } = await made.json() return api(page, `/dashboards/${doc.name}`, { method: "PUT", data: { ...doc, version }, }) } /** * The id the server issued a flow just created in the app, read off the * editor it opened: an opaque `f_` and eight characters, never the key. */ export async function openedFlowId(page: Page) { await page.waitForURL(/\/flows\/f_[a-z2-7]{8}$/) return new URL(page.url()).pathname.split("/").pop() ?? "" } /** Delete the given API paths, whether or not they are still there. */ export async function deleteAll(browser: Browser, paths: string[]) { const page = await apiPage(browser) for (const path of paths) { await api(page, path, { method: "DELETE" }) } await page.close() }