Ctrl K
ComunidadGuías…

packages/app/component-tests/browser-pane.spec.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { expect, sourceURL, story } from "../../stories/playwright/story"

const source = (path: string) => sourceURL(new URL(path, import.meta.url))

const modules = {
  fixture: source("../../gui-plugins/src/browser/panel.fixture.tsx"),
  embeds: source("../src/runtime/plugin/embeds.tsx"),
  panelMotion: source("../src/shell/panel-motion.ts"),
  language: source("../src/runtime/i18n/language.tsx"),
}

story.beforeEach(async ({ mount, page }) => {
  // Any story loads the app styles; the fixture mounts the pane beside it on the real host embeds.
  await mount("ui-line-comment--editor")
  await page.evaluate(async (modules) => {
    const [{ mountBrowserPane }, { createEmbeds }, panelMotion, language] = await Promise.all([
      import(modules.fixture),
      import(modules.embeds),
      import(modules.panelMotion),
      import(modules.language),
    ])

    mountBrowserPane({
      createEmbeds,
      panelMotion,
      LanguageProvider: language.LanguageProvider,
      UiI18nBridge: language.UiI18nBridge,
      useLanguage: language.useLanguage,
    })
  }, modules)
  await expect(page.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
})

story("hides a native page when another takes the pane, the pane hides, or it unmounts", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const alpha = root.getByTestId("native-Alpha")
  const beta = root.getByTestId("native-Beta")
  await root.getByRole("button", { name: "Beta", exact: true }).click()
  await expect(beta).toHaveAttribute("data-visible", "true")
  await expect(alpha).toHaveAttribute("data-visible", "false")
  await root.getByRole("button", { name: "Empty", exact: true }).click()
  await expect(root.locator("#browser-panel")).toHaveCount(0)
  await expect(beta).toHaveAttribute("data-visible", "false")
  await root.getByRole("button", { name: "Alpha", exact: true }).click()
  await expect(alpha).toHaveAttribute("data-visible", "true")
  await root.getByRole("button", { name: "Toggle Review tab", exact: true }).click()
  await expect(alpha).toHaveAttribute("data-visible", "false")
  await root.getByRole("button", { name: "Toggle Review tab", exact: true }).click()
  await expect(alpha).toHaveAttribute("data-visible", "true")
  await root.getByRole("button", { name: "Unmount pane", exact: true }).click()
  await expect(alpha).toHaveAttribute("data-visible", "false")
})

story("hides the native view immediately while the pane stays mounted", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const toggle = root.getByRole("button", { name: "Toggle Review tab", exact: true })
  await expect(toggle).toBeEnabled()

  // Read in the same task as the click so a deferred animation-frame hide cannot pass.
  const visible = await toggle.evaluate((element) => {
    element.dispatchEvent(new MouseEvent("click", { bubbles: true }))

    return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
  })

  expect(visible).toBe("false")
  await expect(root.locator("#browser-panel")).toHaveCount(1)
  await toggle.click()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
})

story("paints a still of the page in place of the native view while a panel moves", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const motion = root.getByRole("button", { name: "Toggle panel motion", exact: true })
  const pane = root.getByRole("button", { name: "Toggle Review tab", exact: true })
  const native = root.getByTestId("native-Alpha")
  const still = root.locator("#browser-panel img")

  // Read in the same task as the click: a native view left up for even one frame floats over the moving app.
  const visible = await motion.evaluate((element) => {
    element.dispatchEvent(new MouseEvent("click", { bubbles: true }))

    return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
  })

  expect(visible).toBe("false")
  // A close: the page flies out as a still of itself rather than a blank box.
  await expect(still).toBeVisible()
  await pane.click()
  await expect(still).toBeVisible()
  await motion.click()
  await expect(still).toHaveCount(0)

  // An open shows the still the close left, until the flight lands and the page is back.
  await motion.click()
  await pane.click()
  await expect(still).toBeVisible()
  await expect(native).toHaveAttribute("data-visible", "false")
  await motion.click()
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)
})

story("paints a still of the page in place of the native view while a resize handle is dragged", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const handle = root.getByTestId("pane-resize-handle")
  const native = root.getByTestId("native-Alpha")
  const still = root.locator("#browser-panel img")

  // A native view's bounds land on screen frames before the DOM around it, so one left up through a drag jumps against
  // its pane on every move. A click on the handle moves nothing and must not blink the page out. Read in the same task
  // as each event, so a view hidden and shown again before the next frame cannot pass.
  const visible = await handle.evaluate((element) => {
    const read = () => document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
    const box = element.getBoundingClientRect()
    const at = { bubbles: true, pointerId: 1, clientX: box.x + box.width / 2, clientY: box.y + box.height / 2 }
    element.dispatchEvent(new PointerEvent("pointerdown", { ...at, button: 0 }))
    document.dispatchEvent(new PointerEvent("pointerup", at))
    const click = read()
    element.dispatchEvent(new PointerEvent("pointerdown", { ...at, button: 0 }))
    const pressed = read()
    document.dispatchEvent(new PointerEvent("pointermove", { ...at, clientX: at.clientX - 40 }))

    return { click, pressed, dragging: read() }
  })

  expect(visible).toEqual({ click: "true", pressed: "true", dragging: "false" })
  await expect(still).toBeVisible()
  await handle.evaluate((element) => {
    const box = element.getBoundingClientRect()
    document.dispatchEvent(
      new PointerEvent("pointerup", { bubbles: true, pointerId: 1, clientX: box.x, clientY: box.y }),
    )
  })
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)
})

