fix: resume video playback and advance linked exams with attempt controls
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/* The common attempt policy reads visible scoped DOM, never Angular/network state. */
|
||||
'use strict';
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ channel: process.env.PLAYWRIGHT_CHANNEL || 'chrome', headless: true });
|
||||
const requests = [], errors = [];
|
||||
try {
|
||||
const context = await browser.newContext();
|
||||
await context.route('**/*', route => { requests.push(route.request().url()); return route.abort(); });
|
||||
const page = await context.newPage();
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
await page.addScriptTag({ content: fs.readFileSync(path.join(__dirname, '..', 'attempt-policy.js'), 'utf8') });
|
||||
async function read(html, tag = 'app-examdetail') {
|
||||
return page.evaluate(({ html, tag }) => {
|
||||
document.body.innerHTML = '<style>app-examdetail,app-examtest,.question-modal-pc{display:block;padding:12px}.hidden{display:none}</style><' + tag + ' id="scope">' + html + '</' + tag + '>';
|
||||
return YianAttemptPolicy.read(document.querySelector('#scope'));
|
||||
}, { html, tag });
|
||||
}
|
||||
const positive = [
|
||||
'允许答题次数:1次', '总考试次数:1', '最多作答次数为一次', '剩余考试次数:1 次',
|
||||
'可用答题机会:1次', '考试总次数:1次', '答题次数上限:1次', '允许答题次数:仅1次', '考试机会:仅剩1次',
|
||||
'本次仅可答题1次', '仅允许考试一次', '仅有1次答题机会', '本场只剩一次考试机会',
|
||||
'允许作答1次', '总共可考试1次', '还可补考 1 次', '还可补考一次', '剩余答题次数:1次',
|
||||
'剩余 1 次答题机会', '还有一次考试机会', '还可重考1次',
|
||||
'不及格,还可补考 1 次', '(不及格,还可补考 1 次)',
|
||||
'<button>重新答题(不及格,还可补考 1 次)</button>', '<button>重新考试(不及格,还可补考1次)</button>',
|
||||
'<button>重新答题<span>(不及格,还可补考 <strong>1</strong> 次)</span></button>',
|
||||
'<div><span>允许答题次数:</span><strong>1</strong><span>次</span></div>',
|
||||
'<dl><dt>剩余考试次数</dt><dd>1 次</dd></dl>'
|
||||
];
|
||||
for (const html of positive) {
|
||||
const result = await read(html);
|
||||
assert.equal(result.blocked, true, html);
|
||||
assert.equal(result.known, true, html);
|
||||
assert.equal(result.count, 1, html);
|
||||
assert(result.reason.includes('1 次'), html);
|
||||
}
|
||||
console.log('PASS explicit allowed/total/remaining counts, split inline labels, definition rows and real retake wording');
|
||||
for (const html of ['剩余考试次数:0次', '还可补考零次', '允许答题次数:0', '重新答题(不及格,还可补考0次)']) {
|
||||
const result = await read(html); assert(result.blocked, html); assert.equal(result.count, 0, html);
|
||||
}
|
||||
for (const html of ['总考试次数:2次', '剩余答题次数:12次', '还可补考十一次', '允许答题次数:无限次', '重新考试(不及格,还可补考3次)']) {
|
||||
const result = await read(html); assert.equal(result.blocked, false, html); assert.equal(result.count, null, html);
|
||||
}
|
||||
assert.equal((await read('<p>总考试次数:3次</p><p>剩余考试次数:1次</p>')).count, 1);
|
||||
assert.equal((await read('<p>总考试次数:1次</p><p>还可补考0次</p>')).count, 0);
|
||||
console.log('PASS zero/single attempts stop, larger counts are not blocked, remaining zero wins over total one');
|
||||
const ignored = [
|
||||
'已答次数:1次', '已用答题次数:1次', '已考试次数:1次', '已答题数1/40', '已答 1/40', '157人已交卷',
|
||||
'已答题次数:1次,答对 1 题', '答题次数:1', '考试次数:1次', '考试题数:1',
|
||||
'剩余考试次数:1/3', '允许答题次数:1.5次', '允许答题次数:-1次', '还可补考1次是正确答案',
|
||||
'<div class="examtest_content_question">仅有1次答题机会</div>',
|
||||
'<div class="examtest_content_question">(不及格,还可补考1次)是这道题的答案</div>',
|
||||
'<div class="model_answer">重新答题(不及格,还可补考1次)</div>',
|
||||
'<div class="pc_examtest_content_question">允许答题次数1次</div>',
|
||||
'<div class="examtest_content_list_des">还可补考1次</div>',
|
||||
'<div class="model_answer">剩余考试次数1次</div>',
|
||||
'<div data-question-stem>总考试次数1次</div>', '<div class="answer-text">允许答题次数1次</div>',
|
||||
'<div hidden>剩余考试次数1次</div>', '<div aria-hidden="true">还可补考1次</div>',
|
||||
'<div class="hidden">仅有1次答题机会</div>', '<div style="visibility:hidden">总考试次数1次</div>',
|
||||
'<div style="opacity:0"><span>剩余考试次数1次</span></div>', '<div inert>还可补考1次</div>',
|
||||
'<template>允许答题次数1次</template>', '<script type="application/json">{"totalExamCount":1}</script>',
|
||||
'<div data-max-attempts="1" data-remaining-attempts="1">考试已就绪</div>', '<p>这里只剩1题要答</p>'
|
||||
];
|
||||
for (const html of ignored) {
|
||||
const result = await read(html);
|
||||
assert.equal(result.blocked, false, html); assert.equal(result.count, null, html); assert.equal(result.signature, '[]', html);
|
||||
assert.equal(result.known, false, html);
|
||||
}
|
||||
console.log('PASS used counts, question totals, people, decimals, hidden/answer/question text and private/data attributes do not infer limits');
|
||||
for (const tag of ['app-examtest', 'div']) assert.equal((await read('剩余考试次数:1次', tag)).count, 1);
|
||||
const before = await read('<p>总考试次数:3次</p>');
|
||||
const after = await read('<p>总考试次数:2次</p>');
|
||||
assert(before.known && after.known, 'larger explicit count is known even though it does not block');
|
||||
assert.notEqual(before.signature, after.signature, 'larger explicit count changes invalidate earlier evidence');
|
||||
const stable = await read('<p>剩余考试次数:1次</p><p hidden>还可补考0次</p>');
|
||||
assert.equal(stable.signature, (await read('<p>剩余考试次数:1次</p>')).signature);
|
||||
await page.evaluate(() => { document.querySelector('#scope').insertAdjacentHTML('afterend', '<app-examtest id="other">还可补考0次</app-examtest>'); });
|
||||
assert.equal(await page.evaluate(() => YianAttemptPolicy.read(document.querySelector('#scope')).count), 1, 'another exam outside scope is ignored');
|
||||
for (const invalid of ['document', 'body', 'null', 'detached']) {
|
||||
const result = await page.evaluate(invalid => YianAttemptPolicy.read(invalid === 'document' ? document : invalid === 'body' ? document.body : invalid === 'detached' ? document.createElement('app-examtest') : null), invalid);
|
||||
assert.equal(result.blocked, false); assert.equal(result.signature, '[]');
|
||||
}
|
||||
assert.deepEqual(requests, []); assert.deepEqual(errors, []);
|
||||
console.log('PASS scoped containers only, stable signatures, explicit changes, no network or browser errors');
|
||||
} finally { await browser.close(); }
|
||||
})().catch(error => { console.error(error); process.exitCode = 1; });
|
||||
Reference in New Issue
Block a user