// 临时窗外释放调试(验证后删除) import { createServer, type Server } from 'node:http'; import { expect, test, type Page } from '@playwright/test'; import { cleanupSeededAlbums, createEmbedSlot, flushOutbox, flushSync, seedAlbum, seedPhoto, updateEmbedSlot, } from './helpers'; const HOST_PORT = 3976; const MINUTE = 60_000; let slotToken = ''; test.beforeAll(async () => { const album = await seedAlbum({ alias: 'outsidedbg', defaultLocale: 'zh-CN', translations: [{ locale: 'zh-CN', title: '窗外释放调试' }], }); for (let i = 1; i <= 6; i++) { await seedPhoto(album.id, `outsidedbg-p${i}`, { publishedAt: new Date(Date.now() - (7 - i) * MINUTE) }); } await flushSync(album.id); const slot = await createEmbedSlot('outside-dbg'); await updateEmbedSlot(slot.id, { albumId: album.id, settings: { position: 'right', size: 'md', intervalMs: 5000, lang: 'auto', collapsedDefault: false, imageFit: 'contain', offsetX: 16, offsetBottom: 24, zIndex: 1000, displayMode: 'vertical-stream', scrollSpeed: 16, reactionsEnabled: true, }, }); await flushOutbox(); slotToken = slot.token; }); const server: Server = createServer((_q, res) => { res.setHeader('content-type', 'text/html; charset=utf-8'); res.end(` `); }); test.afterAll(async () => { await cleanupSeededAlbums(); server.close(); }); test('outside release debug', async ({ page }: { page: Page }) => { page.on('console', (m) => { const txt = m.text(); if (txt.includes('stream-frame-error') || txt.includes('host-msg') || txt.includes('[ev]')) { console.log(txt.slice(0, 200)); } }); await new Promise((r) => server.listen(HOST_PORT, '127.0.0.1', r)); await page.setViewportSize({ width: 1440, height: 900 }); await page.goto(`http://127.0.0.1:${HOST_PORT}`); const frame = page.locator('iframe[src*="/embed/"][src*="v=2"]'); await frame.waitFor({ timeout: 20000 }); await page.waitForTimeout(3000); const f = page.frames().find((fr) => fr.url().includes('v=2')); expect(f).toBeDefined(); const offset = async () => { const t = await f!.locator('.stream-track').evaluate((el) => el.style.transform); return Number.parseFloat((/translateY\((-?[\d.]+)px\)/.exec(t) ?? ['0', '0'])[1]); }; const box = await frame.boundingBox(); const cx = (box ? box.x : 0) + 90; const cy = (box ? box.y : 0) + 150; const b1 = await offset(); await page.waitForTimeout(1000); console.log('[1 baseline]', b1 !== (await offset())); await f!.evaluate(() => { const log = (name: string) => (e: PointerEvent) => console.log(`[ev] ${name} type=${e.type} x=${e.clientX} y=${e.clientY}`); const vp = document.querySelector('.stream-viewport'); vp?.addEventListener('pointerdown', log('vp'), true); window.addEventListener('pointerup', log('win'), true); window.addEventListener('pointercancel', log('win'), true); window.addEventListener('pointerleave', log('win'), true); window.addEventListener('pointerout', log('win'), true); vp?.addEventListener('pointerleave', log('vp'), true); }); await page.mouse.move(cx, cy); await page.mouse.down(); await page.mouse.move(cx + 120, cy + 200, { steps: 4 }); await page.mouse.up(); await page.waitForTimeout(3500); const w1 = await offset(); await page.waitForTimeout(1000); console.log('[2 after outside]', w1 !== (await offset()), 'offset =', w1); console.log('[2 state]', await f!.evaluate(() => { const d = (window as unknown as { __embedDebug?: { reasons: Set; userPaused: { value: boolean } } }).__embedDebug; return `reasons=[${d ? [...d.reasons].join(',') : '?'}] userPaused=${d?.userPaused.value}`; })); // 手动在 iframe window 派发 pointerup——验证组件监听是否注册生效 await f!.evaluate(() => { window.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: 0, clientY: 0, pointerId: 1 })); }); await page.waitForTimeout(1500); console.log('[2b after manual pointerup]', await f!.evaluate(() => { const d = (window as unknown as { __embedDebug?: { reasons: Set } }).__embedDebug; return `reasons=[${d ? [...d.reasons].join(',') : '?'}]`; })); const s1 = await offset(); await page.waitForTimeout(15000); console.log('[3 15s moved]', Math.abs((await offset()) - s1).toFixed(0), 'px'); });