fix: resume video playback and advance linked exams with attempt controls
This commit is contained in:
@@ -25,8 +25,8 @@ function paperHTML(total = 25) {
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
for (const name of modules) await page.addScriptTag({ content: fs.readFileSync(path.join(rootDir, name), 'utf8') });
|
||||
|
||||
async function setup({ matched = 25, unknownPoint = false, confirm = false, showResult = false, unansweredWarning = false, manualConfirmIncomplete, total = 25 } = {}) {
|
||||
await page.evaluate(({ html, matched, unknownPoint, confirm, showResult, unansweredWarning, manualConfirmIncomplete }) => {
|
||||
async function setup({ matched = 25, unknownPoint = false, confirm = false, showResult = false, unansweredWarning = false, manualConfirmIncomplete, skipSingleAttemptQuiz, attemptPolicy = null, total = 25 } = {}) {
|
||||
await page.evaluate(({ html, matched, unknownPoint, confirm, showResult, unansweredWarning, manualConfirmIncomplete, skipSingleAttemptQuiz, attemptPolicy }) => {
|
||||
window.qaExam?.destroy(); window.qaRunner?.destroy();
|
||||
document.documentElement.innerHTML = html.replace(/^<!doctype html>/, '').replace(/^<html>/, '').replace(/<\/html>$/, '');
|
||||
window.qaClock = { now: 0 };
|
||||
@@ -48,6 +48,8 @@ function paperHTML(total = 25) {
|
||||
window.qaRunnerState = 'complete';
|
||||
window.qaSettings = { minSubmitMinutes: 6 };
|
||||
if (manualConfirmIncomplete !== undefined) qaSettings.manualConfirmIncomplete = manualConfirmIncomplete;
|
||||
if (skipSingleAttemptQuiz !== undefined) qaSettings.skipSingleAttemptQuiz = skipSingleAttemptQuiz;
|
||||
window.qaAttemptPolicy = attemptPolicy || { blocked: false, count: null, signature: 'unknown' };
|
||||
if (unknownPoint) document.querySelector('.pc_examtest_content_question_value').textContent = '分值待定';
|
||||
const boxes = Array.from(document.querySelectorAll('.pc_examtest_content'));
|
||||
boxes.forEach((box, index) => box.querySelectorAll('.pc_examtest_content_list_des').forEach(node => node.addEventListener('click', () => {
|
||||
@@ -75,17 +77,59 @@ function paperHTML(total = 25) {
|
||||
storage: { get: async (key, fallback) => { qaStorageReads++; if (qaReadGate) await qaReadGate; return qaStorage.has(key) ? structuredClone(qaStorage.get(key)) : fallback; },
|
||||
set: async (key, value) => { if (qaStorageHook) await qaStorageHook(key, value); qaStorage.set(key, structuredClone(value)); } },
|
||||
getContext: () => qaContext, getTopic: () => qaTopic, getSnapshot: () => qaSnapshot,
|
||||
getSettings: () => qaSettings, getRunnerState: () => qaRunnerState,
|
||||
getSettings: () => qaSettings, getRunnerState: () => qaRunnerState, getAttemptPolicy: () => qaAttemptPolicy,
|
||||
onState: state => qaStates.push(state), onLog: (level, message) => qaLogs.push({ level, message })
|
||||
});
|
||||
window.qaExam = qaMakeController();
|
||||
}, { html: paperHTML(total), matched, unknownPoint, confirm, showResult, unansweredWarning, manualConfirmIncomplete });
|
||||
}, { html: paperHTML(total), matched, unknownPoint, confirm, showResult, unansweredWarning, manualConfirmIncomplete, skipSingleAttemptQuiz, attemptPolicy });
|
||||
return page.evaluate(async () => { await qaExam.observe(); const enabled = await qaExam.enable(); await qaExam.tick(); return { enabled, ...qaExam.getState() }; });
|
||||
}
|
||||
async function tickAt(seconds) {
|
||||
return page.evaluate(async value => { qaSetClock(value * 1000); await qaExam.tick(); return { state: qaExam.getState(), model: qaModel }; }, seconds);
|
||||
}
|
||||
|
||||
const singleAttempt = { blocked: true, count: 1, reason: '答题机会仅 1 次,等待人工操作', signature: 'total-attempts:1' };
|
||||
let guarded = await setup({ attemptPolicy: singleAttempt });
|
||||
assert.equal(guarded.enabled, false, '单次机会守卫默认开启,在启动前拒绝自动计划');
|
||||
assert.equal(guarded.singleAttemptBlocked, true);
|
||||
assert.equal(guarded.attemptCount, 1);
|
||||
assert.match(guarded.message, /1 次.*人工/);
|
||||
let guardedResult = await tickAt(600);
|
||||
assert.equal(guardedResult.model.commitClicks, 0, '全部题已核验且满 6 分钟也不能自动消耗唯一机会');
|
||||
assert.equal(guardedResult.model.confirmClicks, 0);
|
||||
assert.equal(guardedResult.state.requiresManualSubmission, true);
|
||||
await page.evaluate(() => { qaSettings.skipSingleAttemptQuiz = false; });
|
||||
assert.equal((await tickAt(601)).model.commitClicks, 0, '仅关闭开关不会重启此前已停止的计划');
|
||||
assert.equal(await page.evaluate(() => qaExam.enable()), true, '关闭开关并明确启动可恢复原交卷条件');
|
||||
assert.equal((await tickAt(602)).model.commitClicks, 1);
|
||||
guarded = await setup({ skipSingleAttemptQuiz: false, attemptPolicy: singleAttempt, confirm: true, showResult: true });
|
||||
assert.equal(guarded.enabled, true);
|
||||
assert.equal(guarded.singleAttemptBlocked, false);
|
||||
assert.equal((await tickAt(360)).model.commitClicks, 1);
|
||||
assert.equal((await tickAt(361)).model.confirmClicks, 1);
|
||||
assert.equal((await tickAt(362)).state.submitted, true, '关闭守卫后仍需网站结果确认成功');
|
||||
guarded = await setup({ attemptPolicy: { blocked: false, count: null, signature: 'unknown' } });
|
||||
assert.equal(guarded.enabled, true, '次数证据未知时不猜测为单次机会');
|
||||
assert.equal((await tickAt(360)).model.commitClicks, 1);
|
||||
await setup({ attemptPolicy: { blocked: false, count: 2, signature: 'total-attempts:2' } });
|
||||
await page.evaluate(policy => { qaAttemptPolicy = policy; }, singleAttempt);
|
||||
guardedResult = await tickAt(360);
|
||||
assert.equal(guardedResult.model.commitClicks, 0, '运行中发现仅一次机会立即阻止旧自动交卷');
|
||||
assert.equal(guardedResult.state.singleAttemptBlocked, true);
|
||||
assert.equal(guardedResult.state.enabled, false);
|
||||
await setup({ confirm: true, showResult: true });
|
||||
await tickAt(360);
|
||||
await page.evaluate(policy => { qaAttemptPolicy = policy; }, singleAttempt);
|
||||
guardedResult = await tickAt(361);
|
||||
assert.equal(guardedResult.model.confirmClicks, 0, '已弹交卷确认后出现单次机会也不能自动确认');
|
||||
assert.equal(guardedResult.state.stage, 'unknown', '已发起交卷后停止自动确认保留结果待确认状态');
|
||||
await page.locator('.qa-confirm').click();
|
||||
guardedResult = await tickAt(362);
|
||||
assert.equal(guardedResult.model.confirmClicks, 1, '用户仍可在原网页人工确认');
|
||||
assert.equal(guardedResult.state.submitted, true, '守卫生效期间仍只读识别人工交卷的实际网站结果');
|
||||
assert.equal(guardedResult.state.requiresManualSubmission, false);
|
||||
console.log('PASS 单次答题机会守卫默认开启、关闭后明确恢复、未知次数不猜、运行变化及人工交卷结果观察');
|
||||
|
||||
let initial = await setup({ matched: 20 });
|
||||
assert.equal(initial.total, 25);
|
||||
assert.equal(initial.totalScore, 100, '按实际 DOM 分值计算 25×4=100');
|
||||
@@ -339,7 +383,7 @@ function paperHTML(total = 25) {
|
||||
assert.equal(inferred.waitSeconds, 120);
|
||||
console.log('PASS 原始入卷时间持久化、0 时间值和明确倒计时反推');
|
||||
|
||||
for (const mutation of ['ready', 'route', 'revision', 'answers', 'points', 'topic', 'pause', 'manualConfirmIncomplete']) {
|
||||
for (const mutation of ['ready', 'route', 'revision', 'answers', 'points', 'topic', 'pause', 'manualConfirmIncomplete', 'singleAttempt', 'attemptEvidence', 'skipSingleAttemptQuiz']) {
|
||||
await setup();
|
||||
const blocked = await page.evaluate(async mutation => {
|
||||
qaSetClock(360000);
|
||||
@@ -356,13 +400,16 @@ function paperHTML(total = 25) {
|
||||
if (mutation === 'topic') qaTopic = { status: 'matched', courses: ['其他主题'] };
|
||||
if (mutation === 'pause') qaExam.disable('用户已暂停');
|
||||
if (mutation === 'manualConfirmIncomplete') qaSettings.manualConfirmIncomplete = false;
|
||||
if (mutation === 'singleAttempt') qaAttemptPolicy = { blocked: true, count: 1, reason: '仅一次机会,等待人工', signature: 'total-attempts:1' };
|
||||
if (mutation === 'attemptEvidence') qaAttemptPolicy = { blocked: false, count: 2, signature: 'total-attempts:2' };
|
||||
if (mutation === 'skipSingleAttemptQuiz') qaSettings.skipSingleAttemptQuiz = false;
|
||||
};
|
||||
await qaExam.tick(); return { model: qaModel, state: qaExam.getState() };
|
||||
}, mutation);
|
||||
assert.equal(blocked.model.commitClicks, 0, '异步保存期间发生 ' + mutation + ' 时禁止旧提交点击');
|
||||
assert.equal(blocked.state.enabled, false);
|
||||
}
|
||||
for (const mutation of ['revision', 'duplicateButton', 'pause', 'manualConfirmIncomplete']) {
|
||||
for (const mutation of ['revision', 'duplicateButton', 'pause', 'manualConfirmIncomplete', 'singleAttempt', 'attemptEvidence', 'skipSingleAttemptQuiz']) {
|
||||
await setup({ confirm: true });
|
||||
await tickAt(360);
|
||||
const blocked = await page.evaluate(async mutation => {
|
||||
@@ -372,6 +419,9 @@ function paperHTML(total = 25) {
|
||||
if (mutation === 'duplicateButton') { const button = document.createElement('button'); button.textContent = '确认交卷'; document.querySelector('.dialog_box').append(button); }
|
||||
if (mutation === 'pause') qaExam.disable('用户已暂停');
|
||||
if (mutation === 'manualConfirmIncomplete') qaSettings.manualConfirmIncomplete = false;
|
||||
if (mutation === 'singleAttempt') qaAttemptPolicy = { blocked: true, count: 1, reason: '仅一次机会,等待人工', signature: 'total-attempts:1' };
|
||||
if (mutation === 'attemptEvidence') qaAttemptPolicy = { blocked: false, count: 2, signature: 'total-attempts:2' };
|
||||
if (mutation === 'skipSingleAttemptQuiz') qaSettings.skipSingleAttemptQuiz = false;
|
||||
};
|
||||
await qaExam.tick(); return { model: qaModel, state: qaExam.getState() };
|
||||
}, mutation);
|
||||
@@ -417,6 +467,19 @@ function paperHTML(total = 25) {
|
||||
});
|
||||
assert.equal(interruptedWrite.returned, false);
|
||||
assert.equal(interruptedWrite.state.enabled, false);
|
||||
await setup();
|
||||
const changedWhileEnabling = await page.evaluate(async () => {
|
||||
qaStorageHook = async (_key, value) => {
|
||||
if (value.stage === 'answering') qaAttemptPolicy = { blocked: true, count: 1, reason: '仅一次机会,等待人工', signature: 'total-attempts:1' };
|
||||
};
|
||||
const returned = await qaExam.enable(); await qaExam.tick();
|
||||
return { returned, state: qaExam.getState(), model: qaModel };
|
||||
});
|
||||
assert.equal(changedWhileEnabling.returned, false, '异步启动保存期间出现单次证据不能返回启动成功');
|
||||
assert.equal(changedWhileEnabling.state.enabled, false);
|
||||
assert.equal(changedWhileEnabling.state.singleAttemptBlocked, true);
|
||||
assert.equal(changedWhileEnabling.model.commitClicks, 0);
|
||||
assert.equal(changedWhileEnabling.model.confirmClicks, 0);
|
||||
console.log('PASS observe 共享异步读取及 enable 在读写期间被暂停后不复活');
|
||||
|
||||
await setup();
|
||||
|
||||
Reference in New Issue
Block a user