This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
||||
// What every spec does: sign in through the dev adapter, call a procedure the way the page's own
|
||||
// forms do (with the page's CSRF token), and make an organization to work in.
|
||||
|
||||
import { randomBytes } from "node:crypto"
|
||||
import { type APIRequestContext, expect, type Page } from "@playwright/test"
|
||||
import axe from "axe-core"
|
||||
|
||||
export const unique = (prefix: string): string => `${prefix}-${randomBytes(3).toString("hex")}`
|
||||
|
||||
/** Signs in as `email` (created on first use) and lands on the home page. */
|
||||
export async function signIn(page: Page, email: string): Promise<void> {
|
||||
await page.goto(`/auth/dev-login?email=${encodeURIComponent(email)}`)
|
||||
await expect(page.locator('meta[name="csrf-token"]')).toHaveCount(1)
|
||||
}
|
||||
|
||||
/** The CSRF token of the page the browser is on: what the page's own forms send back. */
|
||||
const csrfOf = (page: Page): Promise<string> =>
|
||||
page
|
||||
.locator('meta[name="csrf-token"]')
|
||||
.getAttribute("content")
|
||||
.then((value) => value ?? "")
|
||||
|
||||
/** Calls a procedure with the session of `page`. Throws on a refusal, with the problem's own words. */
|
||||
export async function call<T = unknown>(page: Page, procedure: string, input: unknown): Promise<T> {
|
||||
const response = await callRaw(page.request, await csrfOf(page), procedure, input)
|
||||
const body = (await response.json().catch(() => null)) as T
|
||||
if (!response.ok()) throw new Error(`${procedure} answered ${response.status()}: ${JSON.stringify(body)}`)
|
||||
return body
|
||||
}
|
||||
|
||||
export const callRaw = (request: APIRequestContext, csrf: string, procedure: string, input: unknown) =>
|
||||
request.post(`/api/v1/${procedure}`, {
|
||||
data: input,
|
||||
headers: { "x-csrf-token": csrf, "content-type": "application/json", "sec-fetch-site": "same-origin" },
|
||||
})
|
||||
|
||||
export interface Workspace {
|
||||
org: string
|
||||
workspace: string
|
||||
project: string
|
||||
}
|
||||
|
||||
/** The signed-in user's own organization with one workspace and one project. */
|
||||
export async function makeOrganization(page: Page, prefix = "acme"): Promise<Workspace> {
|
||||
const org = unique(prefix)
|
||||
await call(page, "tenancy.org.create", { slug: org, name: `Org ${org}` })
|
||||
const workspace = (
|
||||
await call<{ slug: string }>(page, "tenancy.workspace.create", {
|
||||
org,
|
||||
slug: "main",
|
||||
name: "Main",
|
||||
visibility: "org",
|
||||
})
|
||||
).slug
|
||||
const project = (
|
||||
await call<{ slug: string }>(page, "tenancy.project.create", {
|
||||
org,
|
||||
workspace,
|
||||
slug: "review",
|
||||
name: "Review",
|
||||
moduleKey: "documents",
|
||||
})
|
||||
).slug
|
||||
return { org, workspace, project }
|
||||
}
|
||||
|
||||
/** Puts axe into the page. */
|
||||
async function injectAxe(page: Page): Promise<void> {
|
||||
await page.addScriptTag({ content: axe.source })
|
||||
}
|
||||
|
||||
/** WCAG 2.2 AA violations on the page the browser is on, as axe finds them. */
|
||||
export async function violations(page: Page): Promise<string[]> {
|
||||
await injectAxe(page)
|
||||
const results = await page.evaluate(() =>
|
||||
(
|
||||
globalThis as unknown as {
|
||||
axe: {
|
||||
run(
|
||||
context: unknown,
|
||||
options: unknown,
|
||||
): Promise<{ violations: Array<{ id: string; nodes: Array<{ target: unknown[] }> }> }>
|
||||
}
|
||||
}
|
||||
).axe.run(document, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"] } }),
|
||||
)
|
||||
return results.violations.map(
|
||||
(violation) => `${violation.id}: ${violation.nodes.map((node) => node.target.join(" ")).join(", ")}`,
|
||||
)
|
||||
}
|
||||
|
||||
/** What the fake platform has recorded: the audit events and usage facts the worker sent, and the blobs it stored. */
|
||||
export interface PlatformState {
|
||||
audit: Array<{ action: string; resource_id: string | null }>
|
||||
usage: Array<Record<string, unknown>>
|
||||
blobs: Array<{ pathname: string; contentType: string; size: number }>
|
||||
otel: { logs: Array<Record<string, unknown>>; traces: Array<Record<string, unknown>> }
|
||||
}
|
||||
|
||||
export async function platformState(): Promise<PlatformState> {
|
||||
const base = process.env.E2E_PLATFORM_URL
|
||||
if (!base) throw new Error("e2e: E2E_PLATFORM_URL is set by the global setup")
|
||||
const response = await fetch(`${base}/__e2e/state`)
|
||||
if (!response.ok) throw new Error(`e2e: the fake platform answered ${response.status}`)
|
||||
return (await response.json()) as PlatformState
|
||||
}
|
||||
Reference in New Issue
Block a user