story("shows the native view again when the window loses focus mid-drag", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const handle = root.getByTestId("pane-resize-handle")
  const native = root.getByTestId("native-Alpha")
  const still = root.locator("#browser-panel img")

  // Alt+Tab mid-drag delivers no pointerup; the page must not stay locked behind a still.
  await handle.evaluate((element) => {
    const box = element.getBoundingClientRect()
    const at = { bubbles: true, pointerId: 1, clientX: box.x + box.width / 2, clientY: box.y + box.height / 2 }
    element.dispatchEvent(new PointerEvent("pointerdown", { ...at, button: 0 }))
    document.dispatchEvent(new PointerEvent("pointermove", { ...at, clientX: at.clientX - 40 }))
  })
  await expect(native).toHaveAttribute("data-visible", "false")
  await expect(handle).toHaveAttribute("data-dragging", "")

  await handle.evaluate(() => window.dispatchEvent(new FocusEvent("blur")))
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)
  await expect(handle).not.toHaveAttribute("data-dragging")
  expect(await handle.evaluate(() => document.body.style.userSelect)).toBe("")
})

story("never flies a still of a page the tab has navigated away from", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const motion = root.getByRole("button", { name: "Toggle panel motion", exact: true })
  const native = root.getByTestId("native-Alpha")
  const still = root.locator("#browser-panel img")
  await motion.click()
  await expect(still).toBeVisible()
  await motion.click()
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)

  await root.getByRole("button", { name: "Hold capture", exact: true }).click()
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("https://example.com/")
  await address.press("Enter")
  await expect(address).toHaveValue("https://example.com/")
  await expect(native).toHaveAttribute("data-visible", "true")
  await motion.click()
  await expect(native).toHaveAttribute("data-visible", "false")
  await expect(still).toHaveCount(0)
  // The new page's own still stands in once it arrives.
  await root.getByRole("button", { name: "Release capture", exact: true }).click()
  await expect(still).toBeVisible()
})

story("refreshes page stills by the window's focus, not the document's", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const captures = async () => Number((await root.getByText(/^Captures: \d+$/).textContent())?.split(": ")[1])
  await page.clock.install()
  await expect(root.getByText(/^Captures: [1-9]/)).toBeVisible()

  // Main reports the window unfocused while this document keeps focus: stills follow the window, so they stop.
  await root.getByRole("button", { name: "Blur window", exact: true }).click()
  const paused = await captures()
  await page.clock.runFor(120_000)
  expect(await captures()).toBe(paused)

  // Focus coming back to the window, even straight into the embed, which never focuses this document, takes a fresh
  // still at once.
  await root.getByRole("button", { name: "Focus window", exact: true }).click()
  await page.clock.runFor(10)
  await expect(root.getByText(`Captures: ${paused + 1}`, { exact: true })).toBeVisible()
})

story("keeps a still of the page under floating content that covers it", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const still = root.locator("#browser-panel img")
  const native = root.getByTestId("native-Alpha")
  // Reduced motion lands panels at once, so the pane keeps no flight still and captures only for the cover.
  await page.emulateMedia({ reducedMotion: "reduce" })
  await root.getByRole("button", { name: "Hold capture", exact: true }).click()
  await root.getByRole("button", { name: "Toggle popover", exact: true }).click()
  await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
  // The native page stays up until its still is ready, so the pane never shows blank.
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)

  // Main hides the native page at once, so the pane hides it only after a frame has presented the still.
  const order = await native.evaluateHandle((element) => {
    const events: string[] = []
    new PerformanceObserver((list) => {
      if (list.getEntries().length > 0) events.push("still presented")
    }).observe({ type: "element" })
    new MutationObserver(() => {
      if (element.getAttribute("data-visible") === "false") events.push("page hidden")
    }).observe(element, { attributeFilter: ["data-visible"] })

    return events
  })

  await root.getByRole("button", { name: "Release capture", exact: true }).click()
  await expect(native).toHaveAttribute("data-visible", "false")
  await expect(still).toBeVisible()
  expect(await order.jsonValue()).toEqual(["still presented", "page hidden"])

  await root.getByRole("button", { name: "Toggle popover", exact: true }).click()
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)
  await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
})

