feat: add unified learning workflows and six-tab assistant
This commit is contained in:
@@ -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; });
|
||||
Reference in New Issue
Block a user