feat: add manual confirmation toggle for incomplete exams
This commit is contained in:
@@ -169,6 +169,35 @@ async function main() {
|
||||
state = await tick(); assert.equal(state.tasks[0].status, 'waiting');
|
||||
await tick(600000); assert.equal(await page.evaluate(() => qaExamCalls), 1); assert.equal(await page.evaluate(() => qaReloads), 0); checks++;
|
||||
|
||||
// 缺题改由用户在网站交卷,等待与局部暂停只读同步最终结果,不能重启控制器。
|
||||
for (const locallyPaused of [false, true]) {
|
||||
await setup(examDetail, { mode: 'exam' });
|
||||
await page.evaluate(() => {
|
||||
qaEngine.destroy(); window.qaExamPauseCalls = 0;
|
||||
qaEngine = qaCreate({ onExamPause: () => { qaExamPauseCalls++; qaExam = { stage: 'paused', message: '已交给用户处理' }; } });
|
||||
});
|
||||
await page.evaluate(() => qaEngine.start());
|
||||
await page.evaluate(() => { document.body.innerHTML = '<app-examtest><div class="pc_examtest_title">测试课程</div></app-examtest>'; });
|
||||
await tick(); assert.equal(await page.evaluate(() => qaExamCalls), 1);
|
||||
await page.evaluate(() => { qaExam = { stage: 'waiting', predictedScore: 84, total: 40, verifiedCount: 32, requiresManualSubmission: true, enabled: false, message: '题目不全,请在网站手动确认交卷' }; });
|
||||
state = await tick(); assert.equal(state.tasks[0].status, 'waiting');
|
||||
if (locallyPaused) {
|
||||
await page.evaluate(() => qaEngine.configure({ examPaused: true }));
|
||||
assert.equal(await page.evaluate(() => qaExamPauseCalls), 1);
|
||||
}
|
||||
for (const stage of ['waiting', 'unknown', 'submitting']) {
|
||||
await page.evaluate(stage => { qaExam = { stage, submitted: true, confirmed: false, requiresManualSubmission: true, enabled: false }; }, stage);
|
||||
state = await tick(600000);
|
||||
assert.equal(state.tasks[0].status, 'waiting', '等待/未知/仅点击提交都不能当作网站确认完成');
|
||||
assert.equal(state.completed, 0); assert.equal(await page.evaluate(() => qaExamCalls), 1);
|
||||
}
|
||||
await page.evaluate(flagOnly => { qaExam = flagOnly ? { stage: 'unknown', submitted: true, confirmed: true } : { stage: 'submitted', submitted: true, confirmed: true }; }, locallyPaused);
|
||||
state = await tick(); assert.equal(state.tasks[0].status, 'completed'); assert.equal(state.completed, 1);
|
||||
assert.equal(await page.evaluate(() => qaExamCalls), 1, '人工交卷完成只读同步,不得重新启动答题');
|
||||
assert.equal(await page.evaluate(() => qaReloads), 0); assert.equal(await page.evaluate(() => qaNavigations.length), 0);
|
||||
checks++;
|
||||
}
|
||||
|
||||
await setup('<app-examdetail><div class="examdetail_header">没有题库的考试</div><div class="examdetail_button" onclick="window.qaWrongEnter=true">开始答题</div></app-examdetail>', { mode: 'exam' });
|
||||
state = await page.evaluate(() => qaEngine.start()); assert.equal(state.tasks[0].status, 'skipped'); assert.equal(await page.evaluate(() => Boolean(window.qaWrongEnter)), false); checks++;
|
||||
|
||||
@@ -186,6 +215,27 @@ async function main() {
|
||||
const exclusive = await page.evaluate(async () => { window.qaSecond = qaCreate(); await qaSecond.tick(); await qaSecond.resume(); return { first: qaEngine.getState(), second: qaSecond.getState(), plays: qaPlayCalls }; });
|
||||
assert.equal(exclusive.plays, 1, '同类租约阻止第二页面重复播放'); assert.match(exclusive.second.tasks[0].reason, /另一个页面/); checks++;
|
||||
|
||||
await setup(videoHTML, { manualConfirmIncomplete: false });
|
||||
await page.evaluate(() => qaEngine.start()); await page.evaluate(() => qaEngine.pause());
|
||||
state = await page.evaluate(async () => { qaEngine.destroy(); await new Promise(r => setTimeout(r, 0)); qaEngine = qaCreate(); return qaEngine.tick(); });
|
||||
assert.equal(state.status, 'paused'); assert.equal(state.manualConfirmIncomplete, false, '单页检查点恢复关闭策略且保持暂停');
|
||||
await page.evaluate(() => qaEngine.configure({ manualConfirmIncomplete: true }));
|
||||
assert.equal(await page.evaluate(async () => (await qaStorage.get('session.v1')).config.manualConfirmIncomplete), true);
|
||||
assert.equal((await read()).status, 'paused'); checks++;
|
||||
|
||||
const stalePolicyRead = await page.evaluate(async () => {
|
||||
qaEngine.destroy(); await new Promise(r => setTimeout(r, 0));
|
||||
const previous = qaStorage; let release; let held; let holdNext = false;
|
||||
const gate = new Promise(resolve => { release = resolve; }); const captured = new Promise(resolve => { held = resolve; });
|
||||
qaStorage = { get: async key => { const value = await previous.get(key); if (key === 'control.v1' && holdNext) { holdNext = false; held(); await gate; } return value; }, set: previous.set };
|
||||
qaEngine = qaCreate(); await qaEngine.tick();
|
||||
holdNext = true; const oldTick = qaEngine.tick(); await captured;
|
||||
await qaEngine.configure({ manualConfirmIncomplete: false }); release(); await oldTick;
|
||||
return { state: qaEngine.getState(), shared: await qaStorage.get('control.v1') };
|
||||
});
|
||||
assert.equal(stalePolicyRead.state.manualConfirmIncomplete, false, '迟到的旧共享读数不得覆盖刚发布的开关');
|
||||
assert.equal(stalePolicyRead.shared.config.manualConfirmIncomplete, false); assert.equal(stalePolicyRead.state.status, 'paused'); checks++;
|
||||
|
||||
// 两个实际标签通过同一GM式存储确认工作页,暂停信号同步,完全隔离真实业务网络。
|
||||
const shared = new Map();
|
||||
await context.exposeBinding('__learningGet', (_, key) => shared.has(key) ? JSON.parse(JSON.stringify(shared.get(key))) : null);
|
||||
@@ -214,6 +264,18 @@ async function main() {
|
||||
assert.equal(state.exam.predictedScore, 84); assert.equal(state.exam.verified, 32); assert.equal(state.etaSeconds, 240, '并行总览显示另一场考试证据和剩余时间');
|
||||
await page.evaluate(() => qaEngine.pause()); workerState = await workerPage.evaluate(() => fixtureEngine.tick());
|
||||
assert.equal(workerState.status, 'paused'); assert.ok(await workerPage.evaluate(() => fixtureExamPause) > 0);
|
||||
await page.evaluate(() => qaEngine.configure({ manualConfirmIncomplete: false }));
|
||||
workerState = await workerPage.evaluate(() => fixtureEngine.tick()); state = await tick();
|
||||
assert.equal(state.manualConfirmIncomplete, false); assert.equal(workerState.manualConfirmIncomplete, false, '主页面关闭开关同步工作页');
|
||||
assert.equal(state.status, 'paused'); assert.equal(workerState.status, 'paused');
|
||||
await workerPage.evaluate(() => fixtureEngine.configure({ manualConfirmIncomplete: true }));
|
||||
state = await tick(); workerState = await workerPage.evaluate(() => fixtureEngine.tick());
|
||||
assert.equal(state.manualConfirmIncomplete, true, '工作页开启开关反向同步主页面'); assert.equal(workerState.manualConfirmIncomplete, true);
|
||||
assert.equal(state.status, 'paused'); assert.equal(workerState.status, 'paused', '策略同步不能自动恢复任务');
|
||||
assert.equal(await workerPage.evaluate(() => fixtureExamCalls), 1, '修改共享策略期间不得重启答题');
|
||||
const synchronizedControl = shared.get('control.v1');
|
||||
assert.equal(synchronizedControl.status, 'paused'); assert.equal(synchronizedControl.config.manualConfirmIncomplete, true);
|
||||
assert.equal(shared.get('session.v1').config.manualConfirmIncomplete, true, '主页面暂停时同步保存工作页策略'); checks++;
|
||||
await page.evaluate(() => qaEngine.resume()); await workerPage.evaluate(() => fixtureEngine.tick()); workerState = await workerPage.evaluate(() => fixtureEngine.tick());
|
||||
assert.equal(workerState.status, 'running'); assert.equal(await workerPage.evaluate(() => fixtureExamCalls), 2, '全局继续后重新核验考试');
|
||||
await workerPage.evaluate(() => fixtureEngine.pause()); state = await tick(); assert.equal(state.status, 'paused', '工作页的全局暂停同步主页面');
|
||||
|
||||
Reference in New Issue
Block a user