story(
  "freezes on the latest capture when floating content closes and reopens before a capture lands",
  async ({ page }) => {
    const root = page.getByTestId("browser-pane-fixture")
    const popover = root.getByRole("button", { name: "Toggle popover", exact: true })
    const still = root.locator("#browser-panel img")
    const native = root.getByTestId("native-Alpha")
    await page.emulateMedia({ reducedMotion: "reduce" })
    await root.getByRole("button", { name: "Hold capture", exact: true }).click()
    await popover.click()
    await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
    await popover.click()
    await expect(page.locator("[data-popper-positioner]")).toHaveCount(0)
    // The pane measures the overlap on the next animation frame; nothing on screen changes while the capture is held.
    await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))
    await popover.click()
    await expect(root.getByText("Captures: 2", { exact: true })).toBeVisible()

    // Both captures land in order; the first one belongs to the cover that already closed.
    await root.getByRole("button", { name: "Release capture", exact: true }).click()
    await expect(native).toHaveAttribute("data-visible", "false")
    await expect(still).toBeVisible()
    // The fixture draws each capture a pixel wider: the second one is 5 pixels wide.
    await expect(still).toHaveJSProperty("naturalWidth", 5)
  },
)

story("keeps a still of the page under an open dialog instead of blanking the pane", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const still = root.locator("#browser-panel img")
  const native = root.getByTestId("native-Alpha")
  const toggle = root.getByRole("button", { name: "Toggle dialog", exact: true })
  await root.getByRole("button", { name: "Hold capture", exact: true }).click()

  // Read in the same task as the click: the page must not hide before its still exists.
  const visible = await toggle.evaluate((element) => {
    element.dispatchEvent(new MouseEvent("click", { bubbles: true }))

    return document.querySelector('[data-testid="native-Alpha"]')?.getAttribute("data-visible")
  })

  expect(visible).toBe("true")
  await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)

  await root.getByRole("button", { name: "Release capture", exact: true }).click()
  await expect(native).toHaveAttribute("data-visible", "false")
  await expect(still).toBeVisible()

  await toggle.click()
  await expect(native).toHaveAttribute("data-visible", "true")
  await expect(still).toHaveCount(0)
  await expect(root.getByText("Captures: 1", { exact: true })).toBeVisible()
})

story("hides the native page under a dialog even when its still never arrives", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const native = root.getByTestId("native-Alpha")
  const toggle = root.getByRole("button", { name: "Toggle dialog", exact: true })
  await page.clock.install()
  await root.getByRole("button", { name: "Hold capture", exact: true }).click()
  await toggle.click()
  await page.clock.runFor(100)
  await expect(native).toHaveAttribute("data-visible", "true")

  // The capture never settles: after its timeout the page hides over the box background instead of the dialog.
  await page.clock.runFor(5_000)
  await expect(native).toHaveAttribute("data-visible", "false")
  await expect(root.locator("#browser-panel img")).toHaveCount(0)

  await toggle.click()
  await page.clock.runFor(100)
  await expect(native).toHaveAttribute("data-visible", "true")
})

story("comments on a picked element over a still of the page", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("combobox", { name: "Picked element scenario" }).selectOption("zoomed")
  const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
  await picker.click()
  await expect(picker).toHaveAttribute("aria-pressed", "true")
  await expect(root.getByText("Picker: on", { exact: true })).toBeVisible()
  await expect(root.getByText("Session getter reads: 0", { exact: true })).toBeVisible()
  await expect(root.getByRole("status")).toHaveText(
    "Click an element in the page to comment on it. Press Escape to cancel.",
  )

  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(picker).toHaveAttribute("aria-pressed", "false")
  const editor = root.locator('[data-slot="browser-comment-editor"] textarea')
  await expect(editor).toBeFocused()
  await expect(editor).toHaveAttribute("placeholder", "Add an optional comment...")
  await expect(editor).toHaveAccessibleName("Comment on this element")
  await expect(root.getByText("Session getter reads: 0", { exact: true })).toBeVisible()
  await expect(root.locator('[data-slot="browser-comment-editor"]')).toContainText("button.primary")
  // The spotlight frames the picked element in surface pixels.
  await expect(root.locator('[data-slot="browser-comment-spotlight"]')).toHaveCSS("left", "38.4px")
  await expect(root.locator('[data-slot="browser-comment-spotlight"]')).toHaveCSS("width", "128px")
  // The native page hides behind its still so the editor can float over it.
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
  await expect(root.locator("#browser-panel img")).toBeVisible()

  await editor.fill("Make this the primary colour")
  await root.getByRole("button", { name: "Add element comment", exact: true }).click()
  await expect(root.getByTestId("fixture-comments")).toHaveText("button.primary @e7: Make this the primary colour")
  await expect(root.locator('[data-component="browser-comment"]')).toHaveCount(0)
  await expect(root.getByText("Highlights: clear", { exact: true })).toBeVisible()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(root.getByTestId("fixture-comment-metadata")).toContainText('selector \\"main > button.primary\\"')
  await expect(root.getByTestId("fixture-comment-metadata")).toContainText("accessible name")

  // The pane stays mounted when Beta is routed, and its picker then listens to Beta's page.
  await root.getByRole("button", { name: "Beta", exact: true }).click()
  await expect(root.getByTestId("native-Beta")).toHaveAttribute("data-visible", "true")
  await picker.click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(editor).toBeFocused()
  await editor.fill("Beta's button too")
  await editor.press("Enter")
  await expect(root.getByTestId("fixture-comments").getByRole("listitem")).toHaveText([
    "button.primary @e7: Make this the primary colour",
    "button.primary @e7: Beta's button too",
  ])
})

