| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- import fs from 'node:fs/promises';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import crypto from 'node:crypto';
- import { pathToFileURL } from 'node:url';
- import { buildModel } from '../src/core/kfs/build.js';
- import { seedMemberGroups } from '../src/core/member-groups.js';
- import { CORE_DELIVERABLES, DELIVERY_KINDS, DELIVERY_SCHEMA } from '../skills/ksf-workshop-runtime/scripts/report-contract.mjs';
- const out = path.resolve('docs/workshop-report-parity-20261111');
- const origin = 'http://127.0.0.1:4181';
- const raw = { artifact: [], session: [], project: [], insight: [], opportunity: [{ objectId: 'separate-opportunity', title: '浏览器隔离夹具机会' }], source: [] };
- const at = new Date().toISOString();
- const htmlFile = Buffer.from('<!doctype html><meta charset="utf-8"><h1>浏览器隔离夹具提案</h1><script>parent.postMessage("unexpected-report-script","*")</script>');
- const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+a5YkAAAAASUVORK5CYII=', 'base64');
- const publicFiles = new Map();
- for (let i = 0; i < 35; i++) {
- const participant = 'browser-participant-' + i, sessionId = 'browser-session-' + i;
- raw.session.push({ objectId: sessionId, studentName: '浏览器夹具组员' + i, workshopKey: 'browser-topic', groupId: 'G' + (i % 5 + 1), meta: { participantKey: participant }, createdAt: at });
- for (const output of i === 0 ? CORE_DELIVERABLES : CORE_DELIVERABLES.slice(0, 1)) {
- const id = 'browser-report-' + i + '-' + output.key;
- const artifact = { objectId: id, title: '浏览器隔离夹具:' + output.name, ownerName: '浏览器夹具组员' + i,
- ownerSession: sessionId, uploadedByKey: participant, agentName: '隔离测试共用智能体', artifactType: DELIVERY_KINDS[output.key].types[0],
- stageCode: 'S5', dataMode: 'sample', workshopKey: 'browser-topic', version: 1, reportSeriesKey: 'browser-topic:D2-08:' + output.key,
- createdAt: at, rawText: '浏览器隔离测试正文,不代表真实研究成果。',
- report: { title: '浏览器隔离夹具:' + output.name, executive: ['浏览器隔离测试,不代表真实研究成果。'],
- delivery: { schema: DELIVERY_SCHEMA, moduleId: 'D2-08', kind: output.key, contentStatus: 'ready', outputs: [output.key] },
- chapters: [{ id: output.anchor, title: output.name, sections: [{ kind: 'para', text: '浏览器隔离测试正文:核对同一人的分类、正文、文件与报告跳转。' }] }] } };
- if (output.key === 'proposal') {
- artifact.report.attachments = [['报告.html','text/html',htmlFile],['示意图.png','image/png',png]].map(([name,contentType,bytes], index) => {
- const file = { id: 'browser-file-' + index, name, contentType, sizeBytes: bytes.length, sha256: crypto.createHash('sha256').update(bytes).digest('hex') };
- file.url = origin + '/api/functions?path=%2Fksf%2Ffile&fileId=' + file.id + '&artifactId=' + id;
- publicFiles.set(file.id, { ...file, bytes });return file;
- });
- }
- raw.artifact.push(artifact);
- }
- }
- const db = buildModel(raw), groups = seedMemberGroups(db.members);
- const { chromium } = await import(pathToFileURL(path.resolve('work/workshop-manual-tools/node_modules/playwright/index.mjs')).href);
- const browser = await chromium.launch({ headless: true, executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe' });
- const checks = [], errors = [], requests = [];
- try {
- const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
- await context.route('**/*', route => {
- const request = route.request(), url = new URL(request.url());
- if (url.pathname === '/api/functions' && url.searchParams.get('path') === '/ksf/file') {
- const file = publicFiles.get(url.searchParams.get('fileId'));
- if (!file) return route.abort();
- return route.fulfill({ contentType: file.contentType, body: file.bytes,
- headers: { 'Content-Security-Policy': "default-src 'none'; sandbox", 'Content-Disposition': url.searchParams.has('preview') ? 'inline' : 'attachment' } });
- }
- if (url.pathname === '/api/functions' || url.pathname === '/__dev/member-groups') {
- const body = request.postDataJSON();requests.push(body?.path);
- const response = body?.path === '/ksfGetPublicBoard' ? raw : body?.path === '/ksfGetPublicMemberGroups' ? groups : null;
- if (!response) { errors.push('Unexpected action ' + body?.path);return route.abort(); }
- return route.fulfill({ contentType: 'application/json', body: JSON.stringify({ result: { ok: true, data: response } }) });
- }
- if (url.origin === origin && request.method() === 'GET') return route.continue();
- errors.push('Unexpected network request ' + url.origin + url.pathname);return route.abort();
- });
- const page = await context.newPage();page.on('pageerror', error => errors.push(error.message));
- await page.goto(origin + '/workshop?mode=sample');
- await page.locator('[data-output-member]').first().waitFor();
- assert.equal(await page.locator('[data-output-member]').count(), 35);
- assert.equal(await page.locator('[data-output-group]').count(), 6); // Includes empty, explicitly ungrouped section.
- const memberId = db.artifactById['browser-report-0-market'].memberId;
- const member = page.locator(`[data-output-member="${memberId}"]`);
- await member.locator(':scope > summary').click();
- assert.equal(await member.locator('[data-delivery-state="saved"]').count(), 7);
- assert.equal(await member.locator('[data-workshop-artifact]').count(), 7);
- checks.push('35名隔离夹具组员、5组、个人七类交付状态与报告链接');
- for (const width of [1280,390]) {
- await page.setViewportSize({ width, height: 900 });
- const size = await page.evaluate(() => ({ innerWidth, scrollWidth: document.documentElement.scrollWidth }));
- assert.ok(size.scrollWidth <= size.innerWidth, JSON.stringify(size));
- await page.screenshot({ path: path.join(out, 'personal-reports-' + width + '.png'), fullPage: false });
- checks.push(width + 'px个人报告页无横向溢出');
- }
- for (const output of CORE_DELIVERABLES) {
- await page.goto(origin + '/workshop?mode=sample&reportType=' + DELIVERY_KINDS[output.key].category);
- await page.locator(`[data-output-member="${memberId}"] > summary`).click();
- const row = page.locator('[data-workshop-artifact="browser-report-0-' + output.key + '"]');
- await row.locator('h3 a').click();
- await page.locator('view-report [data-chapter="' + output.anchor + '"]').waitFor();
- assert.ok((await page.locator('view-report [data-chapter="' + output.anchor + '"]').textContent()).includes('浏览器隔离测试正文'));
- checks.push(output.name + ':个人分类报告点击进入同编号正文');
- }
- assert.equal(await page.locator('[data-report-attachments] img').count(), 1);
- assert.equal(await page.locator('[data-report-attachments] a').filter({ hasText: '下载文件' }).count(), 2);
- await page.evaluate(() => { window.__reportScriptExecuted = false;window.addEventListener('message', e => { if (e.data === 'unexpected-report-script') window.__reportScriptExecuted = true; }); });
- await page.locator('[data-report-attachments] details summary').click();
- await page.frameLocator('[data-report-attachments] iframe').locator('h1').waitFor();
- assert.equal(await page.locator('[data-report-attachments] iframe').getAttribute('sandbox'), '');
- assert.equal(await page.evaluate(() => window.__reportScriptExecuted), false);
- assert.ok(await page.locator('[data-report-attachments] img').evaluate(img => img.complete && img.naturalWidth > 0));
- await page.screenshot({ path: path.join(out, 'report-files-mobile.png'), fullPage: true });
- checks.push('图片实际加载、HTML受限预览和文件下载入口;HTML脚本未执行');
- await page.goto(origin + '/reports');await page.locator('[data-types]').waitFor();
- assert.equal(await page.locator('[data-type-card]').count(), 11);
- checks.push('报告中心11类入口可渲染,独立机会不混入报告数');
- await page.goto(origin + '/docs/workshop-quality-r13-20261110/' + encodeURIComponent('final-康师傅工作坊操作手册.html') + '#topic');
- await page.locator('#prompt-topic-1').waitFor();
- assert.match(await page.locator('#prompt-topic-1').textContent(), /按当前登录员工个人保存/);
- checks.push('手册课题收尾的复制正文包含后续个人保存、分类和文件关联约定');
- await context.close();
- } finally { await browser.close(); }
- assert.deepEqual(errors, []);
- await fs.writeFile(path.join(out, 'browser-check.json'), JSON.stringify({ checkedAt: new Date().toISOString(), productionWrites: 0,
- method: 'Local real components, isolated browser-only fixtures, all API requests intercepted; not real classroom results',
- checks, errors, requestFunctions: [...new Set(requests)] }, null, 2) + '\n');
- console.log(JSON.stringify({ checks: checks.length, errors, productionWrites: 0 }));
|