Files
photo-live/apps/viewer/e2e/download.spec.ts
T
2026-09-21 23:10:58 +08:00

131 lines
5.4 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, type Page } from '@playwright/test';
import {
cleanupSeededAlbums,
flushSync,
seedAlbum,
seedPhoto,
} from './helpers';
// P4a Task 4 下载 Playwright(真实后端 + 真实签名管线):
// · 入口可见性随相册两级开关(off/off 无按钮 / on/off 单按钮 / on/on 分体
// 菜单两选项);
// · 微信内置浏览器 UA mock(addInitScript 覆写 navigator.userAgent)→ 点击
// 下载出引导浮层(不触端点、不 window.open),「知道了」收起;
// · 真实下载点击:download 端点 200 + window.open 收到签名 URL(对象键模式
// 断言;实际文件保存不在浏览器 e2e 域——由 api e2e 字节级覆盖)。
/** 种「下载相册」:1 张已发布照片,开关组合由参数给定。 */
async function seedDownloadAlbum(
alias: string,
switches: { downloadEnabled?: boolean; originalDownloadEnabled?: boolean },
): Promise<string> {
const album = await seedAlbum({
alias,
translations: [{ locale: 'zh-CN', title: `下载测试相册 ${alias}` }],
...switches,
});
await seedPhoto(album.id, `${alias}-p0`, {
width: 480,
height: 300,
publishedAt: new Date(Date.now() - 60_000),
});
await flushSync(album.id);
return album.publicToken;
}
async function openLightbox(page: Page, token: string, init?: () => void): Promise<void> {
await page.addInitScript(() => localStorage.setItem('viewer.locale', 'zh-CN'));
if (init) await page.addInitScript(init);
await page.goto(`/a/${token}`);
await expect(page.locator('.cell').first()).toBeVisible();
await page.locator('.cell').first().click();
await expect(page.locator('.photo-viewer')).toBeVisible();
}
test.afterAll(async () => {
await cleanupSeededAlbums();
});
test('download entry visibility follows album two-tier switches', async ({ page }) => {
test.setTimeout(120_000);
// off/off:无下载入口。
const offOff = await seedDownloadAlbum('dl-offoff', {
downloadEnabled: false,
originalDownloadEnabled: false,
});
await openLightbox(page, offOff);
await expect(page.locator('.download-group')).toHaveCount(0);
// on/off:单按钮(无 caret/菜单)。UI-02 V05:下载位在底部操作栏(.download-main)。
const onOff = await seedDownloadAlbum('dl-onoff', {});
await openLightbox(page, onOff);
await expect(page.locator('.download-main')).toBeVisible();
await expect(page.locator('.download-caret')).toHaveCount(0);
// on/on:分体按钮,菜单两选项(浏览图 / 原图)。
const onOn = await seedDownloadAlbum('dl-onon', { originalDownloadEnabled: true });
await openLightbox(page, onOn);
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 }) => {
test.setTimeout(120_000);
const token = await seedDownloadAlbum('dl-wechat', {});
// UA 覆写先于应用代码(addInitScript);MicroMessenger 触发引导浮层面。
await openLightbox(page, token, () => {
const ua =
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49(0x18003133) NetType/WIFI Language/zh_CN';
Object.defineProperty(window.navigator, 'userAgent', { get: () => ua, configurable: true });
});
let endpointHit = false;
page.on('request', (request) => {
if (request.url().includes('/download?')) endpointHit = true;
});
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();
// 「知道了」收起浮层;期间 download 端点零命中(引导替代直接下载)。
await page.locator('.guide-dismiss').click();
await expect(page.locator('.wechat-guide')).toHaveCount(0);
expect(endpointHit).toBe(false);
});
test('real click downloads browse via signed url from the endpoint', async ({ page }) => {
test.setTimeout(120_000);
const token = await seedDownloadAlbum('dl-real', {});
// 捕获 window.open(避免真实弹出标签页):记录 URL 供对象键断言。
await openLightbox(page, token, () => {
(window as unknown as { __openedUrls: string[] }).__openedUrls = [];
window.open = ((url: string | URL) => {
(window as unknown as { __openedUrls: string[] }).__openedUrls.push(String(url));
return {} as Window;
}) as typeof window.open;
});
const endpoint = page.waitForResponse(
(response) =>
response.url().includes('/download?variant=browse') && response.status() === 200,
);
await page.locator('.download-main').click();
const response = await endpoint;
expect((await response.json()).variant).toBe('browse');
await expect
.poll(async () =>
page.evaluate(() => (window as unknown as { __openedUrls: string[] }).__openedUrls[0] ?? ''),
)
.toContain('/variants/');
const opened = await page.evaluate(
() => (window as unknown as { __openedUrls: string[] }).__openedUrls[0] ?? '',
);
// 短期签名凭证(SigV4 查询参数面)+ browse 对象键(无水印相册)。
expect(opened).toContain('X-Amz-Signature');
expect(opened).toContain('/browse.jpg');
});