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 { 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 { 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'); });