feat: add unified learning workflows and six-tab assistant
This commit is contained in:
@@ -48,6 +48,11 @@ function documentHTML(fixture) {
|
||||
body{font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif;margin:24px;background:#f3f6fa;color:#23334c}h1{font-size:24px;margin:0 0 12px}.pc_examtest{display:grid;grid-template-columns:minmax(0,1fr) 205px;gap:20px;max-width:1120px}.pc_examtest_left{min-width:0}.pc_examtest_right{background:white;padding:15px;align-self:start;border-radius:9px}.pc_examtest_title{font-size:20px;margin-bottom:12px}.pc_examtest_content{background:white;padding:14px 18px;margin-bottom:9px;border:1px solid #e1e7ef;border-radius:8px;overflow-wrap:anywhere}.pc_examtest_content_question p{margin:0 0 8px}.pc_examtest_content_question_value,.pc_examtest_content_question_difficulty{color:#8792a3;font-size:11px}.pc_examtest_content_list_des{padding:6px 9px;cursor:pointer;border-radius:5px}.examtest_content_list_des_optionsort{display:inline-block;margin-right:8px}.examtest_content_list_des_checked{background:#e3f5e9;color:#246240}.hidden,[hidden]{display:none!important}.modal:not(.show):not(.in){display:none}.examine_box.show{position:fixed;z-index:1000;inset:130px auto auto 160px;background:white;border:1px solid #ccd6e2;padding:24px;border-radius:10px}.pc_examtest_countdown{margin-bottom:15px}.pc_examtest_card{display:flex;flex-wrap:wrap;gap:5px;margin-top:12px}.examtestcard_count_defult{border:1px solid #d7dee7;border-radius:4px;padding:2px 5px;font-size:11px}.examtestcard_count_assignment{background:#e4f4e9;color:#296343}.pc_examtest_commit{margin-top:14px;background:#285fd3;color:white;padding:8px;border-radius:6px;text-align:center;cursor:pointer}.pc_examtest_right_col{display:flex;gap:15px}.pc_examtest_right_col h4{margin:4px 0}.pc_examtest_content img{max-width:100%}@media(max-width:700px){body{margin:12px}.pc_examtest{display:block}.pc_examtest_right{display:none}.pc_examtest_content{padding:12px}}
|
||||
</style></head><body onselectstart="return false" oncontextmenu="return false"><h1>考试答案全文核对</h1><p>本地验证:40 道保存题目,交卷由用户操作。</p><app-examtest>${fixture.html}</app-examtest></body></html>`;
|
||||
}
|
||||
async function activate(page, key) {
|
||||
const expand = page.getByRole('button', { name: '展开题库助手', exact: true });
|
||||
if (await expand.isVisible()) await expand.click();
|
||||
await page.getByTestId('tab-' + key).click();
|
||||
}
|
||||
|
||||
// 模拟题目组件自己的作答状态。点击先更新独立模型,再根据模型渲染 class 与答题卡。
|
||||
// 不加载原站 Angular,也不实现提交接口。
|
||||
@@ -152,9 +157,12 @@ function installExamModel() {
|
||||
await page.addScriptTag({ content: sandboxScript });
|
||||
await page.evaluate(installExamModel);
|
||||
await page.evaluate(() => YianAssistant.ready);
|
||||
await page.evaluate(() => YianAssistant.setLearningSetting('answerInterval', 0));
|
||||
await activate(page, 'exam');
|
||||
}
|
||||
await open(page, examURL);
|
||||
assert.equal(await page.getByRole('switch', { name: '自动选择答案' }).isChecked(), false);
|
||||
await activate(page, 'settings');
|
||||
assert.equal(await page.getByRole('switch', { name: '导出题库', exact: true }).isChecked(), false);
|
||||
assert.equal(await page.locator('#yian-question-assistant .section-title').filter({ hasText: '当前考试' }).count(), 1);
|
||||
const extracted = await page.evaluate(() => YianAssistant.collectCurrent().questions);
|
||||
@@ -164,6 +172,7 @@ function installExamModel() {
|
||||
assert(!/(?:难度低|难度中|难度高)/.test(question.question), '题干应去除难度');
|
||||
});
|
||||
const bankFiles = realSample ? bankPaths : { name: '三岗位考试题库.json', mimeType: 'application/json', buffer: Buffer.from(JSON.stringify(fixture.records)) };
|
||||
await activate(page, 'bank');
|
||||
await page.locator('#yian-question-assistant input[type=file]').setInputFiles(bankFiles);
|
||||
await page.waitForFunction(() => YianAssistant.getSnapshot().counts.主操 > 0 && YianAssistant.getSnapshot().counts.班长 > 0 && YianAssistant.getSnapshot().counts.副操 > 0);
|
||||
console.log('PASS ' + (realSample ? '真实保存考试 DOM 与仓库三岗位 CSV 导入' : '可独立运行的 40 题 / 三岗位内部 fixture') + '、考试识别、默认关闭、分值与难度去除');
|
||||
@@ -171,9 +180,16 @@ function installExamModel() {
|
||||
const pages = [page, await context.newPage(), await context.newPage()];
|
||||
pages.slice(1).forEach(capture);
|
||||
await Promise.all(pages.map(async (target, index) => {
|
||||
if (index) await open(target, examURL + '&rolecase=' + index);
|
||||
await target.getByRole('radio', { name: roles[index], exact: true }).check();
|
||||
if (index) await open(target, examURL + '&sessioncase=' + index);
|
||||
await target.evaluate(async () => {
|
||||
const title = YianAssistant.collectCurrent().title;
|
||||
const courses = Array.from(new Set(YianAssistant.getSnapshot().records.map(record => record.course))).filter(Boolean);
|
||||
await YianAssistant.setLearningSetting('associations', { [title]: courses });
|
||||
});
|
||||
await activate(target, 'exam');
|
||||
assert.equal(await target.getByRole('radio').count(), 0, '统一题库没有岗位选择');
|
||||
await target.waitForFunction(() => YianAssistant.getContext().ready);
|
||||
assert((await target.evaluate(() => YianAssistant.getContext().courses || [])).length > 0, '明确关联后须确认可用题库主题,再启动答题');
|
||||
const expectations = await target.evaluate(() => {
|
||||
const context = YianAssistant.getContext(), snapshot = YianAssistant.getSnapshot();
|
||||
return YianAssistant.collectCurrent().questions.map(question => {
|
||||
@@ -181,14 +197,24 @@ function installExamModel() {
|
||||
return { number: question.number, type: question.type, status: matched.status, reason: matched.reason,
|
||||
originalAnswer: matched.records[0] && matched.records[0].answer,
|
||||
bankTexts: matched.records[0] && matched.records[0].correctTexts,
|
||||
currentLabels: matched.correctLabels, evidenceTexts: matched.correctTexts };
|
||||
currentLabels: matched.correctLabels, evidenceTexts: matched.correctTexts,
|
||||
sourceRoles: Array.from(new Set((matched.records || []).map(record => record.role))).sort() };
|
||||
});
|
||||
});
|
||||
assert.equal(expectations.filter(item => item.status === 'matched').length, 40, roles[index] + '应匹配 40 / 40;' + JSON.stringify(expectations.filter(item => item.status !== 'matched')));
|
||||
assert.equal(expectations.filter(item => item.status === 'matched').length, 40, '统一题库应匹配 40 / 40;' + JSON.stringify(expectations.filter(item => item.status !== 'matched')));
|
||||
expectations.forEach(item => assert.deepEqual(item.sourceRoles, roles.slice().sort(), '同题聚合保留三个岗位来源'));
|
||||
assert.equal(expectations.filter(item => item.currentLabels.join('') !== item.originalAnswer).length, 20, '应有 20 题需要按全文重新映射字母');
|
||||
target.__expectations = expectations;
|
||||
await target.getByRole('switch', { name: '自动选择答案' }).check();
|
||||
await target.waitForFunction(() => YianAssistant.getState().metrics.selected === 40, undefined, { timeout: 35000 });
|
||||
await target.waitForFunction(() => YianAssistant.getState().metrics.selected === 40, undefined, { timeout: 35000 }).catch(async error => {
|
||||
console.error('考试超时诊断:' + JSON.stringify(await target.evaluate(() => {
|
||||
const state = YianAssistant.getState(), context = YianAssistant.getContext();
|
||||
return { autoAnswer: state.autoAnswer, metrics: state.metrics, examStage: state.exam.stage, examMessage: state.exam.message,
|
||||
ready: context.ready, courses: context.courses, clicks: examModel.clicks.length,
|
||||
notice: document.querySelector('#yian-question-assistant').shadowRoot.querySelector('.notice').textContent };
|
||||
})));
|
||||
throw error;
|
||||
});
|
||||
const actual = await target.evaluate(() => {
|
||||
const bank = YianAssistant.collectCurrent();
|
||||
const rows = Array.from(document.querySelectorAll('.pc_examtest_content'));
|
||||
@@ -204,12 +230,12 @@ function installExamModel() {
|
||||
assert.equal(actual.cards, 40, '独立模型应更新全部答题卡');
|
||||
assert.equal(actual.evidence.length, 40);
|
||||
expectations.forEach((expected, qIndex) => {
|
||||
assert.deepEqual(sortedTexts(actual.selectedTexts[qIndex]), sortedTexts(expected.bankTexts), roles[index] + '第 ' + expected.number + ' 题 DOM 应等于题库全文');
|
||||
assert.deepEqual(sortedTexts(actual.modelTexts[qIndex]), sortedTexts(expected.bankTexts), roles[index] + '第 ' + expected.number + ' 题独立作答模型应等于题库全文');
|
||||
assert.deepEqual(sortedTexts(actual.selectedTexts[qIndex]), sortedTexts(expected.bankTexts), '会话 ' + (index + 1) + ' 第 ' + expected.number + ' 题 DOM 应等于题库全文');
|
||||
assert.deepEqual(sortedTexts(actual.modelTexts[qIndex]), sortedTexts(expected.bankTexts), '会话 ' + (index + 1) + ' 第 ' + expected.number + ' 题独立作答模型应等于题库全文');
|
||||
expected.evidenceTexts.forEach((text, optionIndex) => assert(normalize(actual.evidence[qIndex]).includes(normalize(expected.currentLabels[optionIndex] + '. ' + text)), '核对面板应显示原库全文与对应当前字母'));
|
||||
});
|
||||
await target.getByRole('switch', { name: '自动选择答案' }).uncheck();
|
||||
console.log('PASS ' + roles[index] + ':40 / 40 已选择,20 题字母变化;所有 DOM、独立模型及核对面板均与原题库正确答案全文一致');
|
||||
console.log('PASS 统一题库会话 ' + (index + 1) + ':40 / 40 已选择,20 题字母变化;三岗位来源聚合,所有 DOM、独立模型及核对面板均与正确答案全文一致');
|
||||
}));
|
||||
await Promise.all(pages.slice(1).map(target => target.close()));
|
||||
const evidenceDetails = page.locator('#yian-question-assistant .answer-check');
|
||||
@@ -224,10 +250,12 @@ function installExamModel() {
|
||||
assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), '小屏页面不得横向溢出');
|
||||
await page.screenshot({ path: path.join(qaDir, 'exam-mobile.png') });
|
||||
await page.setViewportSize({ width: 1366, height: 900 });
|
||||
await activate(page, 'settings');
|
||||
await page.evaluate(() => YianAssistant.setExportEnabled(true));
|
||||
assert.equal(await page.getByRole('button', { name: '导出 CSV', exact: true }).isDisabled(), true);
|
||||
assert.equal(await page.evaluate(() => { try { YianAssistant.exportCurrent('csv'); return ''; } catch (error) { return error.message; } }), '请先进入支持的练习页面');
|
||||
await page.evaluate(() => YianAssistant.setExportEnabled(false));
|
||||
await activate(page, 'exam');
|
||||
console.log('PASS 答案核对展开状态 / 节点复用、桌面与小屏截图、考试禁用导出');
|
||||
|
||||
let sequence = 0;
|
||||
@@ -246,6 +274,7 @@ function installExamModel() {
|
||||
await page.evaluate(installExamModel);
|
||||
if (waitReady) await page.waitForFunction(() => YianAssistant.getContext().ready);
|
||||
else await page.waitForTimeout(1600);
|
||||
await activate(page, 'exam');
|
||||
}
|
||||
await reset();
|
||||
await page.getByRole('switch', { name: '自动选择答案' }).check();
|
||||
|
||||
Reference in New Issue
Block a user