| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- import type { Page } from "@playwright/test"
- import { summarizeFirstNavigation, type FirstNavigationSample } from "./first-navigation-metrics"
- type FirstNavigationProbe = {
- samples: FirstNavigationSample[]
- stop: () => void
- }
- export async function measureFirstNavigation(
- page: Page,
- input: {
- href: string
- destinationPath: string
- sourceSelector: string
- destinationSelector: string
- contentSelector: string
- navigate: () => Promise<void>
- },
- ) {
- await page.evaluate(
- ({ href, destinationPath, sourceSelector, destinationSelector, contentSelector }) => {
- const samples: FirstNavigationSample[] = []
- let started: number | undefined
- let running = true
- const visible = (selector: string) =>
- [...document.querySelectorAll<HTMLElement>(selector)].some((element) => {
- const rect = element.getBoundingClientRect()
- const style = getComputedStyle(element)
- return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none"
- })
- const sample = () => {
- if (!running || started === undefined) return
- requestAnimationFrame(() => {
- setTimeout(() => {
- if (!running || started === undefined) return
- samples.push({
- observedAtMs: performance.now() - started,
- source: visible(sourceSelector),
- destination: `${location.pathname}${location.search}` === destinationPath && visible(destinationSelector),
- content: visible(contentSelector),
- pathname: `${location.pathname}${location.search}`,
- center: document.elementFromPoint(innerWidth / 2, innerHeight / 2)?.textContent?.slice(0, 80),
- })
- sample()
- }, 0)
- })
- }
- document.addEventListener(
- "click",
- (event) => {
- const link = event.target instanceof Element ? event.target.closest("a") : undefined
- if (link?.getAttribute("href") !== href) return
- started = performance.now()
- sample()
- },
- { capture: true, once: true },
- )
- ;(window as Window & { __firstNavigationProbe?: FirstNavigationProbe }).__firstNavigationProbe = {
- samples,
- stop: () => {
- running = false
- },
- }
- },
- {
- href: input.href,
- destinationPath: input.destinationPath,
- sourceSelector: input.sourceSelector,
- destinationSelector: input.destinationSelector,
- contentSelector: input.contentSelector,
- },
- )
- await input.navigate()
- await page.waitForFunction(() => {
- const samples = (window as Window & { __firstNavigationProbe?: FirstNavigationProbe }).__firstNavigationProbe
- ?.samples
- if (!samples) return false
- return samples.length >= 3 && samples.slice(-3).every((sample) => sample.destination && !sample.source)
- })
- const samples = await page.evaluate(() => {
- const probe = (window as Window & { __firstNavigationProbe?: FirstNavigationProbe }).__firstNavigationProbe!
- probe.stop()
- return probe.samples
- })
- return { summary: summarizeFirstNavigation(samples), samples }
- }
|