story("cancels the picker and a comment with Escape", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
  await picker.click()
  await expect(picker).toHaveAttribute("aria-pressed", "true")
  await page.keyboard.press("Escape")
  await expect(picker).toHaveAttribute("aria-pressed", "false")
  await expect(root.getByText("Picker: off", { exact: true })).toBeVisible()

  await picker.click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  const editor = root.locator('[data-slot="browser-comment-editor"] textarea')
  await expect(editor).toBeFocused()
  await editor.press("Escape")
  await expect(root.locator('[data-component="browser-comment"]')).toHaveCount(0)
  await expect(root.getByTestId("fixture-comments")).toHaveText("")
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
})

story("leaves the picker off and Escape to the app when main never answers", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
  await root.getByRole("button", { name: "Drop picker requests", exact: true }).click()
  await picker.click()
  await expect(picker).toHaveAttribute("aria-pressed", "false")

  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.click()
  await expect(address).toBeFocused()
  await address.press("Escape")
  await expect(address).not.toBeFocused()
})

story("replaces an open page note with the comment for an element picked meanwhile", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.locator('[data-component="browser-address"]').hover()
  await root.getByRole("button", { name: "Add page to session", exact: true }).click()
  await expect(root.locator('[data-slot="browser-comment-editor"] textarea')).toBeFocused()

  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(root.locator('[data-component="browser-comment"]')).toHaveCount(1)
  await expect(root.locator('[data-slot="browser-comment-editor"]')).toContainText("button.primary")
})

story("cleans up the originating session's picker and comment after a session switch", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
  await picker.click()
  await expect(root.getByText("Picker Alpha: on", { exact: true })).toBeVisible()
  await root.getByRole("button", { name: "Beta", exact: true }).click()
  await expect(root.getByTestId("native-Beta")).toHaveAttribute("data-visible", "true")
  await expect(root.getByText("Picker Alpha: off", { exact: true })).toBeVisible()
  await expect(picker).toHaveAttribute("aria-pressed", "false")

  await root.getByRole("button", { name: "Alpha", exact: true }).click()
  await picker.click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(root.locator('[data-slot="browser-comment-editor"] textarea')).toBeFocused()
  await root.getByRole("button", { name: "Beta", exact: true }).click()
  await expect(root.locator('[data-component="browser-comment"]')).toHaveCount(0)
  await expect(root.getByText("Highlights: clear", { exact: true })).toBeVisible()
  await expect(root.getByText("Highlight owners: Alpha", { exact: true })).toBeVisible()
})

story("ends the native picker and highlight when the pane unmounts", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
  await picker.click()
  await expect(root.getByText("Picker: on", { exact: true })).toBeVisible()
  await root.getByRole("button", { name: "Unmount pane", exact: true }).click()
  await expect(root.locator("#browser-panel")).toHaveCount(0)
  await expect(root.getByText("Picker: off", { exact: true })).toBeVisible()

  await root.getByRole("button", { name: "Alpha", exact: true }).click()
  await picker.click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(root.locator('[data-slot="browser-comment-editor"] textarea')).toBeFocused()
  await root.getByRole("button", { name: "Unmount pane", exact: true }).click()
  await expect(root.locator("#browser-panel")).toHaveCount(0)
  await expect(root.getByText("Highlights: clear", { exact: true })).toBeVisible()
})

story("keeps a comment draft but drops its ref when the page navigates", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Select an element to comment on", exact: true }).click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  const editor = root.locator('[data-slot="browser-comment-editor"] textarea')
  await editor.fill("Still misaligned after the reload")
  await root.getByRole("button", { name: "Load current page", exact: true }).click()
  await expect(editor).toHaveValue("Still misaligned after the reload")
  await editor.press("Enter")
  await expect(root.getByTestId("fixture-comments")).toHaveText(
    "button.primary (no ref): Still misaligned after the reload",
  )
})

