36 lines
3.0 KiB
JavaScript
36 lines
3.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
|
|
const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
|
|
const directory = new URL('../docs/teaching/rtcp-five-axis/', import.meta.url);
|
|
await mkdir(new URL('verification/', directory), { recursive: true });
|
|
const browser = await chromium.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] });
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 720 }, deviceScaleFactor: 1 });
|
|
const errors = []; page.on('pageerror', (e) => errors.push(e.message));
|
|
await page.goto(new URL('course.html', directory).href);
|
|
await page.emulateMedia({ media: 'print' });
|
|
await page.evaluate(async () => { await document.fonts.ready; await Promise.all([...document.images].map((im) => im.decode())); });
|
|
const checks = await page.locator('.slide').evaluateAll((slides) => slides.map((slide) => {
|
|
const rect = slide.getBoundingClientRect(), body = slide.querySelector('.body'), takeaway = slide.querySelector('.takeaway').getBoundingClientRect();
|
|
const clipped = [...body.querySelectorAll('p,li,table,figure,pre,.equation,.flow,h3')].filter((element) => {
|
|
const r = element.getBoundingClientRect();
|
|
return r.bottom > takeaway.top - 5 || r.right > rect.right - 35 || r.left < rect.left + 30;
|
|
}).map((element) => ({ tag: element.tagName, text: element.textContent.slice(0, 80), bottom: element.getBoundingClientRect().bottom - rect.top }));
|
|
const title = slide.querySelector('h1');
|
|
const range = document.createRange(); range.selectNodeContents(title);
|
|
const titleBounds = range.getBoundingClientRect();
|
|
return { page: Number(slide.dataset.page), title: title.textContent, clipped, content_overflow: body.scrollHeight > body.clientHeight + 1, title_right: titleBounds.right - rect.left, images: [...slide.querySelectorAll('img')].map((im) => ({ src: im.getAttribute('src'), complete: im.complete && im.naturalWidth > 0 })) };
|
|
}));
|
|
await writeFile(new URL('verification/layout.json', directory), JSON.stringify({ pages: checks, errors }, null, 2) + '\n');
|
|
for (const check of checks) {
|
|
await page.locator(`.slide[data-page="${check.page}"]`).screenshot({ path: new URL(`verification/page-${String(check.page).padStart(2, '0')}.png`, directory).pathname });
|
|
}
|
|
const bad = checks.filter((c) => c.clipped.length || c.content_overflow || c.title_right > 1230 || c.images.some((im) => !im.complete));
|
|
assert.deepEqual(errors, []);
|
|
assert.deepEqual(bad, [], 'PDF slide overflow or missing assets');
|
|
await page.pdf({ path: fileURLToPath(new URL('五轴机床与RTCP-教学课件.pdf', directory)), preferCSSPageSize: true, printBackground: true, tagged: true, outline: true });
|
|
console.log(JSON.stringify({ report_id: 'rtcp-teaching-layout-v1', pages: checks.length, status: 'verified', pdf: '五轴机床与RTCP-教学课件.pdf' }));
|
|
} finally { await browser.close(); }
|