99 lines
7.1 KiB
TypeScript
99 lines
7.1 KiB
TypeScript
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);
|
|
}
|
|
});
|