const assert = require('node:assert/strict'); const { chromium } = require('playwright'); const baseUrl = process.env.YOUTHOME_WEB_URL || 'http://127.0.0.1:4173/pages'; (async () => { const browser = await chromium.launch({ channel: 'msedge', headless: true }); const context = await browser.newContext({ viewport: { width: 1440, height: 900 } }); const page = await context.newPage(); await context.addInitScript(() => localStorage.setItem('youthome:web:dataSource', 'local')); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); page.on('response', (response) => { if (response.status() >= 400) errors.push(`${response.status()} ${response.url()}`); }); await page.goto(`${baseUrl}/feed.html`); await page.locator('.web-nav-link[data-href^="notification.html"]').click(); await page.waitForURL('**/notification.html?v=*'); assert.ok((await page.locator('.message-list').innerText()).includes('校园十佳歌手大赛')); assert.equal((await page.locator('.message-list').innerText()).includes('校园音乐节'), false); const plainResponse = await page.goto(`${baseUrl}/notification.html`); assert.ok(plainResponse.headers()['cache-control'].includes('no-store')); assert.equal(await page.locator('.message-item[href]').count(), 6); const cases = [ { id: 'like-canteen', path: 'post-detail.html?post=canteen', text: '今天食堂的新菜也太好吃了吧!' }, { id: 'comment-canteen', path: 'post-detail.html?post=canteen', text: '好棒!我也想去~', target: 'li-mengqi' }, { id: 'mention-milk-tea', path: 'post-detail.html?post=milk-tea', text: '学校西门的奶茶店', target: 'wang-haoran' }, { id: 'follow-chen-shiyu', path: 'user.html?name=', text: '华东师范大学' }, { id: 'activity-singer', path: 'activity-detail.html', text: '校园十佳歌手大赛' }, { id: 'privacy-settings', path: 'settings.html?section=privacy', text: '谁可以查看我的动态' } ]; for (const item of cases) { await page.goto(`${baseUrl}/notification.html`); await page.locator(`[data-notification-id="${item.id}"]`).click(); await page.waitForURL(`**/${item.path.split('?')[0]}*`); await page.locator('body.web-shell-ready').waitFor(); assert.ok(page.url().includes(item.path), `${item.id}: unexpected URL ${page.url()}`); assert.ok((await page.locator('body').innerText()).includes(item.text), `${item.id}: expected content not visible`); if (item.path.startsWith('post-detail.html')) { assert.equal(await page.locator('html.notification-route-pending').count(), 0); assert.equal((await page.locator('.post-detail-content').innerText()).includes('今天图书馆自习'), false); } if (['like-canteen', 'comment-canteen', 'mention-milk-tea', 'follow-chen-shiyu'].includes(item.id)) { assert.ok((await page.locator('.web-nav-link.is-active').innerText()).includes('消息通知')); } if (item.target) { assert.ok(await page.locator(`#comment-${item.target}`).evaluate((element) => element.classList.contains('is-targeted'))); } assert.equal(await page.evaluate((id) => localStorage.getItem(`youthome:web:notification-read:${id}`), item.id), 'true'); } await page.goto(`${baseUrl}/notification.html`); assert.equal(await page.locator('.category-item:first-child .category-badge').innerText(), '0'); assert.equal(await page.locator('.web-nav-badge:visible').count(), 0); await page.goto(`${baseUrl}/activity-detail.html`); assert.equal(await page.locator('.web-nav-badge:visible').count(), 0); await page.goto(`${baseUrl}/settings.html?section=privacy`); assert.equal(await page.locator('.web-nav-badge:visible').count(), 0); await page.goto(`${baseUrl}/me.html`); assert.equal(await page.locator('.notification-work-card').count(), 2); await page.locator('.notification-work-card').first().click(); assert.ok(page.url().includes('post=canteen')); await page.goto(`${baseUrl}/user.html?name=%E9%99%88%E8%AF%97%E9%9B%A8`); assert.equal(await page.locator('.profile-name').innerText(), '陈诗雨'); assert.equal(await page.locator('.post-mini-card:visible').count(), 0); assert.equal(await page.locator('.web-empty-state:visible').count(), 1); const withoutScript = await context.newPage(); await withoutScript.route('**/js/web-shell.js*', (route) => route.abort()); await withoutScript.goto(`${baseUrl}/post-detail.html?post=canteen`); assert.equal(await withoutScript.locator('.detail-layout').isVisible(), false); assert.equal(await withoutScript.locator('html.notification-route-pending').count(), 1); await withoutScript.close(); const nativeLink = await context.newPage(); const blockScript = (route) => route.abort(); await nativeLink.route('**/js/web-shell.js*', blockScript); await nativeLink.goto(`${baseUrl}/notification.html`); await nativeLink.unroute('**/js/web-shell.js*', blockScript); await nativeLink.locator('[data-notification-id="like-canteen"]').click(); await nativeLink.waitForURL('**/post-detail.html?post=canteen*'); assert.ok((await nativeLink.locator('.post-detail-content').innerText()).includes('今天食堂的新菜')); await nativeLink.close(); await page.setViewportSize({ width: 390, height: 844 }); for (const path of ['notification.html', 'post-detail.html?post=canteen', 'user.html?name=%E9%99%88%E8%AF%97%E9%9B%A8']) { await page.goto(`${baseUrl}/${path}`); const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth); assert.ok(overflow <= 1, `${path}: horizontal overflow ${overflow}px`); } assert.deepEqual(errors, []); console.log('6 notification routes, read state, cross-page badge, and 390px layout passed'); await browser.close(); })().catch((error) => { console.error(error); process.exitCode = 1; });