| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- import fs from 'node:fs/promises';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import { pathToFileURL } from 'node:url';
- import { chromium } from '../work/workshop-manual-tools/node_modules/playwright/index.mjs';
- const root=path.resolve('docs/workshop-validated-final-20261110');
- const old=path.resolve('docs/workshop-self-run-20261110');
- const baseline=JSON.parse(await fs.readFile(path.join(old,'对话与产出.json'),'utf8'));
- const rerun=JSON.parse(await fs.readFile(path.join(root,'rerun/对话与产出.json'),'utf8'));
- const browser=await chromium.launch({headless:true,executablePath:'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'});
- const context=await browser.newContext({permissions:['clipboard-read','clipboard-write']});
- const page=await context.newPage();
- const errors=[];page.on('pageerror',e=>errors.push(e.message));
- const cases=[];
- async function noOverflow(name){
- const sizes=await page.evaluate(()=>({viewport:innerWidth,scroll:document.documentElement.scrollWidth}));
- assert.ok(sizes.scroll<=sizes.viewport,JSON.stringify({name,...sizes}));
- }
- try {
- const originals=[
- {name:'original-manual',file:path.resolve('docs/workshop-final-local-20261110/康师傅工作坊操作手册-本地定稿.html'),steps:baseline.steps},
- {name:'r12-manual',file:path.join(root,'康师傅工作坊操作手册-r12本地定稿.html'),steps:rerun.steps}
- ];
- let prompts=0,copied=0;
- for(const doc of originals){
- await page.goto(pathToFileURL(doc.file).href);
- assert.equal(await page.locator('.nav-item').count(),15);
- for(const width of [1440,390]){
- await page.setViewportSize({width,height:1000});
- for(const s of doc.steps){
- await page.evaluate(id=>{location.hash=id},s.stepId);
- await page.waitForFunction(id=>document.querySelector('.nav-item[aria-current="step"]')?.getAttribute('href')==='#'+id,s.stepId);
- const text=await page.locator('#prompt-text').textContent();
- assert.equal(text,s.prompt,doc.name+' '+s.stepId);prompts++;
- assert.ok(await page.locator('#prompt-text').isVisible());
- await page.locator('#copy-prompt').click();
- // Read only the known content immediately after this button wrote it.
- const clipboard=await page.evaluate(()=>navigator.clipboard.readText());
- assert.equal(clipboard.replaceAll('\r\n','\n'),s.prompt.replaceAll('\r\n','\n'));copied++;
- await noOverflow(doc.name+' '+s.stepId+' '+width);
- }
- if(doc.name==='r12-manual'){
- await page.evaluate(()=>{location.hash='topic'});
- await page.waitForFunction(()=>document.querySelector('.nav-item[aria-current="step"]')?.getAttribute('href')==='#topic');
- await page.waitForFunction(()=>!document.getElementById('toast').classList.contains('show'));
- await page.screenshot({path:path.join(root,`手册-${width}.png`),fullPage:true});
- }
- }
- }
- cases.push({check:'all-15-prompts-match-records-at-both-widths',comparisons:prompts,passed:true});
- cases.push({check:'copy-button-actual-browser-clipboard-content',comparisons:copied,passed:true});
- for(const rel of ['index.html','first-run/index.html','rerun/index.html']){
- await page.goto(pathToFileURL(path.join(root,rel)).href);
- for(const width of [1440,390]){
- await page.setViewportSize({width,height:1000});await noOverflow(rel+' '+width);
- if(rel==='index.html')await page.screenshot({path:path.join(root,`总览-${width}.png`)});
- }
- if(rel!=='index.html'){
- assert.equal(await page.locator('article[data-step]').count(),15);
- assert.equal(await page.locator('[data-output]').count(),7);
- await page.locator('#collapse').click();assert.equal(await page.locator('article details[open]').count(),0);
- await page.locator('#expand').click();assert.equal(await page.locator('article details[open]').count(),15);
- const steps=rel.startsWith('first-run')?baseline.steps:rerun.steps;
- for(const s of steps)assert.equal(await page.locator('#p-'+s.stepId).textContent(),s.prompt);
- const localSources=await page.locator('article a[href*="证据索引.html#"]').evaluateAll(els=>els.map(e=>e.getAttribute('href')));
- const sourcePage=await context.newPage();await sourcePage.goto(pathToFileURL(path.join(root,path.dirname(rel),'证据索引.html')).href);
- for(const src of new Set(localSources))assert.equal(await sourcePage.locator('#'+src.split('#')[1]).count(),1,src);
- await sourcePage.close();
- }
- const links=await page.locator('a[href]').evaluateAll(els=>els.map(e=>e.getAttribute('href')));
- for(const href of links){
- if(href.startsWith('#')){assert.equal(await page.locator('[id="'+href.slice(1)+'"]').count(),1,href);continue}
- if(/^[a-z]+:/i.test(href))continue;
- if(['本地定稿与两轮记录.zip','核验结果.json','浏览器核验.json'].includes(href))continue; // result files and archive are sealed after checks
- const p=path.resolve(root,path.dirname(rel),decodeURIComponent(href.split('#')[0]));await fs.access(p);
- }
- cases.push({check:'offline-report-navigation-and-links',file:rel,passed:true});
- }
- await page.goto(pathToFileURL(path.join(root,'rerun/index.html')).href+'#s-proposal');
- await page.setViewportSize({width:1440,height:1000});
- await page.locator('#s-proposal').evaluate(el=>el.scrollIntoView({behavior:'instant',block:'start'}));
- await page.waitForFunction(()=>{const y=document.getElementById('s-proposal').getBoundingClientRect().top;return y>=0&&y<=40});
- await page.screenshot({path:path.join(root,'第二轮提案-1440.png')});
- cases.push({check:'proposal-anchor-opens-at-decision-heading',passed:true});
- assert.deepEqual(errors,[]);
- const result={ok:true,widths:[1440,390],cases,scriptErrors:errors,realEmployeeTrial:false,nativeHostSkillDiscovery:false};
- await fs.writeFile(path.join(root,'浏览器核验.json'),JSON.stringify(result,null,2)+'\n');
- console.log(JSON.stringify(result));
- }finally{await browser.close()}
|