mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
8dca372c47
Both only ran on a developer's machine, and both had stopped telling the truth. The end-to-end flows now run in CI in Chromium and in WebKit (the iPad and iPhone cases need the engine every iOS browser uses), with the browsers cached on the lockfile and the traces uploaded when a flow fails. A second job builds the client and fails when one chunk carries both map engines. Making them pass meant fixing what had rotted. The setup closed every first-run notice but the release notes, whose X is called Close, so the notes covered the dashboard and every flow after login timed out on a click. The iPad case still expected undraggable place rows; tablets drag on purpose since #1616, behind the long-press bridge, so it now checks what #1432 is about, a swipe that scrolls and raises no drop overlay, with locators for the scroller and the Plan tab that match today's markup. The iPhone case read the body scroll lock through window.scrollBy, which moves an overflow: hidden viewport in every engine; it now reads the lock where it takes effect and checks the page keeps its place.
117 lines
5.6 KiB
TypeScript
117 lines
5.6 KiB
TypeScript
import { test, expect, devices } from '@playwright/test'
|
|
import { dismissSystemNotices } from './helpers'
|
|
|
|
// Phone regression guard for #1809: the dashboard must scroll the DOCUMENT.
|
|
//
|
|
// iOS Safari minimises its address bar only in reaction to the root scroller
|
|
// moving. V4 blocked that twice over: html was globally overflow:hidden and the
|
|
// mobile shell added a scroll container of its own, so the gesture never
|
|
// reached the viewport and the bar kept ~1/6th of the screen forever. The bar
|
|
// itself is not observable from page context, but the invariant it hangs off is:
|
|
// document.scrollingElement has to move.
|
|
//
|
|
// Needs WebKit (`npx playwright install webkit`). playwright.config.ts has no
|
|
// webkit project, the engine comes from this file-level device (defaultBrowserType
|
|
// 'webkit'), same as e2e/ipad-scroll-1432.spec.ts. WebKit is the point here, not
|
|
// a nicety: viewport propagation and the toolbar behaviour are Safari's.
|
|
//
|
|
// The file sorts last in the e2e run, so the trips it seeds cannot show up in
|
|
// another spec's dashboard assertions.
|
|
test.use({ ...devices['iPhone 14'] })
|
|
|
|
function readScroll() {
|
|
const el = document.scrollingElement as HTMLElement
|
|
return {
|
|
top: el.scrollTop,
|
|
scrollHeight: el.scrollHeight,
|
|
viewport: window.innerHeight,
|
|
htmlOverflow: getComputedStyle(document.documentElement).overflowY,
|
|
}
|
|
}
|
|
|
|
test('#1809 iPhone: flow screens scroll the document, full-screen ones do not', async ({ page }) => {
|
|
await page.goto('/dashboard')
|
|
await dismissSystemNotices(page)
|
|
|
|
// The context has to be the one from the report: phone width, coarse pointer.
|
|
// If either is wrong, nothing below proves anything.
|
|
const env = await page.evaluate(() => ({
|
|
width: window.innerWidth,
|
|
coarse: window.matchMedia('(pointer: coarse)').matches,
|
|
}))
|
|
expect(env.width, 'iPhone sits below the 768px phone breakpoint').toBeLessThan(768)
|
|
expect(env.coarse, 'iPhone reports a coarse primary pointer').toBe(true)
|
|
|
|
// Enough trips for the dashboard to overflow a phone screen.
|
|
const stamp = Date.now()
|
|
for (let i = 1; i <= 20; i++) {
|
|
const res = await page.request.post('/api/trips', {
|
|
data: { title: `URL bar ${stamp} ${i}`, start_date: '2099-06-01', end_date: '2099-06-10' },
|
|
})
|
|
expect(res.ok(), `seed trip ${i}`).toBeTruthy()
|
|
}
|
|
await page.reload()
|
|
await dismissSystemNotices(page)
|
|
await expect(page.getByText(`URL bar ${stamp} 1`).first()).toBeVisible({ timeout: 20_000 })
|
|
|
|
// 1. The root scroller is unlocked and the page is longer than the viewport.
|
|
const before = await page.evaluate(readScroll)
|
|
expect(before.htmlOverflow, 'html must not lock the viewport scroller').not.toBe('hidden')
|
|
expect(before.scrollHeight, 'dashboard is longer than one viewport').toBeGreaterThan(before.viewport)
|
|
expect(before.top).toBe(0)
|
|
|
|
// 2. Scrolling moves the document, the signal Safari retracts its bar for.
|
|
await page.evaluate(() => window.scrollBy(0, 400))
|
|
const scrolled = await page.evaluate(readScroll)
|
|
expect(scrolled.top, 'the document itself scrolled').toBeGreaterThan(0)
|
|
|
|
// 3. The brand tile scrolls the page back up (it used to walk up to the shell
|
|
// container, which no longer exists).
|
|
await page.getByRole('button', { name: 'TREK' }).click()
|
|
await expect.poll(() => page.evaluate(() => document.scrollingElement!.scrollTop)).toBe(0)
|
|
|
|
// 4. The shared body scroll lock: with the document as the scroller, an open
|
|
// sheet must freeze the page behind it and give the position back. The
|
|
// lock is body overflow: hidden, which reaches the viewport only while
|
|
// html stays overflow: visible (index.css), and there it stops the swipe.
|
|
// It does not stop a script: window.scrollBy moves an overflow: hidden
|
|
// viewport in every engine, so the lock is read where it takes effect.
|
|
await page.evaluate(() => window.scrollBy(0, 300))
|
|
const locked = await page.evaluate(readScroll)
|
|
expect(locked.top).toBeGreaterThan(0)
|
|
|
|
await page.getByRole('button', { name: 'New Trip' }).click()
|
|
await expect(page.getByRole('dialog')).toBeVisible()
|
|
const lock = await page.evaluate(() => ({
|
|
body: getComputedStyle(document.body).overflowY,
|
|
html: getComputedStyle(document.documentElement).overflowY,
|
|
top: document.scrollingElement!.scrollTop,
|
|
}))
|
|
expect(lock.body, 'the open sheet locks body').toBe('hidden')
|
|
expect(lock.html, 'html stays visible, so the lock reaches the viewport').toBe('visible')
|
|
expect(lock.top, 'opening the sheet does not move the page').toBe(locked.top)
|
|
|
|
await page.keyboard.press('Escape')
|
|
await expect(page.getByRole('dialog')).toHaveCount(0)
|
|
expect(await page.evaluate(() => getComputedStyle(document.body).overflowY), 'the lock is released').not.toBe('hidden')
|
|
expect(await page.evaluate(() => document.scrollingElement!.scrollTop)).toBe(locked.top)
|
|
await page.evaluate(() => window.scrollBy(0, 200))
|
|
expect(await page.evaluate(() => document.scrollingElement!.scrollTop)).toBeGreaterThan(locked.top)
|
|
|
|
// 5. Counter-probe: the map screens keep their own full-viewport layout. They
|
|
// deliberately do NOT scroll the document (and so keep the address bar), and
|
|
// the map must not collapse to zero height now that the shell has none.
|
|
await page.goto('/atlas')
|
|
await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 20_000 })
|
|
const atlas = await page.evaluate(() => {
|
|
const map = document.querySelector('.leaflet-container') as HTMLElement
|
|
window.scrollBy(0, 400)
|
|
return {
|
|
top: document.scrollingElement!.scrollTop,
|
|
mapHeight: map.getBoundingClientRect().height,
|
|
}
|
|
})
|
|
expect(atlas.top, 'the atlas does not move the document').toBe(0)
|
|
expect(atlas.mapHeight, 'the atlas map keeps a real height').toBeGreaterThan(200)
|
|
})
|