test(e2e): §22-current Tier-1 harness + metadata E2E (SLICE-3/4/5)

Modernize the Tier-1 stack to the three-tier app and add browser coverage for
the §22.4a metadata UI, closing the E2E gap deferred across SLICE-3/4/5:
- seed-gitea.sh: create a REGISTRY_REPO with projects.yaml + a faceted named
  collection (.collection.yaml fields: priority enum + tags) seeded with three
  metadata-bearing entries; register content+registry webhooks; self-guarding
  (skip if a prior token still works) so a dependency-triggered re-run can't
  remint and invalidate the backend's token.
- .env.tier1: REGISTRY_REPO/DEFAULT_PROJECT_ID; disable OTC cooldown + lift the
  per-IP auth limiter for the single-IP test runner.
- docker-compose: pin backend image; backend-seed inserts a granted owner the
  OTC path can sign in as (write paths need contributor+).
- Makefile: two-phase tier1-up (seed to completion, then create backend so it
  reads the populated token env); robust down; e2e-fresh = down+up+e2e (the
  canonical run, since the edit/bulk specs mutate the seeded corpus).
- metadata.spec.js: SLICE-3 faceted filter (anon), SLICE-4 edit panel (owner),
  SLICE-5 bulk bar (owner). 4 passed against a fresh stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-07 22:29:11 -07:00
parent 9e1b7ce34f
commit 2fc7029bd9
7 changed files with 282 additions and 29 deletions
+82
View File
@@ -0,0 +1,82 @@
import { test, expect } from '@playwright/test'
import { signIn, OWNER_EMAIL } from './lib/auth.js'
import { dismissCookies } from './lib/ui.js'
// §22.4a Configurable Collection Metadata — end-to-end browser coverage for the
// three UI slices, against the Tier-1 stack's faceted `bdd` collection (seeded
// in testing/seed-gitea.sh with a fields: schema of priority(enum) + tags, and
// three entries: checkout-guest [P0], checkout-returning [P1], search-facets [P0]).
//
// Tests run in order against a freshly-seeded stack: SLICE-3 reads first, then
// SLICE-4 edits `checkout-returning`, then SLICE-5 bulk-edits the two P0 entries
// (distinct rows — no cross-test interference within one run).
const BDD = '/p/ohm/c/bdd'
// SLICE-3 (PUC-3) — faceted left-pane filtering, anonymous/read-only.
test('SLICE-3: faceted filter narrows the catalog by Priority', async ({ page }) => {
await page.goto(BDD)
await dismissCookies(page)
const catalog = page.locator('aside.catalog')
// All three entries are listed initially.
await expect(catalog.getByText('Guest checkout')).toBeVisible()
await expect(catalog.getByText('Returning-customer checkout')).toBeVisible()
await expect(catalog.getByText('Faceted search')).toBeVisible()
// The Priority facet renders with the seeded P0 count (2 entries).
const p0 = catalog.locator('.facet-value', { hasText: 'P0' })
await expect(p0.locator('.facet-count')).toHaveText('2')
// Selecting P0 re-fetches server-side and drops the lone P1 entry.
await p0.getByRole('checkbox').check()
await expect(catalog.getByText('Returning-customer checkout')).toHaveCount(0)
await expect(catalog.getByText('Guest checkout')).toBeVisible()
await expect(catalog.getByText('Faceted search')).toBeVisible()
})
// SLICE-4 (PUC-1) — single-entry metadata edit via the detail panel (authed).
test('SLICE-4: edit one entry\'s priority from the detail panel', async ({ page }) => {
await signIn(page, OWNER_EMAIL)
await page.goto(`${BDD}/e/checkout-returning`)
await dismissCookies(page)
const panel = page.locator('.metadata-fields-panel')
await expect(panel).toBeVisible()
// Seeded at P1; change to P2 and save (direct sidecar commit, D7).
await expect(panel.locator('#mf-priority')).toHaveValue('P1')
await panel.locator('#mf-priority').selectOption('P2')
await panel.getByRole('button', { name: 'Save' }).click()
await expect(panel.getByText('Saved')).toBeVisible()
// Persisted across a reload (read back from the committed sidecar).
await page.reload()
await expect(page.locator('.metadata-fields-panel #mf-priority')).toHaveValue('P2')
})
// SLICE-5 (PUC-2) — multi-select + bulk action bar, one commit (authed).
test('SLICE-5: bulk-set priority on multiple selected entries', async ({ page }) => {
await signIn(page, OWNER_EMAIL)
await page.goto(BDD)
await dismissCookies(page)
const catalog = page.locator('aside.catalog')
await expect(catalog.getByText('Guest checkout')).toBeVisible()
// Select the two P0 entries.
await catalog.getByLabel('select Guest checkout').check()
await catalog.getByLabel('select Faceted search').check()
// The sticky bulk bar appears with the selected count.
const bar = page.locator('.bulk-action-bar')
await expect(bar.getByText('2 selected')).toBeVisible()
// Set priority P1 across both → one commit; a toast reports the result.
await bar.getByLabel('Set Priority').selectOption('P1')
await expect(page.getByText(/2 updated/)).toBeVisible()
// The change is reflected server-side: the Priority P1 facet now counts the
// two newly-updated entries plus the pre-existing P1 (checkout-returning was
// P1 at seed; SLICE-4 may have moved it — assert at least the two we set).
await expect(catalog.locator('.facet-value', { hasText: 'P1' }).locator('.facet-count'))
.not.toHaveText('0')
})