feat: add unified learning workflows and six-tab assistant

This commit is contained in:
薇星幻海
2026-10-18 01:11:33 +08:00
parent 20abb33740
commit eb9c610b9f
32 changed files with 23806 additions and 353 deletions
+92
View File
@@ -0,0 +1,92 @@
/* 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) => `<div app-exam-question><div class="pc_examtest_content"><div class="pc_examtest_content_question"><p>[单选] ${i + 1}. 联动验证题${i}<span class="pc_examtest_content_question_value">(4分)</span></p></div><div class="pc_examtest_content_list"><div class="pc_examtest_content_list_des"><span class="examtest_content_list_des_optionsort">A</span>错误${i}</div><div class="pc_examtest_content_list_des"><span class="examtest_content_list_des_optionsort">B</span>正确${i}</div></div></div></div>`).join('');
return `<!doctype html><meta charset="utf-8"><title>助手完整流程验证</title><style>body{font:14px system-ui}.pc_examtest_left{max-width:700px}.pc_examtest_content{padding:8px}.pc_examtest_content_list_des{padding:5px;cursor:pointer}.examtest_content_list_des_checked{background:#ded}.modal[aria-hidden=true]{display:none}</style><app-examtest><div class="pc_examtest"><div class="pc_examtest_left"><div class="pc_examtest_title">完整流程考试</div>${questions}</div><div class="pc_examtest_card">${Array.from({ length: 25 }, (_, i) => `<div class="examtestcard_count_defult">${i + 1}</div>`).join('')}</div><div class="pc_examtest_countdown"><span class="time">00:30:00</span></div><div class="pc_examtest_commit">交卷</div></div></app-examtest><div role="dialog" class="modal" aria-hidden="true"><p>仍有题目未答,是否确认交卷?</p><button>确认交卷</button><button>取消</button></div><div data-exam-result hidden>交卷成功,成绩 84</div>`;
}
(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; });