const { chromium } = require('playwright'); const baseUrl = process.env.YOUTHOME_WEB_URL || 'http://127.0.0.1:4173/pages'; async function inspectOverflow(page) { return page.evaluate(() => ({ innerWidth: window.innerWidth, scrollWidth: document.documentElement.scrollWidth, bodyScrollWidth: document.body.scrollWidth, overflow: Math.max(document.documentElement.scrollWidth, document.body.scrollWidth) - window.innerWidth })); } (async () => { const browser = await chromium.launch({ channel: 'msedge', headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); await page.addInitScript(() => localStorage.setItem('youthome:web:dataSource', 'local')); const browserErrors = []; page.on('console', (message) => { if (message.type() === 'error' && !message.text().includes('404 (File not found)')) { browserErrors.push(`console [${page.url()}]: ${message.text()}`); } }); page.on('pageerror', (error) => browserErrors.push(`page [${page.url()}]: ${error.message}`)); page.on('response', (response) => { if (response.status() >= 400) browserErrors.push(`http ${response.status()}: ${response.url()}`); }); await page.goto(`${baseUrl}/settings.html`, { waitUntil: 'domcontentloaded' }); await page.locator('[data-settings-section="privacy"]').click(); const settings = { url: page.url(), title: await page.locator('.settings-page-heading .page-title').textContent(), visiblePanels: await page.locator('[data-settings-panel]:visible').count(), visiblePanel: await page.locator('[data-settings-panel]:visible').getAttribute('data-settings-panel') }; console.log('settings', JSON.stringify(settings)); await page.goto(`${baseUrl}/feed.html`, { waitUntil: 'domcontentloaded' }); const extrasBefore = await page.locator('.topic-item-extra:visible').count(); await page.locator('.topic-more-button').click(); const extrasAfter = await page.locator('.topic-item-extra:visible').count(); const moreText = (await page.locator('.topic-more-button').innerText()).trim(); const recommendationsBefore = await page.locator('.user-item-name').allTextContents(); await page.locator('.recommendation-refresh').click(); const recommendationsAfter = await page.locator('.user-item-name').allTextContents(); const firstName = recommendationsAfter[0]; await page.locator('.user-item .follow-btn').first().click(); const followText = (await page.locator('.user-item .follow-btn').first().innerText()).trim(); const storedFollow = await page.evaluate( (name) => localStorage.getItem(`youthome:web:follow:${name}`), firstName ); const feed = { extrasBefore, extrasAfter, moreText, recommendationsChanged: recommendationsBefore.join('|') !== recommendationsAfter.join('|'), firstName, followText, storedFollow }; console.log('feed', JSON.stringify(feed)); await page.goto(`${baseUrl}/me.html`, { waitUntil: 'domcontentloaded' }); await page.locator('[data-profile-action="more"]').click(); const profile = { expanded: await page.locator('[data-profile-action="more"]').getAttribute('aria-expanded'), visibleMenu: await page.locator('.profile-more-menu:visible').count(), visibleActions: await page.locator('.profile-more-menu button:visible').count() }; console.log('profile', JSON.stringify(profile)); const mobile = await browser.newPage({ viewport: { width: 390, height: 844 } }); await mobile.addInitScript(() => localStorage.setItem('youthome:web:dataSource', 'local')); const mobileLayout = {}; for (const name of ['settings', 'me', 'feed']) { await mobile.goto(`${baseUrl}/${name}.html`, { waitUntil: 'domcontentloaded' }); await mobile.locator('body.web-shell-ready').waitFor(); mobileLayout[name] = await inspectOverflow(mobile); if (name === 'me') { mobileLayout[name].moreButton = await mobile.locator('[data-profile-action="more"]').evaluate((element) => { const rect = element.getBoundingClientRect(); const centerElement = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2); return { left: Math.round(rect.left), top: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height), clickable: element === centerElement || element.contains(centerElement) }; }); } await mobile.screenshot({ path: `C:/Users/lenovo/AppData/Local/Temp/youthome-qa-20260923/${name}-mobile-final.png`, fullPage: true }); } console.log('mobile', JSON.stringify(mobileLayout)); console.log('browserErrors', JSON.stringify(browserErrors)); const failed = settings.visiblePanel !== 'privacy' || settings.visiblePanels !== 1 || !settings.url.includes('section=privacy') || feed.extrasBefore !== 0 || feed.extrasAfter !== 4 || !feed.recommendationsChanged || feed.followText !== '已关注' || feed.storedFollow !== 'true' || profile.expanded !== 'true' || profile.visibleMenu !== 1 || profile.visibleActions !== 2 || !mobileLayout.me.moreButton.clickable || Object.values(mobileLayout).some((result) => result.overflow > 0) || browserErrors.length > 0; await browser.close(); process.exitCode = failed ? 1 : 0; })().catch((error) => { console.error(error); process.exitCode = 1; });