99 lines
5.6 KiB
TypeScript
99 lines
5.6 KiB
TypeScript
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())
|
|
})
|