story("grows the compact comment upward without flipping and keeps its actions inside the page", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("combobox", { name: "Picked element scenario" }).selectOption("low")
  await root.getByRole("button", { name: "Select an element to comment on", exact: true }).click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  const editor = root.locator('[data-slot="browser-comment-editor"]')
  const field = editor.locator("textarea")
  await expect(editor).toHaveCSS("width", "320px")
  await expect(field).toBeFocused()
  await page.evaluate(() => document.fonts.ready.then(() => undefined))
  const opening = await editor.boundingBox()

  if (!opening) throw new Error("Missing opening annotation")
  await field.fill("A line of feedback\n".repeat(6))
  await expect(field).toHaveCSS("height", "140px")
  // The field lays out before ResizeObserver repositions its parent.
  await expect
    .poll(async () => {
      const grown = await editor.boundingBox()

      return !!grown && grown.y < opening.y && Math.abs(grown.y + grown.height - opening.y - opening.height) < 0.5
    })
    .toBe(true)
  await field.fill("A line of feedback\n".repeat(30))
  await expect(field).toHaveCSS("height", "160px")
  expect(await field.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true)
  await expect
    .poll(async () => {
      const surface = await root.locator('[data-component="browser-comment"]').boundingBox()
      const box = await editor.boundingBox()

      return !!surface && !!box && box.y >= surface.y && box.y + box.height <= surface.y + surface.height
    })
    .toBe(true)
  const submit = editor.getByRole("button", { name: "Add element comment", exact: true })
  await submit.scrollIntoViewIfNeeded()
  await expect(submit).toBeInViewport()
  // Near the surface's top, growth stops at the opening bottom rather than pushing the card down.
  await field.press("Escape")
  await root.getByRole("combobox", { name: "Picked element scenario" }).selectOption("default")
  await root.getByRole("button", { name: "Select an element to comment on", exact: true }).click()
  await root.getByRole("button", { name: "Pick element", exact: true }).click()
  await expect(field).toBeFocused()
  const nearTop = await editor.boundingBox()

  if (!nearTop) throw new Error("Missing near-top annotation")
  await field.fill("A line of feedback\n".repeat(30))
  await expect
    .poll(() => field.evaluate((element) => element.clientHeight < 160 && element.scrollHeight > element.clientHeight))
    .toBe(true)
  await expect
    .poll(async () => {
      const box = await editor.boundingBox()

      return !!box && Math.abs(box.y + box.height - nearTop.y - nearTop.height) < 0.5
    })
    .toBe(true)
  // A narrow, short pane still keeps the compact editor and action within its surface.
  await root.locator("#browser-panel").evaluate((element) => {
    const container = element.parentElement

    if (!container) throw new Error("Missing pane container")
    container.style.width = "280px"
    container.style.height = "150px"
  })
  await expect(editor).toHaveCSS("width", "262px")
  await expect
    .poll(async () => {
      const surface = await root.locator('[data-component="browser-comment"]').boundingBox()
      const box = await editor.boundingBox()

      return !!surface && !!box && box.y >= surface.y && box.y + box.height <= surface.y + surface.height
    })
    .toBe(true)
  await expect(submit).toBeInViewport()
})

