'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, '
本地HTML验收
', 'utf8'); try { await context.route('**/*', route => { const url = new URL(route.request().url()); let content = '普通网页验收
'; if (url.pathname === '/framed') content = ''; else if (url.pathname === '/child') content = ''; else if (url.pathname === '/nested') content = ''; if (url.pathname === '/blocks-document') content += ''; return route.fulfill({ status: 200, contentType: 'text/html', body: `${content}` }); }); 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); } });