Files
photo-live/apps/viewer/e2e/album.spec.ts
T
2026-09-18 09:52:22 +08:00

132 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '@playwright/test';
import {
cleanupSeededAlbums,
makePublicToken,
seedAlbum,
seedBannerAsset,
seedCategory,
} from './helpers';
// Task 6 活动页 Playwright(真实后端:API 8080 由 playwright.config webServer 拉起
// 或复用已在跑的实例;种子走 Prisma/MinIO 直种,见 helpers.ts)。
// 语言前置:无头 Chromium 默认 en-US——用 addInitScript 预置 localStorage,避免
// 依赖浏览器语言;zh 断言由此确定性地落在 zh-CN UI。
test.afterAll(async () => {
await cleanupSeededAlbums();
});
test('renders album info in zh with timezone-adjusted time, chips and empty state', async ({ page }) => {
const album = await seedAlbum({
alias: 'main',
translations: [
{
locale: 'zh-CN',
title: '2026 公司年会',
description: '年度盛会',
venue: '大宴会厅',
address: '上海市浦东新区世纪大道 100 号',
},
{ locale: 'en', title: 'Annual Party 2026', description: 'Yearly gala' },
],
});
await seedCategory(album.id, 0, [
{ locale: 'zh-CN', name: '签到墙' },
{ locale: 'en', name: 'Check-in Wall' },
]);
await seedCategory(album.id, 1, [{ locale: 'zh-CN', name: '开场' }]);
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'zh-CN'));
await page.goto(`/a/${album.publicToken}`);
await expect(page.locator('h1.title')).toHaveText('2026 公司年会');
await expect(page.locator('.description')).toHaveText('年度盛会');
// Asia/Shanghai:UTC 10-01 02:00/12:00 → 本地 10-01(不含 9/30 越日错误)。
const time = page.locator('.meta');
await expect(time).toContainText('10月1日');
await expect(time).not.toContainText('9月30');
await expect(page.locator('.meta')).toContainText('大宴会厅 · 上海市浦东新区世纪大道 100 号');
const chips = page.locator('.chip');
await expect(chips).toHaveCount(2);
await expect(chips.first()).toBeDisabled();
await expect(chips).toHaveText(['签到墙', '开场']);
await expect(page.locator('.empty-notice')).toHaveText('照片即将上线');
// 该相册无 banner 资产 → 不渲染图。
await expect(page.locator('img.banner')).toHaveCount(0);
await expect(page).toHaveTitle('2026 公司年会');
});
test('switching locale re-fetches and shows the English translation', async ({ page }) => {
const album = await seedAlbum({
alias: 'switch',
translations: [
{ locale: 'zh-CN', title: '双语测试相册', description: '中文介绍' },
{ locale: 'en', title: 'Bilingual Test Album', description: 'English intro', venue: 'Main Hall' },
],
});
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'zh-CN'));
await page.goto(`/a/${album.publicToken}`);
await expect(page.locator('h1.title')).toHaveText('双语测试相册');
await page.locator('button.locale-switcher').click();
await expect(page.locator('h1.title')).toHaveText('Bilingual Test Album');
await expect(page.locator('.description')).toHaveText('English intro');
await expect(page.locator('.meta')).toContainText('Main Hall');
await expect(page.locator('.empty-notice')).toHaveText('Photos coming soon');
await expect(page).toHaveTitle('Bilingual Test Album');
});
test('en UI with a zh-only album falls back to zh content while chrome stays en', async ({ page }) => {
const album = await seedAlbum({
alias: 'fallback',
translations: [{ locale: 'zh-CN', title: '只有中文的相册', venue: '大宴会厅' }],
});
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'en'));
await page.goto(`/a/${album.publicToken}`);
// 内容回退:resolvedLocale=zh-CN(服务端返回 zh 翻译)。
await expect(page.locator('h1.title')).toHaveText('只有中文的相册');
await expect(page.locator('.meta')).toContainText('大宴会厅');
// UI 语言仍为用户选择(en)。
await expect(page.locator('.empty-notice')).toHaveText('Photos coming soon');
});
test('banner signed URL loads a real image', async ({ page }) => {
const album = await seedAlbum({
alias: 'banner',
translations: [{ locale: 'zh-CN', title: '有封面的相册' }],
});
await seedBannerAsset(album.id);
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'zh-CN'));
await page.goto(`/a/${album.publicToken}`);
const banner = page.locator('img.banner');
await expect(banner).toBeVisible();
// 签名 URL 可取回真实字节(加载完成 naturalWidth > 0;失败则保持 0)。
await expect
.poll(async () => banner.evaluate((img) => (img as HTMLImageElement).naturalWidth))
.toBeGreaterThan(0);
});
test('offline and ghost tokens show the localized unavailable error', async ({ page }) => {
const offline = await seedAlbum({
alias: 'offline',
status: 'offline',
translations: [{ locale: 'zh-CN', title: '已下线相册' }],
});
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'zh-CN'));
await page.goto(`/a/${offline.publicToken}`);
await expect(page.locator('.error[role="alert"]')).toContainText('相册不存在或已下线');
await expect(page.locator('h1.title')).toHaveCount(0);
// 幽灵 token:同码同文案(防枚举)。
await page.goto(`/a/${makePublicToken()}`);
await expect(page.locator('.error[role="alert"]')).toContainText('相册不存在或已下线');
});