import { chromium } from 'playwright'; import fs from 'fs'; import path from 'path'; const outDir = path.resolve('.ui-review/accept-2026-09-19'); const log = []; const shot = async (page, name) => { const p = path.join(outDir, name); await page.screenshot({ path: p, fullPage: false }); log.push(`shot:${name}`); }; const result = { pass: [], fail: [], notes: [] }; async function main() { const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); page.setDefaultTimeout(12000); await page.goto('http://127.0.0.1:5173/', { waitUntil: 'networkidle' }); await shot(page, 'web-01-home.png'); // Channel switch try { const channel = page.getByRole('combobox').or(page.locator('select, [aria-label*="通道"], button:has-text("通道")')).first(); // Top bar has 通道 popup - from AX it was AXPopUpButton 发信通道 const pop = page.locator('text=通道').first(); await pop.click({ timeout: 5000 }).catch(() => {}); // Try select options visible const modes = ['自动', 'Web代理', '浏览器', '桌面']; let switched = false; for (const m of modes) { const opt = page.getByText(m, { exact: true }).first(); if (await opt.isVisible().catch(() => false)) { await opt.click(); switched = true; result.pass.push(`通道切换点击到「${m}」`); break; } } // Also try native select const select = page.locator('select').filter({ hasText: /自动|代理|浏览器|桌面/ }).first(); if (await select.count()) { const options = await select.locator('option').allTextContents(); result.notes.push('channel select options: ' + options.join('|')); if (options.length > 1) { await select.selectOption({ index: 1 }); await select.selectOption({ index: 0 }); switched = true; result.pass.push('通道 select 往返切换'); } } if (!switched) result.fail.push('通道切换:未找到可点选项'); await shot(page, 'web-02-channel.png'); } catch (e) { result.fail.push('通道切换异常: ' + e.message); } // Env switch - button 本地 1 变量 try { const envBtn = page.getByRole('button', { name: /变量|环境/ }).first(); if (await envBtn.count()) { await envBtn.click(); result.pass.push('打开环境/变量'); await page.keyboard.press('Escape').catch(() => {}); } else { result.fail.push('环境切换:未找到环境按钮'); } await shot(page, 'web-03-env.png'); } catch (e) { result.fail.push('环境: ' + e.message); } // Collection tree - click an existing request try { const req = page.getByText('回归 · GET Query').first(); await req.click({ timeout: 5000 }); result.pass.push('集合树点击切换到「回归 · GET Query」'); await shot(page, 'web-04-tree.png'); } catch (e) { result.fail.push('集合树点击: ' + e.message); } // Dirty save - edit URL then look for save/dirty indicator try { const urlInput = page.locator('input').filter({ hasText: '' }).nth(0); // Prefer visible URL field near method const candidates = page.locator('input[value*="http"], input[placeholder*="http"], textarea, input'); let edited = false; const count = await candidates.count(); for (let i = 0; i < Math.min(count, 30); i++) { const el = candidates.nth(i); const val = await el.inputValue().catch(() => ''); if (val.includes('http') || val.includes('127.0.0.1') || val.includes('/v1/')) { await el.click(); await el.fill(val + (val.includes('?') ? '&' : '?') + '_dirty=1'); edited = true; result.pass.push('编辑 URL 制造脏点'); break; } } if (!edited) result.notes.push('未定位到 URL 输入框,跳过脏点编辑'); // Try save shortcut or save button await page.keyboard.press('Meta+s').catch(() => {}); const saveBtn = page.getByRole('button', { name: /保存/ }).first(); if (await saveBtn.isVisible().catch(() => false)) { await saveBtn.click(); result.pass.push('点击保存'); } else { result.notes.push('未见「保存」按钮(可能自动持久化)'); } await shot(page, 'web-05-dirty.png'); } catch (e) { result.fail.push('脏点保存: ' + e.message); } // New WebSocket via 新建 menu try { await page.getByRole('button', { name: '新建' }).first().click(); await page.waitForTimeout(400); const wsItem = page.getByText(/WebSocket|WS/i).first(); if (await wsItem.isVisible().catch(() => false)) { await wsItem.click(); result.pass.push('新建 WebSocket'); } else { // menu items const items = await page.locator('[role="menu"] >> text=/WebSocket|websocket|WS/i').allTextContents().catch(() => []); result.notes.push('新建菜单项: ' + JSON.stringify(items)); result.fail.push('新建菜单未见 WebSocket'); } await shot(page, 'web-06-new-ws.png'); } catch (e) { result.fail.push('新建 WS: ' + e.message); } // Fill WS url + header if editor present try { const urlBox = page.locator('input').filter({ hasNot: page.locator('[type=checkbox]') }); // fill first empty-ish or wss field let filledUrl = false; const n = await page.locator('input').count(); for (let i = 0; i < n; i++) { const el = page.locator('input').nth(i); const ph = (await el.getAttribute('placeholder').catch(() => '')) || ''; const val = await el.inputValue().catch(() => ''); if (/wss?|url|地址/i.test(ph) || val.startsWith('ws') || (val === '' && /url/i.test(ph))) { await el.fill('wss://ws.postman-echo.com/raw'); filledUrl = true; break; } } if (!filledUrl) { // try active tab url field - often the long input const longInputs = page.locator('input.ops-input, input[class*="input"]'); if (await longInputs.count()) { await longInputs.first().fill('wss://ws.postman-echo.com/raw'); filledUrl = true; } } if (filledUrl) result.pass.push('填写 WS URL'); else result.fail.push('未能填写 WS URL'); // Headers tab const hdrTab = page.getByRole('tab', { name: /Header|请求头|头/i }).or(page.getByText('Headers')).first(); if (await hdrTab.isVisible().catch(() => false)) await hdrTab.click(); // Add Authorization row const addHdr = page.getByRole('button', { name: /添加|Add/ }).first(); if (await addHdr.isVisible().catch(() => false)) await addHdr.click(); // Fill key/value - best effort await page.getByPlaceholder(/[Kk]ey|名称|Header/).first().fill('Authorization').catch(() => {}); await page.getByPlaceholder(/[Vv]alue|值/).first().fill('Bearer smoke').catch(() => {}); // fallback: any two empty inputs in header editor result.notes.push('Header 填写尽力而为'); await shot(page, 'web-07-ws-filled.png'); const connectBtn = page.getByRole('button', { name: /连接|Connect/ }).first(); if (await connectBtn.isVisible().catch(() => false)) { await connectBtn.click(); await page.waitForTimeout(2500); result.pass.push('点击连接'); const bodyText = await page.locator('body').innerText(); if (/open|已连接|连接成功|ping|tunnel|自定义 Header/i.test(bodyText)) { result.pass.push('连接后 UI 出现成功/系统相关文案'); } else { result.notes.push('连接后未明确看到成功文案,需人工看截图'); } // try send const sendBox = page.locator('textarea, input[placeholder*="消息"], input[placeholder*="发送"]').first(); if (await sendBox.isVisible().catch(() => false)) { await sendBox.fill('ping-gui-reg'); const sendBtn = page.getByRole('button', { name: /发送|Send/ }).first(); if (await sendBtn.isVisible().catch(() => false)) await sendBtn.click(); await page.waitForTimeout(1500); result.pass.push('尝试发送消息'); } } else { result.fail.push('未见连接按钮'); } await shot(page, 'web-08-ws-connected.png'); } catch (e) { result.fail.push('WS 填表/连接: ' + e.message); } // History replay toast try { await page.getByRole('button', { name: /历史/ }).first().click(); await page.waitForTimeout(500); await shot(page, 'web-09-history.png'); const replay = page.getByRole('button', { name: /重放/ }).first(); if (await replay.isVisible().catch(() => false)) { await replay.click(); await page.waitForTimeout(2000); const toast = page.locator('.ops-toast, [class*="toast"]'); const toastText = (await toast.allTextContents().catch(() => [])).join(' | '); const body = await page.locator('body').innerText(); if (/正在重放|重放成功|重放完成|重放失败|重放连接/i.test(toastText + body)) { result.pass.push('历史重放出现 toast/文案: ' + (toastText || 'body匹配').slice(0, 120)); } else { result.fail.push('点了重放但未见 toast 文案'); } } else { // hover history rows const rows = page.locator('text=/GET|POST|WS|重放/'); result.notes.push('历史面板打开;重放按钮可能需悬停'); const hist = page.locator('.ops-tree-row, [class*="history"]'); if (await hist.count()) { await hist.first().hover(); await page.waitForTimeout(300); if (await page.getByRole('button', { name: /重放/ }).first().isVisible().catch(() => false)) { await page.getByRole('button', { name: /重放/ }).first().click(); await page.waitForTimeout(2000); const body = await page.locator('body').innerText(); if (/正在重放|重放成功|重放完成|重放失败|重放连接/i.test(body)) { result.pass.push('悬停后重放 toast 文案可见'); } else result.fail.push('悬停重放后未见 toast'); } else result.fail.push('悬停后仍无重放按钮'); } else result.fail.push('历史为空或结构不符'); } await shot(page, 'web-10-replay.png'); } catch (e) { result.fail.push('历史重放: ' + e.message); } await browser.close(); const summary = { verdict: result.fail.length ? '有缺陷' : '通过', ...result, }; fs.writeFileSync(path.join(outDir, 'web-reg-result.json'), JSON.stringify(summary, null, 2)); console.log(JSON.stringify(summary, null, 2)); } main().catch((e) => { console.error(e); process.exit(1); });