story(
  "wraps the selector, attaches elements without comments, and cancels only empty drafts on clickaway",
  async ({ page }) => {
    const root = page.getByTestId("browser-pane-fixture")
    const scenario = root.getByRole("combobox", { name: "Picked element scenario" })
    const picker = root.getByRole("button", { name: "Select an element to comment on", exact: true })
    const editor = root.locator('[data-slot="browser-comment-editor"]')
    const overlay = root.locator('[data-component="browser-comment"]')
    await scenario.selectOption("long")
    await root.evaluate((element) => element.setAttribute("dir", "rtl"))
    await picker.click()
    await root.getByRole("button", { name: "Pick element", exact: true }).click()
    const preview = editor.locator('[data-slot="line-comment-v2-preview"] [dir="ltr"]')
    const selector = `main > section[data-label="إعدادات"] > ${".nested-selector".repeat(24)} > button.primary`
    await expect(preview).toHaveText(selector)
    await expect(preview).toHaveAttribute("dir", "ltr")
    await expect(preview).toHaveCSS("unicode-bidi", "isolate")
    await expect(preview).toHaveCSS("-webkit-line-clamp", "2")
    await expect(preview).toHaveCSS("height", "40px")
    await preview.hover()
    await expect(page.getByRole("tooltip")).toHaveText(selector)
    await editor.locator("textarea").press("Enter")
    await expect(overlay).toHaveCount(0)
    const comments = root.getByTestId("fixture-comments").getByRole("listitem")
    await expect(comments).toHaveText(["button.primary @e7:"])

    await scenario.selectOption("default")
    await picker.click()
    await root.getByRole("button", { name: "Pick element", exact: true }).click()
    await editor.getByRole("button", { name: "Add element", exact: true }).click()
    await expect(overlay).toHaveCount(0)
    await expect(comments).toHaveText(["button.primary @e7:", "button.primary @e7:"])
    expect(JSON.parse(await root.getByTestId("fixture-comment-metadata").innerText())).toEqual(
      [selector, "main > button.primary"].map((value) =>
        expect.objectContaining({
          type: "note",
          label: "button.primary",
          title: value,
          subtitle: "Browser annotation",
          comment: "",
          subject: `the "button.primary" element in browser tab tab_11111111-1111-1111-1111-111111111111 at https://alpha.example/ (role button; accessible name "Save changes"; selector ${JSON.stringify(value)})`,
          live: {
            href: "tab_11111111-1111-1111-1111-111111111111#e7",
            subject: `the "button.primary" element in browser tab tab_11111111-1111-1111-1111-111111111111 at https://alpha.example/ (role button; accessible name "Save changes"; selector ${JSON.stringify(value)}; browser ref @e7, usable as ref in any browser tool including browser.evaluate until the page navigates)`,
          },
        }),
      ),
    )

    await scenario.selectOption("fallback")
    await picker.click()
    await root.getByRole("button", { name: "Pick element", exact: true }).click()
    await expect(preview).toHaveText("button.primary")
    await editor.locator("textarea").fill("Keep this feedback")
    await overlay.click({ position: { x: 620, y: 290 } })
    await expect(editor.locator("textarea")).toHaveValue("Keep this feedback")
    await editor.locator("textarea").fill("")
    await overlay.click({ position: { x: 620, y: 290 } })
    await expect(overlay).toHaveCount(0)
    await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
    await expect(comments).toHaveText(["button.primary @e7:", "button.primary @e7:"])
  },
)

story("shows the empty state over a blank native page and restores navigation", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Blank page", exact: true }).click()
  await expect(root.getByText("Open a page", { exact: true })).toBeVisible()
  await expect(root.getByText("Preview your app, read docs, or browse the web.", { exact: true })).toBeVisible()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
  await expect(root.getByRole("button", { name: "Reload", exact: true })).toBeDisabled()

  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("https://example.com/")
  await expect(root.getByText("Open a page", { exact: true })).toBeVisible()
  await address.press("Enter")
  await expect(address).not.toBeFocused()
  await expect(root.getByText("Open a page", { exact: true })).toBeHidden()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(root.getByRole("button", { name: "Reload", exact: true })).toBeEnabled()
})

story("keeps Stop available and hides the empty state while a blank page loads", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Loading page", exact: true }).click()
  await expect(root.getByText("Open a page", { exact: true })).toBeHidden()
  await root.getByRole("button", { name: "Stop", exact: true }).click()
  await expect(root.getByText("Open a page", { exact: true })).toBeVisible()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
})

story("keeps the submitted URL visible until the browser reports navigation", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Blank page", exact: true }).click()
  await root.getByRole("button", { name: "Delay navigation", exact: true }).click()
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("https://example.com/")
  await address.press("Enter")
  await expect(address).not.toBeFocused()
  await expect(address).toHaveValue("https://example.com/")
  await expect(root.getByText("Open a page", { exact: true })).toBeHidden()

  // Focusing and leaving the field before the browser reports keeps the submitted address.
  await address.focus()
  await expect(address).toHaveValue("https://example.com/")
  await address.press("Escape")
  await expect(address).not.toBeFocused()
  await expect(address).toHaveValue("https://example.com/")
  await expect(root.getByText("Open a page", { exact: true })).toBeHidden()

  await root.getByRole("button", { name: "Complete navigation", exact: true }).click()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(address).toHaveValue("https://example.com/")

  await address.fill("https://unsubmitted.example/")
  await root.getByRole("button", { name: "Delay navigation", exact: true }).click()
  await expect(address).toHaveValue("https://example.com/")
})

story("restores the current URL each time the same submitted navigation is blocked", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Delay navigation", exact: true }).click()
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })

  for (const submission of [1, 2]) {
    await story.step(`blocked submission ${submission}`, async () => {
      await address.fill("https://blocked.example/")
      await address.press("Enter")
      await expect(address).toHaveValue("https://blocked.example/")
      await root.getByRole("button", { name: "Block navigation", exact: true }).click()
      await expect(root.getByRole("alert")).toHaveText("Only web pages can open here.")
      await expect(address).toHaveValue("https://alpha.example/")
      await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
    })
  }
})

story("explains why a typed address cannot open instead of searching for it", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })

  for (const [typed, reason] of [
    ["ftp://example.com", "Only web pages can open here."],
    ["https://user:pass@example.com", "Addresses with a user name or password can't open here."],
  ]) {
    await address.fill(typed)
    await address.press("Enter")
    await expect(root.getByRole("alert")).toHaveText(reason)
    await expect(address).toHaveValue("https://alpha.example/")
  }
})

