feat: add draggable panel and restrict assistant to site directory
This commit is contained in:
+31
-11
@@ -1,14 +1,16 @@
|
||||
/* 真实浏览器验证:只启动本机测试服务,不请求原网站。 */
|
||||
/* 真实浏览器验证:主站 URL 仅由本机服务模拟,不请求业务服务器。 */
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const http = require('node:http');
|
||||
const { chromium } = require('playwright');
|
||||
const { pathToFileURL } = require('node:url');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const coreSource = fs.readFileSync(path.join(root, 'question-bank-core.js'), 'utf8');
|
||||
const scriptSource = fs.readFileSync(path.join(root, '谊安学助手 2.0.user.js'), 'utf8');
|
||||
assert.deepEqual(Array.from(scriptSource.matchAll(/^\/\/ @match\s+(.+)$/gm), match => match[1].trim()), ['http://10.119.67.7/clientcn/*']);
|
||||
const qaDir = process.env.YIAN_QA_DIR || path.join(os.tmpdir(), 'yianxue-export-qa');
|
||||
fs.mkdirSync(qaDir, { recursive: true });
|
||||
const samplePath = path.join(root, '网页样本', '考试答案', '谊安学.html');
|
||||
@@ -56,11 +58,24 @@ function parseCSV(text) {
|
||||
}
|
||||
});
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
const origin = `http://127.0.0.1:${server.address().port}`;
|
||||
const localOrigin = `http://127.0.0.1:${server.address().port}`;
|
||||
const origin = 'http://10.119.67.7';
|
||||
let browser;
|
||||
try {
|
||||
browser = await chromium.launch({ channel: process.env.PLAYWRIGHT_CHANNEL || 'chrome', headless: true });
|
||||
const context = await browser.newContext({ acceptDownloads: true, viewport: { width: 1366, height: 900 } });
|
||||
const externalRequests = [];
|
||||
const toolURL = pathToFileURL(path.join(root, '离线导出题库.html')).href;
|
||||
const coreURL = pathToFileURL(path.join(root, 'question-bank-core.js')).href;
|
||||
await context.route('**/*', async route => {
|
||||
const url = new URL(route.request().url());
|
||||
if (url.origin === origin) {
|
||||
const response = await route.fetch({ url: localOrigin + url.pathname + url.search });
|
||||
return route.fulfill({ response });
|
||||
}
|
||||
if (url.origin === localOrigin || url.href === toolURL || url.href === coreURL || url.protocol === 'file:' && url.href.startsWith(pathToFileURL(qaDir).href + '/')) return route.continue();
|
||||
externalRequests.push(url.href); return route.abort();
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const errors = []; page.on('pageerror', error => errors.push(error.message));
|
||||
page.on('console', message => { if (message.type() === 'error' || message.type() === 'warning') errors.push(message.text()); });
|
||||
@@ -176,7 +191,6 @@ function parseCSV(text) {
|
||||
const tool = await context.newPage();
|
||||
tool.on('pageerror', error => errors.push(error.message));
|
||||
tool.on('console', message => { if (message.type() === 'error' || message.type() === 'warning') errors.push(message.text()); });
|
||||
const toolURL = require('node:url').pathToFileURL(path.join(root, '离线导出题库.html')).href;
|
||||
await tool.goto(toolURL);
|
||||
assert.equal(tool.url(), toolURL);
|
||||
assert.equal(await tool.title(), '离线导出题库');
|
||||
@@ -210,18 +224,24 @@ function parseCSV(text) {
|
||||
await tool.screenshot({ path: path.join(qaDir, 'offline-mobile.png'), fullPage: true });
|
||||
console.log('PASS 离线工具文件导入、三格式下载、无联网、桌面与手机布局');
|
||||
|
||||
// 验证原网页的文档级保存注释可以激活 file: 模式。
|
||||
// 文档级保存注释不能绕过 file 范围限制,独立离线工具仍可用。
|
||||
const local = await context.newPage();
|
||||
const localHTML = '<!doctype html><!-- saved from url=(0100)http://10.119.67.7/clientcn/exam/local/questions?flag=exercise -->' + fixture('本地练习');
|
||||
local.on('pageerror', error => errors.push(error.message));
|
||||
local.on('download', download => downloads.push(download));
|
||||
const localHTML = '<!doctype html><!-- saved from url=(0100)http://10.119.67.7/clientcn/exam/local/questions?flag=exercise -->' + fixture('本地练习').replace('<app-examtest>', '<body onselectstart="return false" oncontextmenu="return false" style="user-select:none"><app-examtest>');
|
||||
const localPath = path.join(qaDir, 'local-practice.html'); fs.writeFileSync(localPath, localHTML);
|
||||
await local.goto(require('node:url').pathToFileURL(localPath).href);
|
||||
await local.goto(pathToFileURL(localPath).href);
|
||||
const beforeBody = await local.evaluate(() => ({ html: document.body.innerHTML, select: document.body.getAttribute('onselectstart'), context: document.body.getAttribute('oncontextmenu'), style: document.body.getAttribute('style') }));
|
||||
const beforeLocal = downloads.length;
|
||||
await local.addScriptTag({ content: scriptSource });
|
||||
await local.evaluate(() => YianAssistant.ready);
|
||||
const localDownload = local.waitForEvent('download');
|
||||
await local.getByRole('switch', { name: '导出题库', exact: true }).check();
|
||||
assert.equal((await localDownload).suggestedFilename(), '本地练习_题库_3题.csv');
|
||||
await local.waitForTimeout(150);
|
||||
assert.equal(await local.locator('#yian-question-assistant').count(), 0);
|
||||
assert.deepEqual(await local.evaluate(() => [typeof window.JSZip, typeof window.QuestionBankCore, typeof window.YianAssistant]), ['undefined', 'undefined', 'undefined']);
|
||||
assert.deepEqual(await local.evaluate(() => ({ html: document.body.innerHTML, select: document.body.getAttribute('onselectstart'), context: document.body.getAttribute('oncontextmenu'), style: document.body.getAttribute('style') })), beforeBody);
|
||||
assert.equal(downloads.length, beforeLocal, 'file 保存网页不能自动下载');
|
||||
assert.deepEqual(externalRequests, [], '业务 URL 均通过本机模拟;不得请求其他网站');
|
||||
assert.equal(errors.length, 0, '浏览器不应有控制台/运行时错误:' + errors.join('\n'));
|
||||
console.log('PASS 本地保存网页自动识别与浏览器错误检查');
|
||||
console.log('PASS file 保存网页不注入/下载或改变复制限制;独立离线导出工具保持可用,无外部请求与浏览器错误');
|
||||
console.log('验证截图和下载文件:' + qaDir);
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
|
||||
Reference in New Issue
Block a user