import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { after, before, describe, it } from 'node:test'; import type { Browser } from 'playwright-core'; import { launch, layout, settle, visit, VIEWPORTS } from './harness.ts'; const SITES = JSON.parse(readFileSync(new URL('../../web/integrations/jav-sites.json', import.meta.url), 'utf8')).sites; const SCRIPT = readFileSync(new URL('../../web/integrations/peach-jav.user.js', import.meta.url), 'utf8'); const RELEASE = JSON.parse(readFileSync(new URL('../../web/integrations/jav-userscript-release.json', import.meta.url), 'utf8')); const USERSCRIPT = { version: RELEASE.version, minimum_version: RELEASE.bridge_min_version, install_url: RELEASE.install_url }; describe('浏览器添加 JAV 设置', () => { let browser: Browser; before(async () => { browser = await launch(); }); after(async () => { await browser?.close(); }); for (const viewport of VIEWPORTS) { it(`${viewport.name}:启用、轮换与关闭,令牌只在签发响应后显示`, async () => { const opened = await visit(browser, '/settings/library?section=' + encodeURIComponent('下载渠道'), viewport); const { page } = opened; try { let enabled = false; const posted: Record[] = []; await page.route('**/api/configuration/jav-browser', async route => { const request = route.request(); const state = { enabled, provider: '115', available: true, userscript: USERSCRIPT, sites: SITES, providers: [ { key: '115', label: '115', configured: true, target: '/115/Movies' }, { key: 'pikpak', label: 'PikPak', configured: true, target: '/Movies' }, ] }; if (request.method() === 'POST') { const body = request.postDataJSON(); posted.push(body); enabled = body.action !== 'disable'; await route.fulfill({ json: { ...state, enabled, ...(enabled ? { token: 'test-issued-token-' + posted.length } : {}) } }); } else await route.fulfill({ json: state }); }); await page.reload(); const region = page.getByRole('region', { name: '浏览器添加 JAV', exact: true }); const install = region.getByRole('link', { name: '安装 Peach 用户脚本' }); await install.waitFor(); const channel = region.locator('button[aria-haspopup="listbox"]'); const installBox = await region.locator('[data-userscript-status]').boundingBox(); const channelBox = await channel.boundingBox(); assert.ok(installBox && channelBox && Math.abs(installBox.x - channelBox.x) <= 1, JSON.stringify({ installBox, channelBox })); assert.equal(await channel.locator('img').count(), 1); const status = region.locator('[data-userscript-status]').getByText('未启用', { exact: true }).or( region.locator('[data-userscript-status]').getByText('已启用', { exact: true })); assert.equal(await status.innerText(), '未启用'); const statusColor = () => status.evaluate(node => getComputedStyle(node).color); const disabledColor = await statusColor(); const themeColor = (token: string) => page.evaluate(token => { const sample = document.createElement('span'); sample.style.color = `var(${token})`; document.querySelector('.peach-react')!.append(sample); const color = getComputedStyle(sample).color; sample.remove(); return color; }, token); assert.equal(disabledColor, await themeColor('--color-text-secondary')); await channel.scrollIntoViewIfNeeded(); await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))); await channel.click(); const pikpak = page.getByRole('option', { name: 'PikPak', exact: true }); await pikpak.waitFor(); const overflow = await page.getByRole('listbox').evaluate(node => ({ width: node.clientWidth, scroll: node.scrollWidth })); assert.ok(overflow.scroll <= overflow.width + 1, JSON.stringify(overflow)); assert.equal(await pikpak.locator('img').count(), 1); const cloud115 = page.getByRole('option', { name: '115', exact: true }); assert.equal(await cloud115.locator('img').count(), 1); await cloud115.click(); await region.getByRole('button', { name: '启用并生成令牌', exact: true }).click(); const token = region.getByLabel('浏览器添加专用令牌', { exact: true }); await token.waitFor(); assert.equal(await status.innerText(), '已启用'); assert.notEqual(await statusColor(), disabledColor); assert.equal(await statusColor(), await themeColor('--color-status-lime-text')); const channelLabelBox = await region.getByText('浏览器添加的下载渠道', { exact: true }).boundingBox(); const channelFieldBox = await channel.boundingBox(); assert.ok(channelLabelBox && channelFieldBox); const channelGap = channelFieldBox.y - channelLabelBox.y - channelLabelBox.height; const inputGaps = await region.locator('[data-input-size]').evaluateAll(fields => fields.map(field => { const label = field.querySelector('label')!; const control = label.nextElementSibling!; return control.getBoundingClientRect().top - label.getBoundingClientRect().bottom; })); assert.equal(inputGaps.length, 2); assert.ok(inputGaps.every(gap => Math.abs(gap - channelGap) <= 0.5), JSON.stringify({ channelGap, inputGaps })); assert.equal(await token.inputValue(), 'test-issued-token-1'); assert.equal(await token.getAttribute('type'), 'password'); const refreshed = page.waitForResponse(response => response.url().endsWith('/api/configuration/jav-browser') && response.request().method() === 'GET'); await page.evaluate(() => window.dispatchEvent(new Event('visibilitychange'))); await refreshed; assert.equal(await token.inputValue(), 'test-issued-token-1'); await page.evaluate(() => { const copied: string[] = []; Object.assign(window, { pairingClipboard: copied }); Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: async (value: string) => { copied.push(value); } } }); }); await region.getByRole('button', { name: '复制配对信息', exact: true }).click(); const copied = await page.evaluate(() => (window as Window & { pairingClipboard: string[] }).pairingClipboard); assert.equal(copied.length, 1); assert.deepEqual(JSON.parse(copied[0]!), { peach_pairing: 1, base: new URL(page.url()).origin, token: 'test-issued-token-1' }); assert.equal(await region.getByRole('link', { name: '安装 Peach 用户脚本' }).getAttribute('href'), USERSCRIPT.install_url); assert.equal(await region.getByLabel('Peach 地址', { exact: true }).inputValue(), new URL(page.url()).origin); await region.getByRole('button', { name: '更新渠道与令牌', exact: true }).click(); await page.waitForFunction(() => document.querySelector( 'input[type="password"][value="test-issued-token-2"]') !== null); await region.getByRole('button', { name: '关闭浏览器添加', exact: true }).click(); await token.waitFor({ state: 'detached' }); assert.equal(await region.getByRole('button', { name: '复制配对信息', exact: true }).count(), 0); assert.equal(await status.innerText(), '未启用'); assert.equal(await statusColor(), disabledColor); assert.deepEqual(posted.map(body => body.action), ['enable', 'rotate', 'disable']); assert.ok(posted.every(body => body.provider === '115')); await settle(page); assert.ok((await layout(page)).scrollWidth <= viewport.width + 1); assert.deepEqual(opened.problems, []); await region.screenshot({ path: `../build/jav-browser-settings-${viewport.name}.png` }); } finally { await opened.close(); } }); } it('桥接页延迟加载仍能检测脚本,未知来源消息不改变检测结果', async () => { const opened = await visit(browser, '/settings/library?section=' + encodeURIComponent('下载渠道'), VIEWPORTS[0]!); const { page } = opened; try { await page.route('**/api/configuration/jav-browser', route => route.fulfill({ json: { enabled: true, provider: '115', available: true, userscript: USERSCRIPT, providers: [{ key: '115', label: '115', configured: true, target: '/Movies' }], } })); let installed = true; await page.route('**/integrations/peach-userscript-bridge', async route => { if (installed) await new Promise(resolve => setTimeout(resolve, 3500)); await route.fulfill({ contentType: 'text/html', body: '' + (installed ? '' : '') }); }); await page.reload(); const region = page.getByRole('region', { name: '浏览器添加 JAV', exact: true }); const connection = region.locator('[data-userscript-status] [data-connection-tone]'); await connection.getByText('已连接', { exact: true }).waitFor(); assert.equal(await connection.getAttribute('data-connection-tone'), 'success'); await region.getByText(`用户脚本 v${RELEASE.version}`, { exact: true }).waitFor(); assert.equal(await region.getByRole('link', { name: '安装 Peach 用户脚本' }).count(), 0); assert.equal(await region.getByLabel('浏览器添加专用令牌', { exact: true }).count(), 0); installed = false; await region.getByRole('button', { name: '重新检测', exact: true }).click(); await connection.getByText('未检测到', { exact: true }).waitFor(); assert.equal(await connection.getAttribute('data-connection-tone'), 'neutral'); await page.evaluate(() => window.postMessage({ type: 'peach-userscript:ready', protocol: 1, version: '9.0.0', paired: true, nonce: crypto.randomUUID() }, location.origin)); assert.equal(await connection.getByText('未检测到', { exact: true }).count(), 1); assert.equal(await region.getByRole('link', { name: '安装 Peach 用户脚本' }).count(), 1); assert.ok((await region.innerText()).includes('允许用户脚本')); assert.equal(await region.getByRole('link', { name: 'Tampermonkey 权限设置说明' }).getAttribute('href'), 'https://www.tampermonkey.net/faq.php#Q209'); assert.deepEqual(opened.problems, []); } finally { await opened.close(); } }); });