story("shows a themed failure state for only the failed tab and allows retry", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Failed page", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeVisible()
  await expect(root.getByText("Check the URL and your connection, then try again.", { exact: true })).toBeVisible()
  await expect(root.getByText("Request failed", { exact: true })).toBeHidden()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
  await expect(root.getByRole("combobox", { name: "Browser address", exact: true })).toHaveValue(
    "https://alpha.example/",
  )

  await root.getByRole("button", { name: "Beta", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeHidden()
  await expect(root.getByTestId("native-Beta")).toHaveAttribute("data-visible", "true")
  await root.getByRole("button", { name: "Alpha", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeVisible()

  await root.getByRole("button", { name: "Reload", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeHidden()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
})

story("returns a failed tab to the empty state when an empty URL is submitted", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Failed page", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeVisible()
  await root.getByRole("button", { name: "Delay navigation", exact: true }).click()

  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("")
  await address.press("Enter")
  await expect(address).not.toBeFocused()
  await expect(address).toHaveValue("")
  await root.getByRole("button", { name: "Load current page", exact: true }).click()
  await expect(root.getByRole("button", { name: "Stop", exact: true })).toBeEnabled()
  await root.getByRole("button", { name: "Complete navigation", exact: true }).click()
  await expect(root.getByText("URL can't be reached", { exact: true })).toBeHidden()
  await expect(root.getByText("Open a page", { exact: true })).toBeVisible()
  await expect(root.getByRole("button", { name: "Reload", exact: true })).toBeDisabled()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "false")
})

story("selects the full URL when the address field gains focus", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.click()
  await expect(address).toHaveJSProperty("selectionStart", 0)
  await expect(address).toHaveJSProperty("selectionEnd", "https://alpha.example/".length)
  await address.pressSequentially("https://example.com/")
  await expect(address).toHaveValue("https://example.com/")
  await address.press("Enter")
  await expect(address).not.toBeFocused()
  await address.focus()
  await expect(address).toHaveJSProperty("selectionStart", 0)
  await expect(address).toHaveJSProperty("selectionEnd", "https://example.com/".length)
  // A click on the focused field places the caret instead of selecting the URL again.
  await address.press("ArrowRight")
  await address.click()
  await expect
    .poll(() => address.evaluate((input: HTMLInputElement) => input.selectionStart === input.selectionEnd))
    .toBe(true)
})

story("draws only the address at rest when focus leaves the window with the field selected", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.click()
  // Chromium blurs the field when the window or the native page takes focus, and the field keeps its full selection.
  await address.evaluate((input: HTMLInputElement) => {
    input.dispatchEvent(new FocusEvent("blur"))
    input.select()
  })
  await expect(address).toHaveJSProperty("selectionStart", 0)
  await expect(address).toHaveJSProperty("selectionEnd", "https://alpha.example/".length)
  await page.mouse.move(0, 0)

  // Computed ::selection styles do not report the default highlight, so compare the input's own paint with the
  // selection collapsed. The drawn address is hidden: a selection layer can change how its text is antialiased.
  await page.addStyleTag({ content: '[data-slot="browser-address-display"] { visibility: hidden }' })
  const field = root.locator('[data-component="browser-address"]')
  const selected = (await field.screenshot()).toString("base64")
  await address.evaluate((input: HTMLInputElement) => input.setSelectionRange(0, 0))
  const collapsed = (await field.screenshot()).toString("base64")

  const changed = await page.evaluate(
    async (shots) => {
      const pixels = await Promise.all(
        shots.map(async (data) => {
          const image = new Image()
          image.src = `data:image/png;base64,${data}`
          await image.decode()
          const canvas = new OffscreenCanvas(image.width, image.height)
          canvas.getContext("2d")?.drawImage(image, 0, 0)

          return canvas.getContext("2d")?.getImageData(0, 0, image.width, image.height).data ?? new Uint8ClampedArray()
        }),
      )

      return Array.from({ length: (pixels[0]?.length ?? 0) / 4 }, (_, index) => index * 4).filter((offset) =>
        [0, 1, 2].some((channel) => pixels[0]?.[offset + channel] !== pixels[1]?.[offset + channel]),
      ).length
    },
    [selected, collapsed],
  )

  expect(changed).toBe(0)
})

story("keeps the current page visible while a submitted URL loads", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Delay navigation", exact: true }).click()
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("https://example.com/")
  await address.press("Enter")
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(address).toHaveValue("https://example.com/")

  await root.getByRole("button", { name: "Load current page", exact: true }).click()
  await expect(root.getByRole("button", { name: "Stop", exact: true })).toBeEnabled()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await root.getByRole("button", { name: "Complete navigation", exact: true }).click()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(address).toHaveValue("https://example.com/")
})

