From 313659fad05add7ef4e2450017da0f4e9d0e08ae Mon Sep 17 00:00:00 2001 From: luinbytes <42706009+luinbytes@users.noreply.github.com> Date: Wed, 11 Nov 2026 09:14:42 +0000 Subject: [PATCH] Fix browser verification drift after homepage design review --- app/fonts/instrument-serif-latin-400.woff2 | Bin 0 -> 21032 bytes app/globals.css | 4 +- docs/verification/design-92-browser-drift.md | 71 ++++++ tests/browser/browser_test.py | 227 ++++++++----------- tests/browser/issues_test.py | 2 +- 5 files changed, 164 insertions(+), 140 deletions(-) create mode 100644 app/fonts/instrument-serif-latin-400.woff2 create mode 100644 docs/verification/design-92-browser-drift.md diff --git a/app/fonts/instrument-serif-latin-400.woff2 b/app/fonts/instrument-serif-latin-400.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0ad69719cac6f45ed3057825095b0cdffa7d3c99 GIT binary patch literal 21032 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 None: @@ -767,7 +767,7 @@ def test_pixel_food_pause_and_reduced_motion(self) -> None: wait_for_condition(page, "before => document.querySelector('[data-pond-state]')?.dataset.fishPositions !== before", arg=positions) x, y = find_open_water(page, pond) before = int(pond.get_attribute("data-food-dropped-count")) - page.mouse.click(x, y, button="right") + page.mouse.click(x, y) wait_for_condition(page, "before => Number(document.querySelector('[data-pond-state]')?.dataset.foodDroppedCount) > before", arg=before) self.assertGreater(int(pond.get_attribute("data-food-count")), 0) page.evaluate("""() => { @@ -1029,6 +1029,21 @@ def test_copyright_year_hydrates_when_the_visitors_clock_differs(self) -> None: expect(page.locator("footer")).to_contain_text(expected) self.assertEqual(issues, []) + def test_display_font_loads_under_the_production_security_policy(self) -> None: + browser = self.playwright.chromium.launch() + self.addCleanup(browser.close) + page = browser.new_page() + issues = [] + page.on("console", lambda message: record_console_issue(issues, message)) + page.goto(self.base_url, wait_until="networkidle") + loaded = page.evaluate("""async () => { + const faces = await document.fonts.load('46px "Instrument Serif"', 'Lu'); + return faces.length === 1 && faces[0].status === 'loaded'; + }""") + self.assertTrue(loaded) + self.assertIn('Instrument Serif', page.locator('#hero-title').evaluate('element => getComputedStyle(element).fontFamily')) + self.assertEqual(issues, []) + def test_homepage_is_responsive_and_routes_featured_work(self) -> None: for viewport_name, viewport in ( ("desktop", {"width": 1440, "height": 900}), @@ -1046,34 +1061,18 @@ def test_homepage_is_responsive_and_routes_featured_work(self) -> None: ).is_visible() ) expect(page.locator("#hero-title")).to_have_css("clip-path", "none") + cards = page.locator("#work article") + self.assertEqual(cards.count(), 4) for project in ("Rakazo", "linux-sonar", "HomeBot", "ConnectorMap"): - self.assertTrue( - page.get_by_role("button", name=re.compile(project, re.I)).is_visible() - ) - - wait_for_condition( - page, - "document.querySelector('[data-pond-state]')?.dataset.pondState === 'running'" - ) - impact_count = page.locator("[data-pond-state]").get_attribute( - "data-primary-impact-count" - ) - self.assertTrue( - page.get_by_role("group", name="Featured projects").is_visible() - ) - page.get_by_role("button", name=re.compile("Rakazo", re.I)).click() - self.assertEqual( - page.locator("[data-pond-state]").get_attribute( - "data-primary-impact-count" - ), - impact_count, - ) - proof = page.get_by_text("4 highlighted merged PRs", exact=True) - proof.wait_for(state="visible") - self.assertTrue(proof.is_visible()) - self.assertTrue( - page.get_by_role("link", name="Merged work").is_visible() - ) + expect(cards.get_by_role("heading", name=project, exact=True)).to_be_visible() + wait_for_condition(page, "document.querySelector('[data-pond-state]')?.dataset.pondState === 'running'") + impact_count = page.locator("[data-pond-state]").get_attribute("data-primary-impact-count") + cards.first.scroll_into_view_if_needed() + cards.first.get_by_role("heading").click() + self.assertEqual(page.locator("[data-pond-state]").get_attribute("data-primary-impact-count"), impact_count) + expect(cards.first.locator("[class*=projectFigure] strong")).to_have_text("4") + expect(cards.first.get_by_text("highlighted merged PRs upstream", exact=True)).to_be_visible() + expect(cards.first.get_by_role("link", name="Merged work")).to_be_visible() self.assertLessEqual( page.evaluate("document.documentElement.scrollWidth"), viewport["width"], @@ -1087,46 +1086,28 @@ def test_homepage_is_responsive_and_routes_featured_work(self) -> None: ) browser.close() - def test_project_marker_is_centered_for_every_selection(self) -> None: + def test_project_grid_shows_every_card_without_selection(self) -> None: browser = self.playwright.chromium.launch() self.addCleanup(browser.close) for width, height in ((1440, 900), (390, 844), (320, 800)): with self.subTest(width=width): page = browser.new_page(viewport={"width": width, "height": height}) page.goto(self.base_url, wait_until="networkidle") - buttons = page.get_by_role("group", name="Featured projects").get_by_role("button") - for index in range(4): - button = buttons.nth(index) - button.click() - page.wait_for_timeout(400) - self.assertEqual(button.get_attribute("aria-pressed"), "true") - centers = button.evaluate("""async button => { - const marker = button.querySelector('i'); - let previous = null; - let stable = 0; - const deadline = performance.now() + 5000; - while (performance.now() < deadline) { - await new Promise(requestAnimationFrame); - const bounds = button.getBoundingClientRect(); - const dot = marker.getBoundingClientRect(); - const centers = [bounds.y + bounds.height / 2, dot.y + dot.height / 2]; - stable = previous && centers.every((value, index) => Math.abs(value - previous[index]) < 0.05) ? stable + 1 : 0; - if (stable >= 3) return centers; - previous = centers; - } - throw new Error('Project marker did not settle'); - }""") - self.assertAlmostEqual(centers[0], centers[1], delta=0.75) - if width <= 390: - heading = page.locator("#work article h3") - expect(heading).to_have_text(button.locator("strong").inner_text()) - heading.scroll_into_view_if_needed() - self.assertTrue(heading.evaluate("""element => { - const range = document.createRange(); - range.selectNodeContents(element); - const lines = [...range.getClientRects()]; - return lines.length === 1 && lines[0].width <= element.clientWidth; - }"""), f"Project heading wraps at {width}px") + cards = page.locator("#work article") + self.assertEqual(cards.count(), 4) + self.assertEqual(page.locator("#work [aria-pressed]").count(), 0) + for index, project in enumerate(("Rakazo", "linux-sonar", "HomeBot", "ConnectorMap")): + card = cards.nth(index) + heading = card.get_by_role("heading", name=project, exact=True) + card.scroll_into_view_if_needed() + expect(heading).to_be_visible() + expect(card.get_by_role("link", name=f"View project {project}", exact=True)).to_be_visible() + self.assertTrue(heading.evaluate("""element => { + const range = document.createRange(); + range.selectNodeContents(element); + return [...range.getClientRects()].every(line => line.width <= element.clientWidth + 1); + }"""), f"Project heading overflows at {width}px") + page.screenshot(path=SCREENSHOTS / f"project-grid-{width}.png", full_page=True) page.close() def test_mobile_navigation_hero_viewport_and_system_color_modes(self) -> None: @@ -1268,9 +1249,9 @@ def test_homepage_preserves_layout_and_world_state_across_viewport_matrix(self) const heading = document.querySelector('#hero-title').getBoundingClientRect(); const header = document.querySelector('header').getBoundingClientRect(); const pond = document.querySelector('[data-renderer]').getBoundingClientRect(); - const buttons = [...document.querySelectorAll('[aria-label="Featured projects"] button')] + const buttons = [...document.querySelectorAll('#work article [class*="projectActions"] a:first-child')] .map(element => element.getBoundingClientRect()); - const projectNames = [...document.querySelectorAll('[aria-label="Featured projects"] strong')]; + const projectNames = [...document.querySelectorAll('#work article h3')]; const aboutCards = [...document.querySelectorAll('#about > article, #about > div:last-child')]; const layoutWidth = document.documentElement.clientWidth; const fish = document.querySelector('[data-pond-state]').dataset.fishPositions @@ -1281,11 +1262,16 @@ def test_homepage_preserves_layout_and_world_state_across_viewport_matrix(self) headerInside: header.left >= -1 && header.right <= width + 1, pondFits: Math.abs(pond.width - width) < 1 && Math.abs(pond.height - height) < 1, projectTargets: buttons.length === 4 && buttons.every(button => button.width >= 44 && button.height >= 44), - projectDistribution: width <= 430 || (buttons.length === 4 && Math.abs(buttons[3].right - document.querySelector('[aria-label="Featured projects"]').getBoundingClientRect().right) < 2), + projectDistribution: (() => { + const cards = [...document.querySelectorAll('#work article')].map(card => card.getBoundingClientRect()); + return cards.length === 4 && (width <= 900 + ? cards.every((card, index) => index === 0 || card.top >= cards[index - 1].bottom) + : Math.abs(cards[0].top - cards[1].top) < 1 && cards[1].left >= cards[0].right && cards[2].top >= cards[0].bottom); + })(), projectNamesFit: width > 430 || projectNames.every(element => { const range = document.createRange(); range.selectNodeContents(element); - return range.getBoundingClientRect().width <= element.getBoundingClientRect().width + 1; + return [...range.getClientRects()].every(line => line.width <= element.getBoundingClientRect().width + 1); }), aboutCardsCentered: width > 430 || aboutCards.every(element => { const bounds = element.getBoundingClientRect(); @@ -1432,7 +1418,7 @@ def test_loading_shows_skeleton_then_fades_in_the_live_ecosystem(self) -> None: self.assertEqual(host.evaluate("element => getComputedStyle(element).opacity"), "1") browser.close() - def test_hero_copy_does_not_block_pointer_water_reaction(self) -> None: + def test_hero_copy_is_selectable_and_blocks_pond_input(self) -> None: browser = self.playwright.chromium.launch() page = self.pond_page(browser, viewport={"width": 1280, "height": 800}) page.goto(f"{self.base_url}/?pond-seed=e2e-hero-pointer", wait_until="networkidle") @@ -1453,7 +1439,12 @@ def test_hero_copy_does_not_block_pointer_water_reaction(self) -> None: box["y"] + box["height"] * vertical, ) responsive |= pond.get_attribute("data-food-affordance") == "true" - self.assertTrue(responsive) + self.assertFalse(responsive) + self.assertEqual(page.locator('#hero-title').evaluate('element => getComputedStyle(element).userSelect'), 'auto') + before_food = pond.get_attribute("data-food-dropped-count") + page.get_by_role("heading", name="I make stubborn software behave.").click() + page.wait_for_timeout(200) + self.assertEqual(pond.get_attribute("data-food-dropped-count"), before_food) action = page.get_by_role("link", name="Things I’ve made").bounding_box() page.mouse.move( @@ -1497,12 +1488,12 @@ def test_navigation_and_bordered_controls_keep_their_visual_hierarchy(self) -> N navigation.evaluate_all( "elements => elements.map(element => [element.textContent.trim(), element.getAttribute('href')])" ), - [["Work", "#work"], ["About", "#about"], ["Contact", "#contact"]], + [["Work", "#work"], ["More", "#about"], ["Contact", "#contact"]], ) expect(hero.get_by_role("img", name="Lu's illustrated avatar wearing a pink cap")).to_be_visible() expect(hero.get_by_role("heading", level=1)).to_have_text("Hi, I’m Lu.") expect(page.locator("header a[href='#top']")).to_be_visible() - category_labels = page.locator("[aria-label='Featured projects'] button small") + category_labels = page.locator("#work article [class*=projectEyebrow]") self.assertEqual(category_labels.count(), 4) self.assertTrue( category_labels.evaluate_all( @@ -1525,21 +1516,19 @@ def test_navigation_and_bordered_controls_keep_their_visual_hierarchy(self) -> N } def assert_homebot_image_fits(panel) -> None: - illustration = panel.locator('svg[aria-hidden="true"]').first + illustration = panel.locator('[class*=projectIllustration] > svg') expect(illustration).to_be_visible() self.assertGreater(illustration.bounding_box()["width"], 100) self.assertEqual(panel.locator("video, img").count(), 0) for project, href in project_hrefs.items(): - button = page.get_by_role("button", name=re.compile(project, re.I)) - button.click() - self.assertEqual(button.get_attribute("aria-pressed"), "true") panel = page.locator("article").filter( has=page.get_by_role("heading", name=project, exact=True) ) + panel.scroll_into_view_if_needed() panel.wait_for(state="visible") self.assertEqual( - panel.get_by_role("link", name="View project", exact=True).get_attribute( + panel.get_by_role("link", name=f"View project {project}", exact=True).get_attribute( "href" ), href, @@ -1554,18 +1543,16 @@ def assert_homebot_image_fits(panel) -> None: )).to_be_visible() for tag in ("Read + act", "Marked inferences", "Source links"): expect(panel.locator("[class*='proofRow']").get_by_text(tag, exact=True)).to_be_visible() - expect(panel.locator('svg[aria-hidden="true"]').first).to_be_visible() - link = panel.get_by_role("link", name="View project", exact=True) + expect(panel.locator('[class*=projectIllustration] > svg')).to_be_visible() + link = panel.get_by_role("link", name=f"View project {project}", exact=True) expect(link).to_have_attribute("target", "_blank") expect(link).to_have_attribute("rel", "noreferrer") page.set_viewport_size({"width": 390, "height": 844}) - homebot_button = page.get_by_role("button", name=re.compile("HomeBot", re.I)) - homebot_button.click() - self.assertEqual(homebot_button.get_attribute("aria-pressed"), "true") homebot_panel = page.locator("article").filter( has=page.get_by_role("heading", name="HomeBot", exact=True) ) + homebot_panel.scroll_into_view_if_needed() homebot_panel.wait_for(state="visible") assert_homebot_image_fits(homebot_panel) browser.close() @@ -1586,8 +1573,9 @@ def test_pointer_stirs_the_pond(self) -> None: self.assertEqual( page.evaluate("getComputedStyle(document.querySelector('#top')).userSelect"), - "none", + "auto", ) + self.assertEqual(page.locator('[data-pond-state]').evaluate('element => getComputedStyle(element).userSelect'), 'none') self.assertEqual(page.get_by_text("Touch the water", exact=True).count(), 0) pond = page.locator("[data-pond-state]") @@ -1716,15 +1704,15 @@ def test_food_drop_reservation_and_feeding(self) -> None: }, { once: true })""" ) page.mouse.click(fish_x, fish_y, button="right") + self.assertEqual(page.locator("html").get_attribute("data-pond-context-prevented"), "false") + self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), food_before) + page.mouse.click(fish_x, fish_y) wait_for_condition( page, "initial => Number(document.querySelector('[data-pond-state]')?.dataset.foodDroppedCount) > initial", arg=food_before, ) - self.assertEqual( - page.locator("html").get_attribute("data-pond-context-prevented"), - "true", - ) + self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), food_before + 1) camera_x, camera_y, scale_x, scale_y = ( float(value) for value in pond.get_attribute("data-pond-camera").split(",") ) @@ -1825,7 +1813,7 @@ def test_reduced_motion_preserves_the_ecosystem_at_low_speed(self) -> None: for point in pond.get_attribute("data-fish-positions").split(";") ] fish_x, fish_y = find_open_water(page, pond, fish_points) - page.mouse.click(fish_x, fish_y, button="right") + page.mouse.click(fish_x, fish_y) wait_for_condition( page, "Number(document.querySelector('[data-pond-state]')?.dataset.foodDroppedCount) > 0" @@ -1852,52 +1840,14 @@ def test_mobile_touch_disturbs_the_shared_pond(self) -> None: for point in pond.get_attribute("data-fish-positions").split(";") ] fish_x, fish_y = find_open_water(page, pond, fish_points) - page.evaluate("""() => { - window.__pondTouchEvents = []; - for (const type of ['pointerdown', 'pointerup', 'pointercancel']) { - document.addEventListener(type, event => { - if (event.pointerType === 'touch') window.__pondTouchEvents.push({ - type, time: performance.now(), x: event.clientX, y: event.clientY, - target: event.target?.tagName, - }); - }, true); - } - }""") impacts_before = int(pond.get_attribute("data-primary-impact-count")) + food_before = int(pond.get_attribute("data-food-dropped-count")) page.touchscreen.tap(fish_x, fish_y) - wait_for_condition( - page, - "document.querySelector('[data-pond-state]')?.dataset.touchGesture === 'single-tap-impact'" - ) - self.assertEqual( - int(pond.get_attribute("data-primary-impact-count")), - impacts_before + 1, - ) + wait_for_condition(page, "document.querySelector('[data-pond-state]')?.dataset.touchGesture === 'tap-food'") + wait_for_condition(page, "before => Number(document.querySelector('[data-pond-state]').dataset.foodDroppedCount) === before + 1", arg=food_before) + self.assertEqual(int(pond.get_attribute("data-primary-impact-count")), impacts_before + 1) self.assertGreater(int(pond.get_attribute("data-ring-count")), 0) - self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), 0) - impacts_before_food = int(pond.get_attribute("data-primary-impact-count")) - touch_session = context.new_cdp_session(page) - - def double_tap(x, y): - touch_session.send("Input.synthesizeTapGesture", { - "x": x, "y": y, "duration": 100, - "tapCount": 2, "gestureSourceType": "touch", - }) - - double_tap(fish_x, fish_y) - try: - wait_for_condition( - page, - "Number(document.querySelector('[data-pond-state]')?.dataset.foodDroppedCount) > 0" - ) - except PlaywrightTimeoutError: - detail = pond.evaluate( - "element => ({ gesture: element.dataset.touchGesture, requested: element.dataset.foodRequestedAt, frame: element.dataset.frame, state: element.dataset.pondState })" - ) - detail["touch_events"] = page.evaluate("window.__pondTouchEvents") - context.close() - browser.close() - self.fail(f"Double-tap did not commit food: {detail}") + self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), food_before + 1) camera_x, camera_y, scale_x, scale_y = ( float(value) for value in pond.get_attribute("data-pond-camera").split(",") ) @@ -1913,16 +1863,19 @@ def double_tap(x, y): page.wait_for_timeout(500) self.assertEqual( int(pond.get_attribute("data-primary-impact-count")), - impacts_before_food, + impacts_before + 1, ) dropped_count = int(pond.get_attribute("data-food-dropped-count")) - project_button = page.get_by_role("button", name=re.compile("Rakazo", re.I)) - project_button.scroll_into_view_if_needed() - box = project_button.bounding_box() - double_tap(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2) + project_link = page.get_by_role("link", name="Things I’ve made", exact=True) + project_link.scroll_into_view_if_needed() + box = project_link.bounding_box() + page.touchscreen.tap(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2) page.wait_for_timeout(500) self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), dropped_count) + page.evaluate("window.scrollTo(0, 0)") + fish_x, fish_y = find_open_water(page, pond) + def pointer_sequence(events): page.evaluate( """({ x, y, events }) => { @@ -1970,7 +1923,7 @@ def pointer_sequence(events): self.assertEqual(int(pond.get_attribute("data-food-dropped-count")), dropped_count) self.assertEqual( int(pond.get_attribute("data-primary-impact-count")), - impacts_before_food, + impacts_before + 1, ) self.assertGreaterEqual(int(pond.get_attribute("data-fish-count")), 8) context.close() diff --git a/tests/browser/issues_test.py b/tests/browser/issues_test.py index c45154c..fead406 100644 --- a/tests/browser/issues_test.py +++ b/tests/browser/issues_test.py @@ -24,7 +24,7 @@ def test_actions_captions_privacy_and_immediate_navigation(self): self.assertGreaterEqual(link.evaluate('e=>parseFloat(getComputedStyle(e).fontSize)'),14) self.assertGreaterEqual(link.bounding_box()['height'],44) expect(page.locator('footer').get_by_role('link',name='Privacy')).to_have_attribute('href','/privacy') - for section in ('Work','About','Contact'): + for section in ('Work','More','Contact'): page.get_by_role('navigation',name='Portfolio navigation').get_by_role('link',name=section,exact=True).click() expect(page.locator('header [aria-current="location"]')).to_have_text(section) self.assertEqual(page.locator('#contact').evaluate('e=>getComputedStyle(e).opacity'),'1')