Something went wrong. Try again.
A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271import { test, expect } from "@playwright/test"import { randomUUID } from "crypto"import pg from "pg"import { loginAsTestAdmin } from "./auth-helper"
const DB_URL = "postgres://happyview:happyview@localhost:5434/happyview_test"const TEST_DID = "did:plc:e2e-test-admin"
async function seedJob( status: string, jobType = "test.e2e.export",): Promise<string> { const client = new pg.Client(DB_URL) await client.connect() try { const id = randomUUID() const now = new Date().toISOString() await client.query( `INSERT INTO happyview_jobs (id, job_type, status, input, progress, created_by, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [ id, jobType, status, JSON.stringify({ source: "e2e-test" }), JSON.stringify({}), TEST_DID, now, ], ) return id } finally { await client.end() }}
async function seedJobLogs( jobId: string, logs: Array<{ level: string; message: string }>,): Promise<void> { const client = new pg.Client(DB_URL) await client.connect() try { for (const log of logs) { const id = randomUUID() const now = new Date().toISOString() await client.query( `INSERT INTO happyview_job_logs (id, job_id, level, message, created_at) VALUES ($1, $2, $3, $4, $5)`, [id, jobId, log.level, log.message, now], ) } } finally { await client.end() }}
async function cleanupJobs(): Promise<void> { const client = new pg.Client(DB_URL) await client.connect() try { await client.query( `DELETE FROM happyview_job_logs WHERE job_id IN ( SELECT id FROM happyview_jobs WHERE created_by = $1 )`, [TEST_DID], ) await client.query( "DELETE FROM happyview_jobs WHERE created_by = $1", [TEST_DID], ) } finally { await client.end() }}
test.describe("Jobs Dashboard", () => { test.beforeEach(async ({ page }) => { await loginAsTestAdmin(page) })
test.afterEach(async () => { await cleanupJobs() })
test("shows empty state when no jobs exist", async ({ page }) => { await page.goto("/dashboard/jobs")
await expect( page.getByText("No background jobs yet"), ).toBeVisible({ timeout: 5000 }) })
test("lists seeded jobs in the table", async ({ page }) => { await seedJob("pending", "test.e2e.alpha") await seedJob("running", "test.e2e.beta")
await page.goto("/dashboard/jobs")
const rows = page.locator("table tbody tr") await expect(rows).toHaveCount(2, { timeout: 5000 })
await expect(page.getByText("test.e2e.alpha")).toBeVisible() await expect(page.getByText("test.e2e.beta")).toBeVisible() })
test("filters jobs by status", async ({ page }) => { await seedJob("pending", "test.e2e.pending-job") await seedJob("completed", "test.e2e.completed-job")
await page.goto("/dashboard/jobs")
const rows = page.locator("table tbody tr") await expect(rows).toHaveCount(2, { timeout: 5000 })
await page.getByRole("combobox").click() await page.getByRole("option", { name: "Completed" }).click()
await expect(rows).toHaveCount(1, { timeout: 5000 }) await expect(page.getByText("test.e2e.completed-job")).toBeVisible() await expect(page.getByText("test.e2e.pending-job")).not.toBeVisible() })
test("opens detail sheet when clicking a job row", async ({ page }) => { const id = await seedJob("pending", "test.e2e.detail")
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.detail", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
await expect(sheet.getByText("Job Details")).toBeVisible() await expect(sheet.getByText(id)).toBeVisible() await expect(sheet.getByText("test.e2e.detail")).toBeVisible() await expect(sheet.getByText("pending")).toBeVisible() })
test("shows cancel button for running job and cancels it", async ({ page, }) => { const id = await seedJob("running", "test.e2e.cancel")
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.cancel", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
const cancelButton = sheet.getByRole("button", { name: "Cancel Job" }) await expect(cancelButton).toBeVisible() await cancelButton.click()
await expect(page.getByText("Job cancelled")).toBeVisible({ timeout: 5000, }) })
test("shows pause button for running job", async ({ page }) => { await seedJob("running", "test.e2e.pause")
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.pause", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
await expect( sheet.getByRole("button", { name: "Pause Job" }), ).toBeVisible() })
test("shows resume button for paused job", async ({ page }) => { await seedJob("paused", "test.e2e.resume")
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.resume", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
await expect( sheet.getByRole("button", { name: "Resume Job" }), ).toBeVisible() })
test("shows logs section in job detail sheet", async ({ page }) => { const id = await seedJob("completed", "test.e2e.logs") await seedJobLogs(id, [ { level: "info", message: "starting migration" }, { level: "warn", message: "rate limited on blob upload" }, { level: "info", message: "migration complete" }, ])
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.logs", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
await expect(sheet.getByText("Logs", { exact: true })).toBeVisible({ timeout: 5000 }) await expect(sheet.getByText("starting migration")).toBeVisible() await expect(sheet.getByText("rate limited on blob upload")).toBeVisible() await expect(sheet.getByText("migration complete")).toBeVisible() await expect(sheet.getByText("WARN")).toBeVisible() await expect(sheet.getByText("INFO").first()).toBeVisible() })
test("shows error section for failed job", async ({ page }) => { const client = new pg.Client(DB_URL) await client.connect() try { const id = randomUUID() const now = new Date().toISOString() await client.query( `INSERT INTO happyview_jobs (id, job_type, status, input, progress, error, created_by, created_at, completed_at) VALUES ($1, $2, 'failed', $3, $4, $5, $6, $7, $7)`, [ id, "test.e2e.failed", JSON.stringify({}), JSON.stringify({}), "something went wrong", TEST_DID, now, ], ) } finally { await client.end() }
await page.goto("/dashboard/jobs")
const row = page.locator("table tbody tr", { hasText: "test.e2e.failed", }) await expect(row).toBeVisible({ timeout: 5000 }) await row.click()
const sheet = page.locator("[data-state='open'][role='dialog']") await expect(sheet).toBeVisible({ timeout: 3000 })
await expect(sheet.getByText("something went wrong")).toBeVisible() })})