docs: UI-02 acceptance record and visual verification
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -50,12 +50,13 @@ test('renders album info in zh with timezone-adjusted time, chips and empty stat
|
||||
await expect(time).not.toContainText('9月30');
|
||||
await expect(page.locator('.meta')).toContainText('大宴会厅 · 上海市浦东新区世纪大道 100 号');
|
||||
|
||||
// Task 10 起排序 chips(最新/热门)与分类 chips 并存——断言收窄到分类栏。
|
||||
// Task 10 起排序(最新/热门)与分类 chips 并存——UI-02 V02 起排序为标签页
|
||||
// (.sort-tabs .sort-tab),断言收窄到分类栏。
|
||||
const chips = page.locator('.categories .chip');
|
||||
await expect(chips).toHaveCount(3); // Task 7 起:全部 + 各分类(可点选)
|
||||
await expect(chips.first()).toBeEnabled();
|
||||
await expect(chips).toHaveText(['全部', '签到墙', '开场']);
|
||||
await expect(page.locator('.sort .chip')).toHaveText(['最新', '热门']);
|
||||
await expect(page.locator('.sort-tabs .sort-tab')).toHaveText(['最新', '热门']);
|
||||
|
||||
await expect(page.locator('.empty-notice')).toHaveText('照片即将上线');
|
||||
// 该相册无 banner 资产 → 不渲染图。
|
||||
@@ -77,7 +78,9 @@ test('switching locale re-fetches and shows the English translation', async ({ p
|
||||
await page.goto(`/a/${album.publicToken}`);
|
||||
await expect(page.locator('h1.title')).toHaveText('双语测试相册');
|
||||
|
||||
await page.locator('button.locale-switcher').click();
|
||||
// UI-02 V01 紧凑语言控件:展开菜单后选择 English(两步)。
|
||||
await page.locator('.locale-trigger').click();
|
||||
await page.locator('.locale-option', { hasText: 'English' }).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');
|
||||
|
||||
@@ -39,7 +39,7 @@ async function openLightbox(page: Page, token: string, init?: () => void): Promi
|
||||
await page.goto(`/a/${token}`);
|
||||
await expect(page.locator('.cell').first()).toBeVisible();
|
||||
await page.locator('.cell').first().click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
}
|
||||
|
||||
test.afterAll(async () => {
|
||||
@@ -56,18 +56,18 @@ test('download entry visibility follows album two-tier switches', async ({ page
|
||||
await openLightbox(page, offOff);
|
||||
await expect(page.locator('.download-group')).toHaveCount(0);
|
||||
|
||||
// on/off:单按钮(无 caret/菜单)。
|
||||
// on/off:单按钮(无 caret/菜单)。UI-02 V05:下载位在底部操作栏(.download-main)。
|
||||
const onOff = await seedDownloadAlbum('dl-onoff', {});
|
||||
await openLightbox(page, onOff);
|
||||
await expect(page.locator('.control.download')).toBeVisible();
|
||||
await expect(page.locator('.control.download-caret')).toHaveCount(0);
|
||||
await expect(page.locator('.download-main')).toBeVisible();
|
||||
await expect(page.locator('.download-caret')).toHaveCount(0);
|
||||
|
||||
// on/on:分体按钮,菜单两选项(下载图片 / 下载原图)。
|
||||
// on/on:分体按钮,菜单两选项(浏览图 / 原图)。
|
||||
const onOn = await seedDownloadAlbum('dl-onon', { originalDownloadEnabled: true });
|
||||
await openLightbox(page, onOn);
|
||||
await expect(page.locator('.control.download-caret')).toBeVisible();
|
||||
await page.locator('.control.download-caret').click();
|
||||
await expect(page.locator('.download-menu button')).toHaveText(['下载图片', '下载原图']);
|
||||
await expect(page.locator('.download-caret')).toBeVisible();
|
||||
await page.locator('.download-caret').click();
|
||||
await expect(page.locator('.download-menu button')).toHaveText(['浏览图', '原图']);
|
||||
});
|
||||
|
||||
test('WeChat webview UA shows guidance overlay instead of downloading', async ({ page }) => {
|
||||
@@ -85,7 +85,7 @@ test('WeChat webview UA shows guidance overlay instead of downloading', async ({
|
||||
if (request.url().includes('/download?')) endpointHit = true;
|
||||
});
|
||||
|
||||
await page.locator('.control.download').click();
|
||||
await page.locator('.download-main').click();
|
||||
await expect(page.locator('.wechat-guide[role="dialog"]')).toBeVisible();
|
||||
await expect(page.locator('.wechat-guide h2')).toHaveText('保存图片指引');
|
||||
await expect(page.locator('.guide-copy')).toBeVisible();
|
||||
@@ -112,7 +112,7 @@ test('real click downloads browse via signed url from the endpoint', async ({ pa
|
||||
(response) =>
|
||||
response.url().includes('/download?variant=browse') && response.status() === 200,
|
||||
);
|
||||
await page.locator('.control.download').click();
|
||||
await page.locator('.download-main').click();
|
||||
const response = await endpoint;
|
||||
expect((await response.json()).variant).toBe('browse');
|
||||
|
||||
|
||||
@@ -384,9 +384,11 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
await expect(page.locator('h1.title')).toHaveText(zhTitle);
|
||||
await expect(page.locator('.categories .chip')).toHaveCount(2); // 全部 + 主舞台
|
||||
|
||||
await page.locator('button.locale-switcher').click();
|
||||
await page.locator('.locale-trigger').click();
|
||||
await page.locator('.locale-option', { hasText: 'English' }).click();
|
||||
await expect(page.locator('h1.title')).toHaveText(enTitle);
|
||||
await page.locator('button.locale-switcher').click(); // 切回 zh,后续断言用中文
|
||||
await page.locator('.locale-trigger').click(); // 切回 zh,后续断言用中文
|
||||
await page.locator('.locale-option', { hasText: '中文' }).click();
|
||||
await expect(page.locator('h1.title')).toHaveText(zhTitle);
|
||||
|
||||
// 瀑布流 3 张 → 分类 chip 过滤 1 张 → 全部恢复 3 张。
|
||||
@@ -397,18 +399,18 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
await expect(page.locator('.cell')).toHaveCount(3);
|
||||
|
||||
// Task 10 互动化:浏览计数行(首访上报后权威回填)+ 排序 chips 在位。
|
||||
await expect(page.locator('.view-count')).toHaveText('1', { timeout: 15_000 });
|
||||
await expect(page.locator('.sort .chip')).toHaveText(['最新', '热门']);
|
||||
await expect(page.locator('.view-count')).toHaveText('1 次浏览', { timeout: 15_000 });
|
||||
await expect(page.locator('.sort-tabs .sort-tab')).toHaveText(['最新', '热门']);
|
||||
|
||||
// 大图:点击开图 → 真实字节 → 滚轮缩放 → 双击复位 → 关闭回列表。
|
||||
const lightboxPhoto = photoIds[1]!; // 800×500(后续 admin 腿按尺寸锚定隐藏)
|
||||
await page.locator('.cell').nth(1).click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect(
|
||||
page.locator('img.photo'),
|
||||
).toHaveAttribute('src', new RegExp(lightboxPhoto));
|
||||
// Task 10:大图内真实点赞 1 次 → 防抖冲批 → 权威计数 1。
|
||||
await page.locator('.control.like-button').click();
|
||||
await page.locator('.action-bar .like-slot .action-btn').click();
|
||||
await expect(page.locator('.like-count')).toHaveText('1');
|
||||
await expect
|
||||
.poll(async () =>
|
||||
@@ -416,7 +418,7 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
expect(page.url()).toContain(`/a/${publicToken}/p/${lightboxPhoto}`);
|
||||
const box = await page.locator('.lightbox').boundingBox();
|
||||
const box = await page.locator('.photo-viewer').boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2);
|
||||
await page.mouse.wheel(0, -360);
|
||||
@@ -435,12 +437,12 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
.evaluate((img) => new DOMMatrixReadOnly(getComputedStyle(img).transform).a),
|
||||
)
|
||||
.toBeCloseTo(1, 1);
|
||||
await page.locator('.control.close').click();
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await page.locator('.viewer-close').click();
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
|
||||
// 深链直达:/a/{token}/p/{photoId} 直接开在大图上。
|
||||
await page.goto(`/a/${publicToken}/p/${lightboxPhoto}`);
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect(page.locator('img.photo')).toHaveAttribute(
|
||||
'src',
|
||||
new RegExp(lightboxPhoto),
|
||||
@@ -473,7 +475,7 @@ test.describe.serial('cross-frontend journey (admin → api/worker → viewer)',
|
||||
const notice = page.locator('.deep-link-notice');
|
||||
await expect(notice).toBeVisible();
|
||||
await expect(notice).toContainText('照片不存在或已下线');
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
|
||||
// ── ⑤ 空相册 B:viewer 空态提示(B 经 API 建于 cn,先同步到 ov)──
|
||||
await flushSync(emptyAlbumId);
|
||||
|
||||
@@ -74,9 +74,9 @@ test('lightbox likes accumulate to 3 and persist across reload', async ({ page }
|
||||
await openAlbum(page, token);
|
||||
|
||||
await page.locator('.cell').first().click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await page.locator('.control.like-button').click();
|
||||
await page.locator('.action-bar .like-slot .action-btn').click();
|
||||
}
|
||||
// 防抖 500ms 冲批 → 服务端权威校准 3(响应/WS burst 先后到达均为 3)。
|
||||
await expect(page.locator('.like-count')).toHaveText('3');
|
||||
@@ -92,10 +92,10 @@ test('lightbox likes accumulate to 3 and persist across reload', async ({ page }
|
||||
// 刷新后从服务端列表回归:深链自动重开大图(URL 仍停在该照片),计数 3;
|
||||
// 关闭后格子计数亦 3(sessionId 会话内持久,幂等面不变)。
|
||||
await page.reload();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect(page.locator('.like-count')).toHaveText('3');
|
||||
await page.locator('.control.close').click();
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await page.locator('.viewer-close').click();
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
await expect(page.locator('.cell-like .count').first()).toHaveText('3');
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ test('others burst bubbles on my page; my own like does not', async ({ browser,
|
||||
await expect(page.locator('.like-bubble .bubble-delta')).toHaveText('+1');
|
||||
await expect(page.locator('.cell-like .count').first()).toHaveText('2');
|
||||
await page.locator('.like-bubble').click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
} finally {
|
||||
await ctx2.close();
|
||||
}
|
||||
@@ -173,7 +173,7 @@ test('hidden photo disappears from the feed and closes the lightbox via WS push'
|
||||
|
||||
// 大图停在第 2 张:下线推送应回退邻居并收口(closeLightbox 路径)。
|
||||
await page.locator('.cell').nth(1).click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
|
||||
await deleteSeededPhoto(photoIds[1]!);
|
||||
await flushSync(albumId); // photo.deleted 走真实管线 → ov 撤下 + WS 房间广播
|
||||
@@ -181,7 +181,7 @@ test('hidden photo disappears from the feed and closes the lightbox via WS push'
|
||||
// 推送到达:列表无刷新即时 2 张 + 下线提示;大图回退到后邻(第 3 张)仍可看。
|
||||
await expect(page.locator('.cell')).toHaveCount(2, { timeout: 20_000 });
|
||||
await expect(page.locator('.removed-notice')).toBeVisible();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect(page.locator('img.photo')).toHaveAttribute('src', new RegExp(photoIds[2]!));
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ test('sort toggle switches latest/hot snapshots and is disabled in the lightbox'
|
||||
await apiLike(token, older, 'e2e-hot-session', 10);
|
||||
await page.getByRole('button', { name: '热门' }).click();
|
||||
await expect(page.locator('.cell-img').first()).toHaveAttribute('src', new RegExp(older));
|
||||
await expect(page.locator('.sort .chip').nth(1)).toHaveClass(/active/);
|
||||
await expect(page.locator('.sort-tabs .sort-tab').nth(1)).toHaveClass(/active/);
|
||||
|
||||
// 切回最新:原顺序恢复(冷快照语义,各自独立)。
|
||||
await page.getByRole('button', { name: '最新' }).click();
|
||||
@@ -206,9 +206,9 @@ test('sort toggle switches latest/hot snapshots and is disabled in the lightbox'
|
||||
|
||||
// 大图打开期间排序切换禁用(快照冻结,避免半开序列换序)。
|
||||
await page.locator('.cell').first().click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.sort .chip').first()).toBeDisabled();
|
||||
await expect(page.locator('.sort .chip').nth(1)).toBeDisabled();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect(page.locator('.sort-tabs .sort-tab').first()).toBeDisabled();
|
||||
await expect(page.locator('.sort-tabs .sort-tab').nth(1)).toBeDisabled();
|
||||
});
|
||||
|
||||
test('visit counting: fresh context counts once, reload is idempotent, second context counts', async ({
|
||||
@@ -222,19 +222,19 @@ test('visit counting: fresh context counts once, reload is idempotent, second co
|
||||
try {
|
||||
await openAlbum(page1, token);
|
||||
// 首访计数(上报成功后以权威 viewCount 回填)。
|
||||
await expect(page1.locator('.view-count')).toHaveText('1', { timeout: 15_000 });
|
||||
await expect(page1.locator('.view-count')).toHaveText('1 次浏览', { timeout: 15_000 });
|
||||
|
||||
// 刷新:visitorId 持久 + 窗口去重 → 不再计数。
|
||||
await page1.reload();
|
||||
await expect(page1.locator('.cell').first()).toBeVisible();
|
||||
await expect(page1.locator('.view-count')).toHaveText('1');
|
||||
await expect(page1.locator('.view-count')).toHaveText('1 次浏览');
|
||||
|
||||
// 第二个全新上下文:新 visitorId → 再计数。
|
||||
const ctx2 = await browser.newContext();
|
||||
try {
|
||||
const page2 = await ctx2.newPage();
|
||||
await openAlbum(page2, token);
|
||||
await expect(page2.locator('.view-count')).toHaveText('2', { timeout: 15_000 });
|
||||
await expect(page2.locator('.view-count')).toHaveText('2 次浏览', { timeout: 15_000 });
|
||||
} finally {
|
||||
await ctx2.close();
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ test('open from feed, navigate in sequence, zoom, close restores scroll and url'
|
||||
|
||||
// 点击第 3 格 → 大图:browse 图取回真实字节,URL 换为深链。
|
||||
await page.locator('.cell').nth(2).click();
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect
|
||||
.poll(() => browseNaturalWidth(page))
|
||||
.toBeGreaterThan(0);
|
||||
@@ -88,10 +88,10 @@ test('open from feed, navigate in sequence, zoom, close restores scroll and url'
|
||||
await expect(page.locator('img.photo')).toHaveAttribute('src', new RegExp(photoIds[step]!), { timeout: 10_000 });
|
||||
}
|
||||
expect(page.url()).toContain(`/a/${token}/p/${photoIds[5]}`);
|
||||
await expect(page.locator('.counter')).toHaveText('6 / 8');
|
||||
await expect(page.locator('.position-indicator')).toHaveText('6 / 8');
|
||||
|
||||
// 滚轮缩放(无需 ctrl):transform scale > 1;双击复位。
|
||||
const box = await page.locator('.lightbox').boundingBox();
|
||||
const box = await page.locator('.photo-viewer').boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2);
|
||||
await page.mouse.wheel(0, -360);
|
||||
@@ -109,8 +109,8 @@ test('open from feed, navigate in sequence, zoom, close restores scroll and url'
|
||||
.toBeCloseTo(1, 1);
|
||||
|
||||
// 关闭:URL 弹回相册路由,滚动精确回位。
|
||||
await page.locator('.control.close').click();
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await page.locator('.viewer-close').click();
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
await expect(page).toHaveURL(new RegExp(`/a/${token}/?$`));
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.scrollY))
|
||||
@@ -123,12 +123,12 @@ test('deep link opens on the target photo with the list loaded behind', async ({
|
||||
const target = photoIds[4]!; // 第 5 张
|
||||
await openLocale(page, token, `/p/${target}`);
|
||||
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
await expect
|
||||
.poll(() => browseNaturalWidth(page))
|
||||
.toBeGreaterThan(0);
|
||||
await expect(page.locator('img.photo')).toHaveAttribute('src', new RegExp(target));
|
||||
await expect(page.locator('.counter')).toHaveText('5 / 8');
|
||||
await expect(page.locator('.position-indicator')).toHaveText('5 / 8');
|
||||
// 背后列表已构成序列(8 张)。
|
||||
await expect(page.locator('.cell')).toHaveCount(8);
|
||||
|
||||
@@ -138,8 +138,8 @@ test('deep link opens on the target photo with the list loaded behind', async ({
|
||||
expect(page.url()).toContain(`/a/${token}/p/${photoIds[3]}`);
|
||||
|
||||
// 关闭 → 回相册路由(深链直达无本会话 push:replace 语义)。
|
||||
await page.locator('.control.close').click();
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await page.locator('.viewer-close').click();
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
await expect(page).toHaveURL(new RegExp(`/a/${token}/?$`));
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@ test('deep link to a removed photo shows the localized notice and a working back
|
||||
const notice = page.locator('.deep-link-notice');
|
||||
await expect(notice).toBeVisible();
|
||||
await expect(notice).toContainText('照片不存在或已下线');
|
||||
await expect(page.locator('.lightbox')).toHaveCount(0);
|
||||
await expect(page.locator('.photo-viewer')).toHaveCount(0);
|
||||
|
||||
const back = notice.getByRole('link', { name: '返回相册' });
|
||||
await back.click();
|
||||
@@ -172,13 +172,13 @@ test('navigating past the first page extends the sequence', async ({ page }) =>
|
||||
|
||||
await expect(page.locator('.cell')).toHaveCount(30);
|
||||
await page.locator('.cell').nth(29).click(); // 第 1 页最后一张
|
||||
await expect(page.locator('.lightbox')).toBeVisible();
|
||||
await expect(page.locator('.photo-viewer')).toBeVisible();
|
||||
|
||||
// 打开即临近末页边界:相邻预取已后台拉取第 2 页(设计行为),右切越过末尾
|
||||
// 落在第 31 张(序列第 2 页),列表同步扩展。
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(page.locator('img.photo')).toHaveAttribute('src', new RegExp(photoIds[30]!), { timeout: 15_000 });
|
||||
await expect(page.locator('.counter')).toHaveText('31 / 32');
|
||||
await expect(page.locator('.position-indicator')).toHaveText('31 / 32');
|
||||
// 背后列表同步扩展到 32 张。
|
||||
await expect(page.locator('.cell')).toHaveCount(32);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user