// Browser check for the relate picker (lib/host/blog.sx). Runs against an // ephemeral host server seeded with a host post + 25 candidates by // run-picker-check.sh, which copies this spec into the Playwright env and sets // SX_TEST_URL. Exercises the login redirect, the JS-driven candidate load, // debounced filter, infinite scroll, and click-to-relate. const { test, expect } = require('playwright/test'); const USER = process.env.SX_ADMIN_USER || 'admin'; const PASS = process.env.SX_ADMIN_PASSWORD || 'letmein'; const HOST = 'picker-host'; // the post whose edit page we drive const LIMIT = 20; // host/blog--picker-limit // Navigate to a guarded path; the host redirects to /login?next=…, so fill the // form and we should land back on the original path (exercises the auth flow). async function loginTo(page, path) { await page.goto(path); await page.waitForURL(/\/login/); await page.fill('input[name="username"]', USER); await page.fill('input[name="password"]', PASS); await page.click('button[type="submit"]'); await page.waitForURL((u) => !u.pathname.startsWith('/login')); } test.describe('relate picker', () => { test('login redirect returns to the edit page', async ({ page }) => { await loginTo(page, `/${HOST}/edit`); await expect(page).toHaveURL(new RegExp(`/${HOST}/edit`)); await expect(page.locator('#relate-filter')).toBeVisible(); }); test('picker loads a page of candidates then loads more on scroll', async ({ page }) => { await loginTo(page, `/${HOST}/edit`); const rows = page.locator('#relate-results li'); // initial JS load fills exactly one page await expect.poll(() => rows.count(), { timeout: 8000 }).toBe(LIMIT); // scroll the results box to the bottom -> infinite scroll fetches the rest await page.locator('#relate-results').evaluate((el) => el.scrollTo(0, el.scrollHeight)); await expect.poll(() => rows.count(), { timeout: 8000 }).toBeGreaterThan(LIMIT); }); test('typing in the filter narrows the candidates', async ({ page }) => { await loginTo(page, `/${HOST}/edit`); await expect.poll(() => page.locator('#relate-results li').count(), { timeout: 8000 }).toBeGreaterThan(0); await page.fill('#relate-filter', 'Item 13'); await expect.poll(() => page.locator('#relate-results li').count(), { timeout: 8000 }).toBe(1); await expect(page.locator('#relate-results')).toContainText('Picker Item 13'); }); test('clicking a candidate relates it (and it shows on the post page)', async ({ page }) => { await loginTo(page, `/${HOST}/edit`); await page.fill('#relate-filter', 'Item 07'); await expect.poll(() => page.locator('#relate-results li').count(), { timeout: 8000 }).toBe(1); await page.locator('#relate-results button').first().click(); // form POST -> 303 back to the edit page; the related list now links the slug await expect(page).toHaveURL(new RegExp(`/${HOST}/edit`)); await expect(page.locator('a[href="/picker-item-07/"]')).toHaveCount(1); // and the public post page shows the Related posts block with the title await page.goto(`/${HOST}/`); await expect(page.getByRole('heading', { name: 'Related posts' })).toBeVisible(); await expect(page.locator('body')).toContainText('Picker Item 07'); }); });