mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
152 lines
7.5 KiB
TypeScript
152 lines
7.5 KiB
TypeScript
import { expect, test, type Locator, type Page } from '@playwright/test'
|
|
import { mkdir } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
|
|
test.use({ viewport: { width: 412, height: 915 }, hasTouch: true })
|
|
test.setTimeout(60_000)
|
|
|
|
const fixture = '/e2e-fixtures/directory-input-fixture.html'
|
|
const windowsPath = 'C:\\Users\\Example\\Downloads\\Agent\\Hapi\\worktrees\\directory-scroll-project'
|
|
const posixPath = '/home/example/projects/hapi/worktrees/mobile-directory-scroll-project'
|
|
|
|
async function atEnd(input: Locator) {
|
|
await expect.poll(() => input.evaluate((el: HTMLInputElement) => (
|
|
el.scrollWidth - el.clientWidth - el.scrollLeft
|
|
))).toBeLessThanOrEqual(2)
|
|
}
|
|
|
|
async function swipe(page: Page, input: Locator, dx: number, dy = 0) {
|
|
const box = (await input.boundingBox())!
|
|
const startX = dx > 0 ? box.x + 24 : box.x + box.width - 24
|
|
const startY = box.y + box.height / 2
|
|
const session = await page.context().newCDPSession(page)
|
|
await session.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x: startX, y: startY }] })
|
|
for (let step = 1; step <= 8; step++) {
|
|
await session.send('Input.dispatchTouchEvent', {
|
|
type: 'touchMove', touchPoints: [{ x: startX + dx * step / 8, y: startY + dy * step / 8 }],
|
|
})
|
|
}
|
|
await session.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] })
|
|
await session.detach()
|
|
}
|
|
|
|
test('initial long paths show the tail without focusing or changing the value', async ({ page }) => {
|
|
for (const path of [windowsPath, posixPath, 'C:\\项目\\长目录名称\\移动端测试\\'.repeat(4) + '最终目录']) {
|
|
await page.goto(`${fixture}?path=${encodeURIComponent(path)}`)
|
|
const input = page.getByRole('textbox')
|
|
await expect(input).toHaveValue(path)
|
|
expect(await input.evaluate(el => el.scrollWidth > el.clientWidth)).toBe(true)
|
|
if (path === windowsPath && process.env.HAPI_DIRECTORY_SCREENSHOTS) {
|
|
await mkdir(process.env.HAPI_DIRECTORY_SCREENSHOTS, { recursive: true })
|
|
await page.screenshot({ path: join(process.env.HAPI_DIRECTORY_SCREENSHOTS, 'initial-directory.png') })
|
|
}
|
|
await atEnd(input)
|
|
await expect(input).not.toBeFocused()
|
|
}
|
|
})
|
|
|
|
test('manual inspection survives rerenders and taps still enter normal editing', async ({ page }) => {
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
await input.evaluate(el => { el.scrollLeft = 30 })
|
|
await page.getByRole('button', { name: /Unrelated update/ }).click()
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBe(30)
|
|
await input.tap()
|
|
await expect(input).toBeFocused()
|
|
await input.press('Home')
|
|
await input.press('ArrowRight')
|
|
await input.press('ArrowRight')
|
|
await input.press('Shift+ArrowRight')
|
|
expect(await input.evaluate((el: HTMLInputElement) => el.value.slice(el.selectionStart!, el.selectionEnd!))).toBe('\\')
|
|
await input.press('ArrowLeft')
|
|
await input.pressSequentially('edited-')
|
|
await expect(input).toHaveValue('C:edited-' + windowsPath.slice(2))
|
|
expect(await input.evaluate((el: HTMLInputElement) => el.selectionStart)).toBe(9)
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBeLessThan(40)
|
|
await input.press('ControlOrMeta+a')
|
|
expect(await input.evaluate((el: HTMLInputElement) => el.selectionEnd! - el.selectionStart!)).toBe((await input.inputValue()).length)
|
|
})
|
|
|
|
test('leaving the editor reveals the tail and keeps subsequent manual inspection', async ({ page }) => {
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
await input.tap()
|
|
await input.press('End')
|
|
// Reproduce the reset observed on Via/WebView after focus is lost.
|
|
await input.evaluate(el => el.addEventListener('blur', () => { el.scrollLeft = 0 }, { once: true }))
|
|
await page.getByText('HAPI · New Session · Directory fixture', { exact: true }).click()
|
|
await expect(input).not.toBeFocused()
|
|
await atEnd(input)
|
|
await expect(input).toHaveValue(windowsPath)
|
|
await input.evaluate(el => { el.scrollLeft = 35 })
|
|
await page.getByRole('button', { name: /Unrelated update/ }).click()
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBe(35)
|
|
await input.tap()
|
|
await input.press('Home')
|
|
await input.pressSequentially('edited-')
|
|
await expect(input).toHaveValue('edited-' + windowsPath)
|
|
expect(await input.evaluate((el: HTMLInputElement) => el.selectionStart)).toBe(7)
|
|
await page.getByText('HAPI · New Session · Directory fixture', { exact: true }).click()
|
|
await atEnd(input)
|
|
})
|
|
|
|
test('empty and disabled fields retain native behavior without changing the page width', async ({ page }) => {
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
await page.getByRole('button', { name: 'Toggle disabled' }).click()
|
|
await expect(input).toBeDisabled()
|
|
await page.getByRole('button', { name: 'Toggle disabled' }).click()
|
|
await page.getByRole('button', { name: 'Clear', exact: true }).click()
|
|
await expect(input).toHaveValue('')
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBe(0)
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(412)
|
|
await input.tap()
|
|
await expect(input).toBeFocused()
|
|
await input.fill('/tmp')
|
|
await expect(input).toHaveValue('/tmp')
|
|
})
|
|
|
|
test('vertical swipes over the unfocused input still scroll the form', async ({ page, browserName }) => {
|
|
test.skip(browserName !== 'chromium', 'Trusted touch events use Chromium CDP.')
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
const before = await input.evaluate(el => el.scrollLeft)
|
|
await swipe(page, input, 0, -80)
|
|
await expect.poll(() => page.locator('main').evaluate(el => el.scrollTop)).toBeGreaterThan(20)
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBe(before)
|
|
await expect(input).not.toBeFocused()
|
|
})
|
|
|
|
test('Browse and recent selections show tails while preserving full values', async ({ page }) => {
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
await page.getByRole('button', { name: 'Browse', exact: true }).click()
|
|
await expect(input).toHaveValue(posixPath)
|
|
await atEnd(input)
|
|
await page.getByRole('button', { name: windowsPath, exact: true }).click()
|
|
await expect(input).toHaveValue(windowsPath)
|
|
await atEnd(input)
|
|
await input.evaluate(el => { el.scrollLeft = 15 })
|
|
await page.getByRole('button', { name: windowsPath, exact: true }).click()
|
|
await atEnd(input)
|
|
await page.getByRole('button', { name: '/tmp', exact: true }).click()
|
|
await expect(input).toHaveValue('/tmp')
|
|
expect(await input.evaluate(el => el.scrollLeft)).toBe(0)
|
|
})
|
|
|
|
test('unfocused touch swipes inspect both directions without opening the editor', async ({ page, browserName }) => {
|
|
test.skip(browserName !== 'chromium', 'Trusted drag touch events use Chromium CDP.')
|
|
await page.goto(fixture)
|
|
const input = page.getByRole('textbox')
|
|
await input.evaluate(el => { el.scrollLeft = el.scrollWidth })
|
|
const tailOffset = await input.evaluate(el => el.scrollLeft)
|
|
await swipe(page, input, 180)
|
|
await expect.poll(() => input.evaluate(el => el.scrollLeft)).toBeLessThan(tailOffset - 100)
|
|
await expect(input).not.toBeFocused()
|
|
const offset = await input.evaluate(el => el.scrollLeft)
|
|
await swipe(page, input, -140)
|
|
await expect.poll(() => input.evaluate(el => el.scrollLeft)).toBeGreaterThan(offset + 80)
|
|
await expect(input).not.toBeFocused()
|
|
await expect(input).toHaveValue(windowsPath)
|
|
})
|