import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test'; import { WIDGETS } from '../../../../../job-tracker-ui/src/dashboardLayout'; const api = 'http://localhost:5302/api'; const password = 'E2ePassword123!'; async function login(request: APIRequestContext, email: string) { expect((await request.post(`${api}/auth/login`, { data: { email, password } })).status()).toBe(200); const me = await (await request.get(`${api}/auth/me`)).json(); const csrf = (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value; return { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': me.sessionContext as string }; } for (const size of ['standard', 'full'] as const) test(`All populated widgets at ${size} across responsive widths`, async ({ page, playwright }, info) => { test.setTimeout(120000); const admin = await playwright.request.newContext(); try { const adminHeaders = await login(admin, 'e2e@example.test'); const email = `p8-sizes-${size}-${Date.now()}@example.test`; expect((await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: [] } })).status()).toBe(200); const headers = await login(page.request, email); const created = await page.request.post(`${api}/companies`, { headers, data: { name: 'Very long Norwegian company name for readable widget layouts', source: 'Native size qualification' } }); expect(created.status()).toBe(201); const companyId = (await created.json()).id; expect((await page.request.post(`${api}/jobapplications`, { headers, data: { companyId, jobTitle: 'Senior platform developer', status: 'Applied', dateApplied: new Date(Date.now() - 86400000).toISOString(), responseReceived: true, responseDate: new Date().toISOString(), tags: 'TypeScript, Norsk programvareutvikling', salaryMin: 600000, salaryMax: 850000, salaryCurrency: 'NOK', salaryPeriod: 'year' } })).status()).toBe(201); await page.route('**/*', route => ['localhost', '127.0.0.1'].includes(new URL(route.request().url()).hostname) ? route.continue() : route.abort()); await page.goto('/dashboard'); await page.getByRole('button', { name: 'Customize dashboard', exact: true }).click(); const editor = page.getByRole('region', { name: 'Editing dashboard', exact: true }); for (const widget of WIDGETS) if (widget.sizes.length > 1) { const select = editor.locator(`[data-layout-row="${widget.id}"] select`); await select.selectOption(size); await expect(select).toHaveValue(size); } await editor.getByRole('button', { name: 'Save layout', exact: true }).click(); await expect(editor).toHaveCount(0); const readback = await (await page.request.get(`${api}/auth/preferences/dashboard`)).json(); expect(readback.widgets.every((widget: { id: string; size: string }) => widget.size === (widget.id === 'cards' ? 'full' : size))).toBe(true); for (const summary of await page.locator('[data-dashboard-widget] details > summary').all()) await summary.click(); for (const width of [320, 768, 1024, 1440]) { await page.setViewportSize({ width, height: 900 }); await expect(page.locator('[data-dashboard-widget]')).toHaveCount(9); await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)).toBe(true); const failures = await page.locator('[data-dashboard-widget]').evaluateAll(widgets => widgets.flatMap(widget => { const box = widget.getBoundingClientRect(); return [...widget.querySelectorAll('table, svg, th, td')].filter(element => { const rect = element.getBoundingClientRect(); return rect.width > 0 && (rect.right > box.right + 1 || rect.left < box.left - 1); }).map(element => ({ widget: widget.getAttribute('data-dashboard-widget'), tag: element.tagName })); })); expect(failures).toEqual([]); const amount = page.locator('[data-dashboard-widget="salary"]').getByText(new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'NOK', maximumFractionDigits: 0 }).format(725000), { exact: true }); await expect(amount).toBeVisible(); expect(await amount.evaluate(element => { const range = document.createRange(); range.selectNodeContents(element); return range.getClientRects().length; })).toBe(1); const screenshot = info.outputPath(`${size}-${width}.png`); await page.screenshot({ path: screenshot, fullPage: true }); await info.attach(`${size}-${width}`, { path: screenshot, contentType: 'image/png' }); } } finally { await admin.dispose(); } });