import { test, expect, devices, type Page } from '@playwright/test' // These tests exercise real browser clipboard operations. Upload responses are // fixtures; the end-to-end cabinet tests cover actual transfers independently. async function mockShareUpload(page: Page) { await page.route('**/api/v1/uploads**', async route => { const path = new URL(route.request().url()).pathname if (path.endsWith('/publish')) await route.fulfill({ json: { code: '7K3MP9TX2R', url: 'http://127.0.0.1:18981/#/pickup/7K3MP9TX2R', expires: 2000000000 } }) else if (path.endsWith('/uploads')) await route.fulfill({ json: { id: 'a'.repeat(32), manage_token: 'b'.repeat(43), files: [{ id: 'c'.repeat(32) }] } }) else if (route.request().method() === 'PUT') await route.fulfill({ json: {} }) else await route.abort() }) } async function openShare(page: Page) { await mockShareUpload(page) await page.goto('http://127.0.0.1:18981/') await page.locator('input[type=file]').setInputFiles({ name: 'clipboard.txt', mimeType: 'text/plain', buffer: Buffer.from('clipboard regression') }) await page.getByRole('button', { name: '上传并生成取件码' }).click() await expect(page.getByRole('heading', { name: '文件已寄出' })).toBeVisible() } const profiles = [ { name: 'desktop HTTP fallback', device: undefined, mode: 'absent' }, { name: 'Android touch HTTP fallback', device: devices['Pixel 7'], mode: 'absent' }, // Chromium emulates the iPhone viewport/touch configuration, not Safari's engine. { name: 'iPhone touch layout HTTP fallback', device: devices['iPhone 13'], mode: 'absent' }, { name: 'Android touch denied Clipboard API', device: devices['Pixel 7'], mode: 'denied' }, { name: 'Android touch secure Clipboard API', device: devices['Pixel 7'], mode: 'native' }, ] for (const profile of profiles) { test(`actual clipboard copy and paste (${profile.name})`, async ({ browser }) => { const context = await browser.newContext(profile.device ?? {}) try { const page = await context.newPage() await mockShareUpload(page) // Localhost exposes Clipboard API. Reproduce its absence on LAN HTTP or its // rejection without replacing the browser's actual copy/paste operations. if (profile.mode !== 'native') await page.addInitScript(mode => { Object.defineProperty(navigator, 'clipboard', { configurable: true, value: mode === 'absent' ? undefined : { writeText: async () => { throw new DOMException('Denied', 'NotAllowedError') } } }) }, profile.mode) await page.goto('http://127.0.0.1:18981/') await page.evaluate(() => { const input = document.createElement('textarea') input.id = 'clipboard-seed' input.value = 'previous clipboard content' document.body.appendChild(input) }) await page.locator('#clipboard-seed').selectText() await page.keyboard.press('Control+c') await page.locator('#clipboard-seed').fill('') await page.keyboard.press('Control+v') await expect(page.locator('#clipboard-seed')).toHaveValue('previous clipboard content') await page.locator('#clipboard-seed').evaluate(node => node.remove()) // Preserve this document's seeded clipboard and upload through the normal UI. await page.locator('input[type=file]').setInputFiles({ name: 'clipboard.txt', mimeType: 'text/plain', buffer: Buffer.from('clipboard regression') }) await page.getByRole('button', { name: '上传并生成取件码' }).click() await expect(page.getByRole('heading', { name: '文件已寄出' })).toBeVisible() const code = (await page.locator('.pickup-code').innerText()).replaceAll('-', '') expect(code).toMatch(/^[0-9A-Z]{10}$/) const url = await page.getByRole('textbox', { name: '取件链接' }).inputValue() await page.locator('.share-dialog').evaluate(node => { const input = document.createElement('input') input.setAttribute('aria-label', '粘贴检查') node.appendChild(input) }) for (const [name, value] of [['复制取件码', code], ['复制取件链接', url]]) { const button = page.getByRole('button', { name, exact: true }) if (profile.device) await button.tap(); else await button.click() await expect(page.getByText(name === '复制取件码' ? '取件码已复制' : '取件链接已复制', { exact: true })).toBeVisible() const paste = page.getByRole('textbox', { name: '粘贴检查' }) await paste.fill('') await paste.focus() await expect(paste).toBeFocused() // Native paste reads the actual clipboard, including in touch emulation. await page.keyboard.press('Control+v') await expect(paste).toHaveValue(value) await expect(page.locator('.share-dialog textarea')).toHaveCount(0) } expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBeTruthy() } finally { await context.close() } }) } test('blocked copy offers manual copying without a success message', async ({ page }) => { await page.addInitScript(() => { Object.defineProperty(navigator, 'clipboard', { value: undefined, configurable: true }) const native = document.execCommand.bind(document) document.execCommand = (command, showUI, value) => command === 'copy' ? false : native(command, showUI, value) }) await openShare(page) await page.getByRole('button', { name: '复制取件码', exact: true }).click() await expect(page.getByText('复制未完成,请选择或长按取件码、链接手动复制', { exact: true })).toBeVisible() await expect(page.getByText('取件码已复制', { exact: true })).toHaveCount(0) await expect(page.locator('.share-dialog textarea')).toHaveCount(0) await page.locator('.pickup-code').selectText() expect(await page.evaluate(() => getSelection()?.toString())).toBe(await page.locator('.pickup-code').innerText()) })