story("keeps the current page and restores its URL when an empty address is submitted", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.fill("")
  await address.press("Enter")
  await expect(address).not.toBeFocused()
  await expect(address).toHaveValue("https://alpha.example/")
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  await expect(root.getByText("Open a page", { exact: true })).toBeHidden()
  await expect(root.getByRole("button", { name: "Reload", exact: true })).toBeEnabled()
})

story("suggests visited pages and opens the first one that completes the typed address", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Seed history", exact: true }).click()
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await address.click()
  await address.pressSequentially("localhost")
  const options = page.getByRole("listbox", { name: "Suggestions" }).getByRole("option")
  await expect(options).toHaveCount(1)
  await expect(options.first()).toHaveAttribute("aria-selected", "true")
  // Arrow keys that pick IME candidates leave the suggestions alone.
  await address.dispatchEvent("keydown", { key: "ArrowDown", isComposing: true })
  await expect(options.first()).toHaveAttribute("aria-selected", "true")
  await address.press("Enter")
  await expect(address).toHaveValue("http://localhost:5173/settings")

  // A search query also opens the first match; stepping past the list leaves the typed text, which searches.
  await address.click()
  await address.pressSequentially("color format")
  await expect(options).toHaveCount(1)
  await expect(options.first()).toHaveAttribute("aria-selected", "true")
  await address.press("ArrowDown")
  await expect(options.first()).toHaveAttribute("aria-selected", "false")
  await address.press("Enter")
  await expect(address).toHaveValue("https://www.google.com/search?q=color%20format")
})

story("lists recent pages on a blank tab and opens one", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const address = root.getByRole("combobox", { name: "Browser address", exact: true })
  await root.getByRole("button", { name: "Seed history", exact: true }).click()
  await root.getByRole("button", { name: "Blank page", exact: true }).click()
  await expect(root.getByText("Recent", { exact: true })).toBeVisible()
  // The globe at the field's start belongs to the field: a click there focuses it.
  await expect(address).not.toBeFocused()
  await root.locator('[data-component="browser-address"]').click({ position: { x: 16, y: 14 } })
  await expect(address).toBeFocused()
  await address.blur()

  await root.getByRole("button", { name: /^Settings - Preview/ }).click()
  await expect(address).toHaveValue("http://localhost:5173/settings")
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")
  // The opened page owns the field afterwards: an edit that is never submitted shows its URL again.
  await address.click()
  await address.fill("unsubmitted")
  await address.blur()
  await expect(address).toHaveValue("http://localhost:5173/settings")
})

story("adds the whole page to the session and keeps the draft when the button is pressed again", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const field = root.locator('[data-component="browser-address"]')
  const add = root.getByRole("button", { name: "Add page to session", exact: true })
  await field.hover()
  await add.click()
  const editor = root.locator('[data-slot="browser-comment-editor"] textarea')
  await editor.fill("Check the header layout")
  await field.hover()
  await add.click()
  await expect(editor).toHaveValue("Check the header layout")
  await editor.press("Enter")
  await expect(root.getByTestId("fixture-comments").getByRole("listitem")).toHaveText([/: Check the header layout$/])
})

story("shows the page's cookies in the site information and clears them", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Site information", exact: true }).click()
  const site = page.getByRole("dialog")
  await expect(site).toContainText("3 cookies in use")
  await site.getByRole("button", { name: "Clear", exact: true }).click()
  await expect(site).toContainText("0 cookies in use")
})

story("says a failed cookie read failed and reads again on retry", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  await root.getByRole("button", { name: "Fail next site read", exact: true }).click()
  await root.getByRole("button", { name: "Site information", exact: true }).click()
  const site = page.getByRole("dialog")
  await expect(site).toContainText("Request failed")
  const retry = site.getByRole("button", { name: "Try again", exact: true })
  await expect(retry).toBeEnabled()
  await retry.click()
  await expect(site).toContainText("3 cookies in use")
  await expect(site.getByRole("button", { name: "Clear", exact: true })).toBeEnabled()
})

story("zooms the page from the browser options in view and keeps its zoom in the address field", async ({ page }) => {
  const root = page.getByTestId("browser-pane-fixture")
  const options = root.getByRole("button", { name: "Browser options", exact: true })
  await options.click()
  await page.getByRole("menuitem", { name: /^Zoom in/ }).click()
  await expect(root.getByText("Zoom: 110", { exact: true })).toBeVisible()
  // The menu covered the page with a still; it closes so the zoomed page shows at once.
  await expect(page.getByRole("menu")).toBeHidden()
  await expect(root.getByTestId("native-Alpha")).toHaveAttribute("data-visible", "true")

  const zoom = root.getByRole("button", { name: "110%", exact: true })
  await expect(zoom).toBeVisible()
  await zoom.click()
  await expect(root.getByText("Zoom: 100", { exact: true })).toBeVisible()
  await expect(zoom).toBeHidden()
})