const assert = require('node:assert/strict'); const { chromium } = require('playwright'); const base = 'http://127.0.0.1:4173/pages'; const PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg=='; (async () => { const browser = await chromium.launch({ channel: 'msedge', headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, locale: 'zh-CN' }); const errors = []; const apiCalls = []; page.on('pageerror', (error) => errors.push(error.message)); page.on('request', (request) => { if (request.url().includes('/api/')) apiCalls.push(request.url()); }); // 未登录状态下的示例作品数量(后面用来确认没有被覆盖) await page.goto(`${base}/me.html`); await page.waitForTimeout(600); const sampleWorks = await page.locator('.content-grid .content-card').count(); assert.ok(sampleWorks > 0, 'expected sample works on me.html'); // 1. 登录 await page.goto(`${base}/login.html?next=feed.html`); await page.evaluate(() => window.YouthomeLocal.reset()); await page.reload(); await page.locator('#panel-password input[type="tel"]').fill('13800000000'); await page.locator('#panel-password input[type="password"]').fill('qinghe123'); await page.locator('#panel-password .btn-primary').click(); await page.waitForURL('**/feed.html'); // 2. 广场读取本地内容 await page.locator('.web-live-card').first().waitFor(); const feedCards = await page.locator('.web-live-card').count(); assert.ok(feedCards >= 6, `expected local feed cards, got ${feedCards}`); // 3. 发布页发一条纯文字动态 → 详情 await page.goto(`${base}/publish-post.html`); await page.locator('.post-textarea').fill('本地数据层冒烟测试:这是网页端发布的第一条动态。'); await page.locator('.web-publish-actions .btn-primary').click(); await page.waitForURL('**/post-detail.html?moment=*'); await page.locator('html:not(.notification-route-pending)').waitFor(); assert.ok((await page.locator('.post-detail-content').innerText()).includes('本地数据层冒烟测试')); // 4. 详情互动:点赞、收藏、评论 await page.locator('.post-detail-actions [aria-label="点赞"]').click(); await page.waitForFunction(() => document.querySelector('.post-detail-actions [aria-label="点赞"] .count')?.textContent.trim() === '1', null, { timeout: 8000 }); await page.locator('.post-detail-actions [aria-label="收藏"]').click(); await page.waitForFunction(() => document.querySelector('.post-detail-actions [aria-label="收藏"]')?.classList.contains('saved'), null, { timeout: 8000 }); await page.locator('.comment-input').fill('自己给自己评论一条,验证评论写入。'); await page.locator('.btn-send').click(); await page.locator('.comment-item').first().waitFor(); assert.equal((await page.locator('.comment-count').innerText()).trim(), '共 1 条'); // 5. 发布页 → 草稿 → 草稿箱 → 发布 await page.goto(`${base}/publish-post.html`); await page.locator('.post-textarea').fill('草稿流程验证内容'); await page.locator('.add-topic-btn').click(); await page.locator('.web-topic-input').fill('校园日常'); await page.locator('.web-topic-input').press('Enter'); await page.locator('input[type="file"]').setInputFiles({ name: 'photo.png', mimeType: 'image/png', buffer: Buffer.from(PNG, 'base64') }); await page.waitForFunction(() => document.querySelectorAll('.preview-images img').length === 1, null, { timeout: 8000 }); await page.locator('.web-publish-actions .btn-secondary').click(); await page.locator('.web-toast').getByText('草稿已保存').waitFor(); assert.ok(page.url().includes('draft='), 'draft id should be in the url'); await page.goto(`${base}/drafts.html`); await page.locator('.draft-card').first().waitFor(); await page.locator('.draft-action-edit').first().click(); await page.waitForURL('**/publish-post.html?draft=*'); await page.waitForFunction(() => document.querySelector('.post-textarea')?.value === '草稿流程验证内容', null, { timeout: 8000 }); await page.locator('.web-publish-actions .btn-primary').click(); await page.waitForURL('**/post-detail.html?moment=*'); await page.locator('html:not(.notification-route-pending)').waitFor(); assert.ok((await page.locator('.post-detail-content').innerText()).includes('草稿流程验证内容')); assert.equal(await page.locator('.post-detail-images img').count(), 1, 'uploaded image should show in detail'); // 6. 草稿发布后应被清理 await page.goto(`${base}/drafts.html`); await page.locator('.web-live-empty').waitFor(); assert.equal((await page.locator('.draft-card').count()), 0); // 7. 个人中心:示例作品保留,本地动态插在前面 await page.goto(`${base}/me.html`); await page.locator('.content-grid .web-live-card').first().waitFor(); const liveCards = await page.locator('.content-grid .web-live-card').count(); assert.equal(liveCards, 2, `expected 2 local posts, got ${liveCards}`); assert.equal(await page.locator('.content-grid .content-card').count(), sampleWorks + liveCards, 'sample works must be preserved'); // 8. 从个人中心进入本地动态详情 await page.locator('.content-grid .web-live-card').first().click(); await page.waitForURL('**/post-detail.html?moment=*'); await page.locator('html:not(.notification-route-pending)').waitFor(); // 9. 刷新后数据仍在(localStorage 持久化) await page.reload(); await page.locator('html:not(.notification-route-pending)').waitFor(); assert.ok((await page.locator('.post-detail-content').innerText()).length > 0); // 10. 移动端不出现横向溢出 await page.setViewportSize({ width: 390, height: 844 }); for (const name of ['feed', 'me', 'publish-post', 'drafts']) { await page.goto(`${base}/${name}.html`); await page.waitForTimeout(400); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - innerWidth); assert.ok(overflow <= 0, `${name} overflows by ${overflow}px at 390px`); } assert.deepEqual(apiCalls, [], 'local mode must not call the backend'); assert.deepEqual(errors, []); await browser.close(); console.log(`local publishing flow passed in Edge (sample works ${sampleWorks}, local posts ${liveCards})`); })().catch((error) => { console.error(error); process.exitCode = 1; });