const { chromium } = require('playwright'); const fs = require('fs'); async function fetchBug(bugId) { const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const context = await browser.newContext({ viewport: { width: 1920, height: 2000 } }); const page = await context.newPage(); try { const zentaoUrl = 'https://zentao.gentronhealth.com'; // Step 1: 访问登录页面 console.log('[Step 1] 访问登录页面...'); await page.goto(zentaoUrl + '/user-login.html', { waitUntil: 'networkidle', timeout: 60000 }); // Step 2: 登录 console.log('[Step 2] 登录...'); await page.waitForSelector('#account', { timeout: 10000 }); await page.fill('#account', 'admin'); await page.fill('input[name="password"]', 'Jchl1528'); await Promise.all([ page.click('#submit'), page.waitForNavigation({ waitUntil: 'networkidle', timeout: 60000 }) ]); console.log('[Step 3] 登录成功'); await page.waitForTimeout(2000); // Step 4: 访问BUG详情页面 console.log(`[Step 4] 访问 BUG #${bugId} ...`); const bugUrl = zentaoUrl + '/index.php?m=bug&f=view&bugID=' + bugId; await page.goto(bugUrl, { waitUntil: 'networkidle', timeout: 60000 }); await page.waitForTimeout(3000); // Step 5: 关键!使用frameLocator获取iframe内容 console.log('[Step 5] 获取iframe内容...'); const frame = page.frameLocator('iframe[name="app-qa"]'); const bodyElement = frame.locator('body'); // 等待iframe内容加载 await bodyElement.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}); // Step 6: 提取BUG信息 const bugData = await bodyElement.evaluate(() => { const info = { title: '', description: '', steps: '', expected: '', actual: '', priority: '', status: '', module: '', assignedTo: '', openedBy: '', bugType: '', severity: '', allText: document.body.innerText }; // 获取标题 const titleEl = document.querySelector('.page-title, h1, h2, .title'); if (titleEl) { info.title = titleEl.textContent.replace(/^\d+\s*/, '').trim(); } // 获取表格数据 const rows = document.querySelectorAll('table.table-form tr'); rows.forEach(row => { const th = row.querySelector('th'); const td = row.querySelector('td'); if (th && td) { const label = th.textContent.trim(); const value = td.textContent.trim(); if (label.includes('所属模块')) info.module = value; else if (label.includes('优先级')) info.priority = value; else if (label.includes('Bug状态') || label.includes('状态')) info.status = value; else if (label.includes('指派给')) info.assignedTo = value; else if (label.includes('创建')) info.openedBy = value; else if (label.includes('Bug类型')) info.bugType = value; else if (label.includes('严重程度')) info.severity = value; } }); return info; }); // 从完整文本提取描述和步骤 const fullText = bugData.allText; const descMatch = fullText.match(/\[步骤\]([\s\S]*?)\[结果\]/); if (descMatch) bugData.steps = descMatch[1].trim(); const actualMatch = fullText.match(/\[结果\]([\s\S]*?)\[期望\]/); if (actualMatch) bugData.actual = actualMatch[1].trim(); const expectedMatch = fullText.match(/\[期望\]([\s\S]*?)(?=历史记录|$)/); if (expectedMatch) bugData.expected = expectedMatch[1].trim(); // 输出结果 console.log('\n==== BUG #' + bugId + ' 详细信息 ===='); console.log(JSON.stringify(bugData, null, 2)); // 保存到文件 fs.writeFileSync(`zentao_bug_${bugId}_parsed.json`, JSON.stringify(bugData, null, 2)); // 保存截图 await page.screenshot({ path: `zentao_bug_${bugId}.png`, fullPage: true }); return { success: true, data: bugData }; } catch (error) { console.error('[Error]', error.message); await page.screenshot({ path: `zentao_bug_${bugId}_error.png`, fullPage: true }); return { success: false, error: error.message }; } finally { await browser.close(); } } // 主函数 async function main() { const bugId = 320; console.log(`开始获取禅道 BUG #${bugId}...`); const result = await fetchBug(bugId); if (!result.success) { console.log('\n❌ 获取失败!'); process.exit(1); } console.log('\n✅ 获取成功!'); } main();