Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions apps/web/e2e/capture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import { readFile } from 'node:fs/promises'
import { expect, test, type Page } from '@playwright/test'
import { hashOf, push, pushes, type Recording, type Shot } from './push'
import { seed } from './fixture'
import { SIGNED_IN } from './session'

// Signed out, deliberately: these screens exist before anybody is.
test.use({ storageState: { cookies: [], origins: [] } })
Expand Down Expand Up @@ -204,3 +206,81 @@ test('a dead link, kept as evidence', async ({ page, browser }) => {

await push('a dead link is turned away', shots, recordings)
})

/**
* Judging a capture in the carousel (#243): ozalid's core surface, stable by
* construction — step names and variant labels are fixed fixture strings, the
* stage shows a deterministic fixture PNG, and the carousel covers the page
* underneath, so no case id or date reaches the pixels.
*
* Signed in, unlike the file's other flows: judging is a reviewer's act.
* Every walk seeds its own case, so the acceptance taken in step three never
* leaks into the next walk's pixels.
*/
test.describe('judging a capture', () => {
test.use({ storageState: SIGNED_IN })

async function walkCarousel(page: Page, theme: 'light' | 'dark'): Promise<Shot[]> {
const variant = { theme }
await page.emulateMedia({ colorScheme: theme })
const seeded = await seed(page)
await page.goto(`/projects/e2e/cases/${seeded.caseId}`)

// Into the carousel on the variant matching the UI theme, first step.
await page
.locator(`tbody button[aria-label$="${theme}·desktop in the carousel"]`)
.first()
.click()
const carousel = page.getByRole('dialog', { name: 'capture' })
await expect(carousel.getByRole('button', { name: 'accept', exact: true })).toBeVisible()
const shots: Shot[] = [{ step: 'faces the capture', variant, bytes: await freshPaint(page) }]

// The refusal sheet: the remark empty, the variant ticks visible. Blurred
// so no caret ever blinks into the bytes.
await carousel.getByRole('button', { name: 'refuse', exact: true }).click()
await expect(carousel.getByText('Refuse the capture')).toBeVisible()
await carousel.locator('textarea').blur()
shots.push({ step: 'weighs a refusal', variant, bytes: await freshPaint(page) })

// Cancel is a true no-op; accepting fills the half and tints the edge.
await page.keyboard.press('Escape')
await carousel.getByRole('button', { name: 'accept', exact: true }).click()
await expect(carousel.getByRole('button', { name: '\u2713 accepted' })).toBeVisible()
shots.push({ step: 'has accepted', variant, bytes: await freshPaint(page) })

return shots
}

test('judging a capture, kept as evidence', async ({ page, browser }) => {
const shots = [...(await walkCarousel(page, 'light')), ...(await walkCarousel(page, 'dark'))]
expect(shots).toHaveLength(6)

const again = [...(await walkCarousel(page, 'light')), ...(await walkCarousel(page, 'dark'))]
expect(again.map((s) => `${s.step}\u00b7${s.variant.theme}\u00b7${hashOf(s.bytes)}`)).toEqual(
shots.map((s) => `${s.step}\u00b7${s.variant.theme}\u00b7${hashOf(s.bytes)}`),
)

const recordings: Recording[] = []
for (const theme of ['light', 'dark'] as const) {
const filming = await browser.newContext({
recordVideo: { dir: test.info().outputPath('videos'), size: { width: 1280, height: 720 } },
viewport: { width: 1280, height: 720 },
storageState: SIGNED_IN,
colorScheme: theme,
baseURL: test.info().project.use.baseURL,
})
const filmed = await filming.newPage()
await walkCarousel(filmed, theme)
const video = filmed.video()
await filming.close()
if (video) {
const saved = test.info().outputPath(`videos/carousel-${theme}.webm`)
await video.saveAs(saved)
recordings.push({ variant: { theme }, bytes: await readFile(saved) })
}
}
expect(recordings).toHaveLength(2)

await push('judging a capture', shots, recordings)
})
})
Loading