2a489aa16a
Co-Authored-By: Claude Code <noreply@anthropic.com>
131 lines
5.4 KiB
TypeScript
131 lines
5.4 KiB
TypeScript
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');
|
||
});
|