/* Full userscript workflow, simulated business URL only: no business requests. */ 'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { chromium } = require('playwright'); const root = path.resolve(__dirname, '..'); const script = fs.readFileSync(path.join(root, '谊安学助手 2.0.user.js'), 'utf8'); function fixture() { const questions = Array.from({ length: 25 }, (_, i) => `

[单选] ${i + 1}. 联动验证题${i}(4分)

A错误${i}
B正确${i}
`).join(''); return `助手完整流程验证
完整流程考试
${questions}
${Array.from({ length: 25 }, (_, i) => `
${i + 1}
`).join('')}
00:30:00
交卷
`; } (async () => { const browser = await chromium.launch({ channel: process.env.PLAYWRIGHT_CHANNEL || 'chrome', headless: true }); try { const memory = new Map(), errors = [], blocked = []; const context = await browser.newContext({ viewport: { width: 1366, height: 900 } }); await context.exposeBinding('__read', (_, key, fallback) => memory.has(key) ? structuredClone(memory.get(key)) : fallback); await context.exposeBinding('__write', (_, key, value) => memory.set(key, structuredClone(value))); const url = 'http://10.119.67.7/clientcn/exam/workflow/questions?flag=test&totaltime=30&title=' + encodeURIComponent('完整流程考试'); await context.route('**/*', route => { if (route.request().isNavigationRequest() && route.request().url() === url) return route.fulfill({ status: 200, contentType: 'text/html;charset=utf-8', body: fixture() }); blocked.push(route.request().url()); return route.abort(); }); const page = await context.newPage(); page.on('pageerror', error => errors.push(error.message)); await page.goto(url); await page.evaluate(() => { window.offset = 0; const nativeNow = Date.now.bind(Date); Date.now = () => nativeNow() + window.offset; window.advanceExamTime = milliseconds => { window.offset += milliseconds; const remaining = Math.max(0, 1800 - Math.floor(window.offset / 1000)); document.querySelector('.pc_examtest_countdown .time').textContent = '00:' + String(Math.floor(remaining / 60)).padStart(2, '0') + ':' + String(remaining % 60).padStart(2, '0'); }; window.commitClicks = 0; window.confirmClicks = 0; document.querySelectorAll('.pc_examtest_content_list_des').forEach(node => node.addEventListener('click', () => { node.parentElement.querySelectorAll('.pc_examtest_content_list_des').forEach(n => n.classList.remove('examtest_content_list_des_checked')); node.classList.add('examtest_content_list_des_checked'); })); document.querySelector('.pc_examtest_commit').addEventListener('click', () => { window.commitClicks++; document.querySelector('[role=dialog]').setAttribute('aria-hidden', 'false'); }); document.querySelector('[role=dialog] button').addEventListener('click', () => { window.confirmClicks++; document.querySelector('[role=dialog]').setAttribute('aria-hidden', 'true'); document.querySelector('[data-exam-result]').hidden = false; }); }); await page.addScriptTag({ content: '(function(){const GM_getValue=(k,v)=>window.__read(k,v);const GM_setValue=(k,v)=>window.__write(k,v);\n' + script + '\n})();' }); await page.evaluate(() => YianAssistant.ready); await page.evaluate(() => { YianAssistant.getState(); }); const records = Array.from({ length: 21 }, (_, i) => ({ role: i % 2 ? '主操' : '班长', course: '流程主题库', type: '单选', number: i + 1, question: '联动验证题' + i, options: [{ label: 'A', text: '正确' + i }, { label: 'B', text: '错误' + i }], answer: 'A' })); await page.evaluate(async records => { const bytes = new TextEncoder().encode(JSON.stringify(records)); await YianAssistant.importFiles([{ name: '统一流程.json', size: bytes.length, arrayBuffer: async () => bytes.buffer }]); await YianAssistant.setLearningSetting('mode', 'exam'); await YianAssistant.setLearningSetting('answerInterval', 0); }, records); await page.waitForFunction(() => YianAssistant.getState().pageReady); assert.equal(await page.evaluate(() => YianAssistant.setAutoAnswer(true)), false, '没有明确主题关联不能答题'); await page.evaluate(async () => { await YianAssistant.setLearningSetting('associations', { 完整流程考试: ['流程主题库'] }); await YianAssistant.taskAction('start'); }); await page.waitForFunction(() => YianAssistant.getState().metrics.selected === 21, null, { timeout: 15000 }); await page.waitForFunction(() => YianAssistant.getExamState().predictedScore === 84); assert.equal(await page.evaluate(() => window.commitClicks), 0); await page.getByTestId('tab-bank').click(); await page.getByRole('textbox', { name: '搜索题目', exact: true }).fill('联动验证题0'); await page.getByRole('button', { name: '搜索', exact: true }).click(); assert.equal(await page.getByRole('switch', { name: '导出题库', exact: true, includeHidden: true }).isChecked(), false); await page.getByTestId('tab-console').click(); assert.equal(await page.evaluate(() => YianAssistant.getExamState().enabled), true, '切栏不取消交卷授权'); await page.evaluate(() => window.advanceExamTime(4 * 60000)); await page.waitForTimeout(750); assert.equal(await page.evaluate(() => window.commitClicks), 0, '4分钟不得交卷'); await page.evaluate(() => window.advanceExamTime(2 * 60000 + 500)); await page.waitForFunction(() => YianAssistant.getExamState().submitted === true, null, { timeout: 8000 }).catch(async error => { console.error(await page.evaluate(() => ({ stage: YianAssistant.getExamState().stage, message: YianAssistant.getExamState().message, score: YianAssistant.getExamState().predictedScore, tasks: YianAssistant.getLearningState().tasks, commits: window.commitClicks, confirms: window.confirmClicks, ready: YianAssistant.getContext().ready }))); throw error; }); assert.equal(await page.evaluate(() => window.commitClicks), 1); assert.equal(await page.evaluate(() => window.confirmClicks), 1); await page.waitForFunction(() => YianAssistant.getLearningState().completed === 1); await page.waitForTimeout(1300); assert.equal(await page.evaluate(() => window.commitClicks), 1); assert.deepEqual(errors, []); assert.deepEqual(blocked, []); console.log('PASS 完整用户脚本:无主题阻止答题→明确关联→统一题库21题全文回读=84分→切栏不扰动→4分钟等待→6分钟正常交卷→网站确认→任务完成,零业务请求'); } finally { await browser.close(); } })().catch(error => { console.error(error); process.exitCode = 1; });