import { expect, test } from '@playwright/test' import { chooseObjective, expectNoHorizontalOverflow, signIn, EDUCATOR } from './helpers.js' /** * The phone. Every bug in this file was found by a person on an iPhone and * reported as a screenshot, which is a slow way to find out. */ test.describe('on a phone', () => { // By width, not by `isMobile`: an iPad in landscape is a touch device with // 1024px of room, where the reading rail is a rail and there is no drawer to // open. The breakpoint here is the one in ArticlesPage.css. test.skip(({ viewport }) => !viewport || viewport.width > 820, 'about the layout below 820px') test.beforeEach(async ({ page }) => { await signIn(page, EDUCATOR) }) test('the menu opens the contents and the same button closes them', async ({ page }) => { await page.goto('/articles/1') await chooseObjective(page) // Wait for the article, not just the page. The reader claims the menu // button when it mounts, and it does not mount until the article arrives — // tap in that window and you get the site menu, which is the right answer // to "there is nothing else this button could mean yet". await expect(page.locator('.article-title, h1').first()).toContainText(/Croup/i) const burger = page.locator('.nav-burger') await burger.click() await expect(page.locator('.article-sections.open')).toBeVisible() // Pressing it again is how a thumb closes a drawer. It used to do nothing, // and the only way out was the strip of page beside it. await burger.click() await expect(page.locator('.article-sections.open')).toHaveCount(0) }) test('the drawer clears the header rather than covering it', async ({ page }) => { await page.goto('/articles/1') await chooseObjective(page) await expect(page.locator('.article-title, h1').first()).toContainText(/Croup/i) await page.locator('.nav-burger').click() const header = await page.locator('.navbar').boundingBox() const drawer = await page.locator('.article-sections').boundingBox() expect(drawer.y, 'the drawer starts under the header').toBeGreaterThanOrEqual(header.height - 1) // And the button that opened it is still the top thing at that point. const onTop = await page.evaluate(() => { const bar = document.querySelector('.nav-burger').getBoundingClientRect() const el = document.elementFromPoint(bar.x + bar.width / 2, bar.y + bar.height / 2) return el?.closest('.nav-burger') !== null }) expect(onTop, 'something is covering the menu button').toBeTruthy() }) test('a figure opens with the picture in it, not just its caption', async ({ page }) => { // An SVG written with a viewBox and no width or height has a shape but no // size; Safari resolves that to zero inside a shrink-to-fit box, and the // viewer was a caption above an empty screen. The seed's figure is written // the same way on purpose. await page.goto('/articles/1') await chooseObjective(page) const thumb = page.locator('.imgfig-thumb').first() await expect(thumb).toBeVisible() await thumb.click() const image = page.locator('.imgfig-frame img') await expect(image).toBeVisible() // Measured after it has actually loaded: an that is still fetching // is a box one line tall, which looks exactly like the bug being tested. await expect.poll(() => image.evaluate(el => el.complete && el.naturalWidth > 0), { message: 'the figure never loaded' }).toBe(true) const box = await image.boundingBox() expect(box.width, 'the picture has no width').toBeGreaterThan(100) expect(box.height, 'the picture has no height').toBeGreaterThan(40) // And the caption is there too, which is all that used to be. await expect(page.locator('.imgfig-desc')).toContainText(/stridor/i) }) test('nothing on the reading page scrolls sideways', async ({ page }) => { await page.goto('/articles/1') await chooseObjective(page) await expect(page.getByRole('heading', { name: 'Croup' }).first()).toBeVisible() await expectNoHorizontalOverflow(page) }) test('a text box does not zoom the page when it is tapped', async ({ page, browserName }) => { test.skip(browserName !== 'webkit', 'the 16px rule is a Safari rule') await page.goto('/ai') await chooseObjective(page) const size = await page.locator('textarea[aria-label="Ask AI Mode"]').evaluate( el => parseFloat(getComputedStyle(el).fontSize)) // Under 16px, iOS zooms in on focus and never zooms back out. expect(size).toBeGreaterThanOrEqual(16) }) })