116 lines
7.3 KiB
JavaScript
116 lines
7.3 KiB
JavaScript
'use strict';
|
|
|
|
// Local browser fixtures cover URL protocols and frame focus without opening
|
|
// any production site or depending on a user's extension configuration.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { pathToFileURL } = require('node:url');
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const record = { bankName: '全网址题库', bankId: 'global-bank', questionId: 'global-question', type: 'single',
|
|
stem: '全网页核验题的标准答案是什么?', options: [{ id: 'a', label: 'A', text: '正确内容' }, { id: 'b', label: 'B', text: '其他内容' }],
|
|
correctOptionIds: ['a'], answerTexts: ['正确内容'], answerStatus: 'confirmed' };
|
|
function installGM(record) {
|
|
window.unsafeWindow = window;
|
|
const values = new Map([['huayi-qb.records.v1', [record]], ['huayi-qb.config.v1', { collecting: true }]]);
|
|
window.__globalMenus = [];
|
|
window.GM_getValue = (key, fallback) => structuredClone(values.has(key) ? values.get(key) : fallback);
|
|
window.GM_setValue = (key, value) => values.set(key, structuredClone(value));
|
|
window.GM_deleteValue = key => values.delete(key);
|
|
window.GM_registerMenuCommand = (name, action) => window.__globalMenus.push({ name, action });
|
|
window.GM_addValueChangeListener = () => 1;
|
|
window.GM_removeValueChangeListener = () => {};
|
|
window.__originalFetch = window.fetch;
|
|
}
|
|
|
|
test('全网址:HTTP、HTTPS、内网地址、本地文件及iframe快捷键', { timeout: 45000 }, async t => {
|
|
let playwright;
|
|
try { playwright = require(process.env.QB_PLAYWRIGHT_MODULE || 'playwright'); }
|
|
catch { playwright = require(path.join(os.homedir(), '.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')); }
|
|
const cached = path.join(os.homedir(), 'AppData/Local/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-win64/chrome-headless-shell.exe');
|
|
const executable = process.env.QB_CHROMIUM_EXECUTABLE || process.env.QB_CHROMIUM_PATH || (fs.existsSync(cached) ? cached : undefined);
|
|
const browser = await playwright.chromium.launch({ headless: true, ...(executable ? { executablePath: executable } : {}) });
|
|
const context = await browser.newContext();
|
|
const errors = [];
|
|
const script = fs.readFileSync(path.join(ROOT, 'dist/huayi-question-bank.user.js'), 'utf8');
|
|
const localDir = fs.mkdtempSync(path.join(os.tmpdir(), 'huayi-qb-global-'));
|
|
const localFile = path.join(localDir, 'index.html');
|
|
fs.writeFileSync(localFile, '<!doctype html><html><body><p>本地HTML验收</p></body></html>', 'utf8');
|
|
try {
|
|
await context.route('**/*', route => {
|
|
const url = new URL(route.request().url());
|
|
let content = '<p>普通网页验收</p>';
|
|
if (url.pathname === '/framed') content = '<iframe src="https://child.example.test/child" id="child"></iframe>';
|
|
else if (url.pathname === '/child') content = '<input id="frame-input"><iframe src="https://nested.example.test/nested"></iframe>';
|
|
else if (url.pathname === '/nested') content = '<input id="frame-input">';
|
|
if (url.pathname === '/blocks-document') content += '<script>document.addEventListener("keydown",event=>event.stopImmediatePropagation(),true)</script>';
|
|
return route.fulfill({ status: 200, contentType: 'text/html', body: `<!doctype html><html><body>${content}</body></html>` });
|
|
});
|
|
await context.addInitScript({ content: `(${installGM.toString()})(${JSON.stringify(record)});\n${script}` });
|
|
context.on('page', page => page.on('pageerror', error => errors.push(error.message)));
|
|
await t.test('元数据覆盖三种协议,并允许iframe接收快捷键', () => {
|
|
const patterns = [...script.matchAll(/^\/\/ @match\s+(\S+)$/gm)].map(match => match[1]);
|
|
assert.deepEqual(patterns, ['http://*/*', 'https://*/*', 'file:///*']);
|
|
assert.equal(/^\/\/ @noframes\b/m.test(script), false);
|
|
});
|
|
for (const url of ['http://public.example.test/path', 'https://public.example.test/path?x=1#part',
|
|
'http://127.0.0.1:52345/local', 'https://intranet:8443/path', pathToFileURL(localFile).href]) {
|
|
await t.test(`${url.startsWith('file:') ? '本地file页面' : url}:默认隐藏、快捷键显示、搜索已导入标准答案`, async () => {
|
|
const page = await context.newPage();
|
|
try {
|
|
await page.goto(url);
|
|
await page.locator('#huayi-qb-root').waitFor({ state: 'attached' });
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), false);
|
|
await page.keyboard.press('Alt+Q');
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), true);
|
|
await page.locator('#qb-search').fill('全网页核验题');
|
|
await page.locator('#qb-search').press('Enter');
|
|
assert.match(await page.locator('#qb-results').innerText(), /正确内容/);
|
|
assert.equal(await page.evaluate(() => window.fetch === window.__originalFetch), true, '普通页面不观察业务请求');
|
|
await page.keyboard.press('Alt+Q');
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), false);
|
|
} finally { await page.close(); }
|
|
});
|
|
}
|
|
await t.test('网页在document阻止键盘传播时仍可唤出,缺少code的Q键也可用', async () => {
|
|
const page = await context.newPage();
|
|
try {
|
|
await page.goto('https://public.example.test/blocks-document');
|
|
await page.locator('#huayi-qb-root').waitFor({ state: 'attached' });
|
|
await page.keyboard.press('Alt+Q');
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), true);
|
|
await page.evaluate(() => window.dispatchEvent(new KeyboardEvent('keydown', { key: 'q', altKey: true })));
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), false);
|
|
await page.keyboard.press('Alt+Shift+Q');
|
|
assert.equal(await page.locator('#qb-panel').isVisible(), true, '旧快捷键仍可使用');
|
|
} finally { await page.close(); }
|
|
});
|
|
await t.test('跨域及嵌套iframe中按快捷键,控制顶层唯一面板且子页不启动存储或采集', async () => {
|
|
const page = await context.newPage();
|
|
try {
|
|
await page.goto('https://public.example.test/framed');
|
|
await page.locator('#huayi-qb-root').waitFor({ state: 'attached' });
|
|
const child = page.frame({ url: 'https://child.example.test/child' });
|
|
const nested = page.frame({ url: 'https://nested.example.test/nested' });
|
|
assert.ok(child); assert.ok(nested);
|
|
for (const frame of [child, nested]) {
|
|
assert.equal(await frame.locator('#huayi-qb-root').count(), 0);
|
|
assert.equal(await frame.evaluate(() => window.__globalMenus.length), 0);
|
|
assert.equal(await frame.evaluate(() => window.fetch === window.__originalFetch), true);
|
|
}
|
|
await child.locator('#frame-input').press('Alt+Q');
|
|
await page.locator('#qb-panel').waitFor({ state: 'visible' });
|
|
await nested.locator('#frame-input').press('Alt+Q');
|
|
await page.locator('#qb-panel').waitFor({ state: 'hidden' });
|
|
assert.equal(await page.locator('#huayi-qb-root').count(), 1);
|
|
} finally { await page.close(); }
|
|
});
|
|
assert.deepEqual(errors, []);
|
|
} finally {
|
|
await context.close(); await browser.close();
|
|
fs.unlinkSync(localFile); fs.rmdirSync(localDir);
|
|
}
|
|
});
|