import { expect, test, type APIRequestContext } from '@playwright/test'; import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import type { StructuredCvProfile } from '../src/profileCv'; import type { CvVariant } from '../src/cvBuilder'; import { translations } from '../src/i18n/translations'; const api = 'http://localhost:5302/api'; const password = 'E2ePassword123!'; const t = translations.en; 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(); return { 'X-CSRF-TOKEN': (await request.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value, 'X-Session-Context': me.sessionContext as string }; } test('real long Docling import preserves twelve jobs, dense skills and qualifications across three PDF templates', async ({ page, context, playwright }, info) => { const root = path.resolve(process.env.CV_QA_LONG_FIXTURE_ROOT!); const expected = JSON.parse(await readFile(path.join(root, 'expected.json'), 'utf8')) as { name: string; employers: string[]; titles: string[]; details: string[]; qualifications: string[]; skills: string[]; markers: string[]; }; const admin = await playwright.request.newContext(); const adminHeaders = await login(admin, 'e2e@example.test'); const email = `long-import-${Date.now()}@example.test`; expect((await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: ['Premium'] } })).status()).toBe(200); await admin.dispose(); const headers = await login(page.request, email); await context.addInitScript(() => { localStorage.setItem('uiLanguage', 'en'); localStorage.setItem('jobtracker.themeMode', 'light'); }); await context.route('**/*', route => { const target = new URL(route.request().url()); return ['localhost', '127.0.0.1'].includes(target.hostname) || ['data:', 'blob:', 'about:'].includes(target.protocol) ? route.continue() : route.abort(); }); await page.setViewportSize({ width: 1440, height: 1000 }); await page.emulateMedia({ reducedMotion: 'reduce' }); await page.goto('/career?section=import'); const file = path.join(root, 'long-labelled-integration.docx'); const sourceHash = createHash('sha256').update(await readFile(file)).digest('hex'); await page.locator('input[type=file][accept*=".pdf"]').setInputFiles(file); await expect.poll(async () => (await (await page.request.get(`${api}/profile-cv/runs`)).json())[0]?.status, { timeout: 90_000 }).toBe('pending_review'); const run = (await (await page.request.get(`${api}/profile-cv/runs`)).json())[0]; expect(run.parserVersion).toBe('docling-v1'); expect(run.operation.progressStage).toBe('cv_review_ready'); expect((await (await page.request.get(`${api}/career/profile`)).json()).revision).toBe(0); const source = await page.request.get(`${api}/profile-cv/runs/${run.id}/source`); expect(source.status()).toBe(200); expect(createHash('sha256').update(await source.body()).digest('hex')).toBe(sourceHash); await page.reload(); const review = page.getByRole('region', { name: t.cvImportReviewPanel, exact: true }); await expect(review.getByRole('button', { name: t.cvImportApplyReviewed, exact: true })).toBeDisabled(); const groups = review.getByRole('button', { name: t.cvImportUseGroup, exact: true }); await expect(groups.first()).toBeVisible(); for (const group of await groups.all()) await group.click(); await review.getByRole('button', { name: t.cvImportApplyReviewed, exact: true }).click(); await expect(page.getByText(t.cvImportSaved, { exact: true })).toBeVisible(); const snapshot = await (await page.request.get(`${api}/career/profile`)).json() as { revision: number; profile: StructuredCvProfile }; expect(snapshot.revision).toBe(1); expect(snapshot.profile.contact.fullName).toBe(expected.name); expect(snapshot.profile.jobs).toHaveLength(12); expect(snapshot.profile.jobs.map(job => job.company)).toEqual(expected.employers); expect(snapshot.profile.jobs.map(job => job.title)).toEqual(expected.titles); expect(snapshot.profile.jobs.map(job => job.start)).toEqual(expected.employers.map((_, index) => String(2011 + index))); expect(snapshot.profile.jobs.slice(0, 11).map(job => job.end)).toEqual(expected.employers.slice(0, 11).map((_, index) => String(2012 + index))); expect(snapshot.profile.jobs[11].isCurrent).toBe(true); expect(snapshot.profile.jobs.flatMap(job => job.bullets)).toEqual(expected.details); snapshot.profile.jobs.forEach((job, index) => expect(job.bullets).toEqual(expected.details.slice(index * 3, index * 3 + 3))); expect(snapshot.profile.education.map(education => education.qualification)).toEqual(expected.qualifications); expect(snapshot.profile.skills).toEqual(expected.skills); expect((await (await page.request.get(`${api}/career/profile/versions`)).json())).toHaveLength(1); const preserved: CvVariant[] = []; for (const [theme, pageSize] of [['minimal', 'a4'], ['modern', 'letter'], ['technical', 'a4']] as const) { const response = await page.request.post(`${api}/cv/variants`, { headers, data: { name: `Long import ${theme}`, settings: { themeId: theme, pageSize } } }); expect(response.status()).toBe(200); const cv = await response.json() as CvVariant; expect(cv.settings.themeId).toBe(theme); expect(cv.settings.pageSize).toBe(pageSize); await page.goto(`/career/builder/${cv.id}`); await expect(page.getByLabel(t.cvEditorName, { exact: true })).toHaveValue(cv.name); await expect(page.getByTitle(t.cvEditorPreviewPage.replace('{page}', '1'), { exact: true })).toBeVisible(); const download = page.waitForEvent('download'); await page.getByRole('button', { name: t.cvEditorDownloadPdf, exact: true }).click(); const pdf = await download; const pdfPath = info.outputPath(`long-import-${theme}-${pageSize}.pdf`); await pdf.saveAs(pdfPath); expect(await pdf.failure()).toBeNull(); const bytes = await readFile(pdfPath); expect(bytes.subarray(0, 5).toString()).toBe('%PDF-'); await info.attach('pdf-semantic-oracle-input', { body: JSON.stringify({ pdfPath, sha256: createHash('sha256').update(bytes).digest('hex').toUpperCase(), expectedLiteralMarkers: [...expected.markers, ...expected.employers.map((_, index) => String(2011 + index)), 'Present'], theme, pageSize, minimumPages: 2, sourceHash, boundary: 'Real source import, explicit automated fixture approval and downloaded bytes. Root independently checks every marker, page bounds and rendered output.' }), contentType: 'application/json' }); await info.attach(`long-${theme}-pdf`, { path: pdfPath, contentType: 'application/pdf' }); await page.screenshot({ path: info.outputPath(`long-${theme}-preview.png`), fullPage: true, animations: 'disabled' }); expect(await (await page.request.get(`${api}/career/profile`)).json()).toEqual(snapshot); for (const prior of preserved) expect(await (await page.request.get(`${api}/cv/variants/${prior.id}`)).json()).toEqual(prior); preserved.push(await (await page.request.get(`${api}/cv/variants/${cv.id}`)).json() as CvVariant); } });