Ctrl K
ComunidadGuías…

tests/e2e/flows-inspector-layout-inspector.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import {
  LONG_MODEL_ID,
  setupWorkflowsPage,
  openAgentInspector,
  findOverlappingCells,
  gridTrackCounts,
  parseCssAlpha,
} from "./helpers/flows-inspector-layout-fixtures";

test("inspector leads with the model selector", async ({ page }) => {
  await setupWorkflowsPage(page);
  await page.setViewportSize({ width: 2048, height: 1152 });
  await openAgentInspector(page);

  const modelBox = await page
    .getByLabel("Model", { exact: true })
    .boundingBox();
  const harnessBox = await page
    .getByRole("radiogroup", { name: "Harness" })
    .boundingBox();
  const taskBox = await page.getByLabel("Agent task").boundingBox();
  const promptBox = await page
    .getByLabel("System prompt override")
    .boundingBox();

  expect(modelBox).not.toBeNull();
  expect(harnessBox).not.toBeNull();
  expect(taskBox).not.toBeNull();
  expect(promptBox).not.toBeNull();
  expect(modelBox!.y).toBeLessThan(harnessBox!.y);
  expect(modelBox!.y).toBeLessThan(taskBox!.y);
  expect(modelBox!.y).toBeLessThan(promptBox!.y);
});

test("inspector grids never overlap their own cells", async ({ page }) => {
  await setupWorkflowsPage(page);

  // Docked (wide viewport, narrow panel) is where the viewport-based breakpoint
  // used to force two columns into a ~300px panel.
  await page.setViewportSize({ width: 2048, height: 1152 });
  await openAgentInspector(page);
  const sheet = page.getByTestId("flows-right-sheet");
  await expect(sheet).toContainText(LONG_MODEL_ID);
  expect(await findOverlappingCells(sheet)).toEqual([]);
  const dockedWidth = (await sheet.boundingBox())!.width;

  // Overlay mode, where the panel is wide enough for two columns. Wait for the
  // panel to actually widen rather than for a fixed delay -- the resize lands
  // via a ResizeObserver, so any timeout is a guess.
  await page.setViewportSize({ width: 1100, height: 800 });
  await expect
    .poll(async () => (await sheet.boundingBox())!.width)
    .toBeGreaterThan(dockedWidth);
  expect(await findOverlappingCells(sheet)).toEqual([]);

  const overflowing = await sheet.evaluate((root) => {
    const bounds = root.getBoundingClientRect();
    return Array.from(root.querySelectorAll<HTMLElement>("*")).filter(
      (el) => el.getBoundingClientRect().right > bounds.right + 1
    ).length;
  });
  expect(overflowing).toBe(0);
});

test("inspector grids size to the panel, not the viewport", async ({
  page,
}) => {
  await setupWorkflowsPage(page);

  // The bug this pins: a viewport breakpoint (`sm:`) forced two columns into the
  // ~300px docked panel on a wide screen. `min-w-0` + `break-words` stop the
  // cells from visibly intersecting, so the overlap test alone cannot catch a
  // regression here -- assert the resolved column count instead.
  await page.setViewportSize({ width: 2048, height: 1152 });
  await openAgentInspector(page);
  const sheet = page.getByTestId("flows-right-sheet");
  await expect(sheet).toContainText(LONG_MODEL_ID);

  const docked = await gridTrackCounts(sheet);
  expect(docked.length).toBeGreaterThan(0);
  expect(docked.filter((grid) => grid.tracks > 1)).toEqual([]);

  // The same grids do take two columns once the panel itself is wide enough,
  // which is what proves they are reading the container and not just never
  // matching the breakpoint.
  await page.setViewportSize({ width: 1100, height: 800 });
  await expect
    .poll(async () => (await gridTrackCounts(sheet)).some((g) => g.tracks > 1))
    .toBe(true);
});

test("inspector header stays put while the body scrolls under it", async ({
  page,
}) => {
  await setupWorkflowsPage(page);
  await page.setViewportSize({ width: 2048, height: 1152 });
  await openAgentInspector(page);

  const close = page.getByTestId("flows-inspector-close");
  const scroller = page.getByTestId("flows-inspector-scroll");
  const headerBefore = await close.boundingBox();

  // Scroll to the end rather than a fixed 400px, which would fail for the
  // unrelated reason of the inspector's content getting shorter.
  const scrolled = await scroller.evaluate((el) => {
    el.scrollTop = el.scrollHeight;
    return { top: el.scrollTop, overflow: el.scrollHeight - el.clientHeight };
  });
  expect(scrolled.overflow).toBeGreaterThan(0);
  expect(scrolled.top).toBeGreaterThan(0);

  // The header does not move, and nothing scrolls into view above it.
  await expect
    .poll(async () => (await close.boundingBox())!.y)
    .toBeCloseTo(headerBefore!.y, 0);

  // The scroll viewport starts below the header, so scrolled content is clipped
  // instead of sliding through it, and the header is fully opaque.
  const chrome = await page.evaluate(() => {
    const el = (testId: string) =>
      document.querySelector<HTMLElement>(`[data-testid="${testId}"]`);
    const scrollBody = el("flows-inspector-scroll");
    const head = el("flows-inspector-header");
    if (!scrollBody || !head) return null;
    return {
      headerBottom: head.getBoundingClientRect().bottom,
      scrollerTop: scrollBody.getBoundingClientRect().top,
      headerBackground: getComputedStyle(head).backgroundColor,
      scrollerOverflowY: getComputedStyle(scrollBody).overflowY,
    };
  });
  expect(chrome).not.toBeNull();
  expect(chrome!.headerBottom).toBeLessThanOrEqual(chrome!.scrollerTop + 1);
  expect(chrome!.scrollerOverflowY).toBe("auto");
  // A translucent header would let scrolled content show through it. Read the
  // alpha channel directly instead of pattern-matching the colour syntax.
  expect(parseCssAlpha(chrome!.headerBackground)).toBe(1);
});

test("inspector empty state stays reachable on a short viewport", async ({
  page,
}) => {
  await setupWorkflowsPage(page);
  // Short enough that the icon, copy and node/connection counts cannot all fit.
  // Width must be >= 1520 so the flows container (minus ~240px sidebar) exceeds
  // the 1280px dock-mode threshold.
  await page.setViewportSize({ width: 1600, height: 380 });
  await page.goto("/alex/workflows");
  await page.waitForLoadState("networkidle");

  const inspector = page.locator(".flows-inspector");
  await expect(inspector).toContainText("Select a node");

  const reach = await inspector.evaluate((root) => {
    const card = root.querySelector<HTMLElement>(
      '[data-testid="flows-inspector-empty"]'
    );
    const body = root.querySelector<HTMLElement>(
      '[data-testid="flows-inspector-empty-body"]'
    );
    if (!body || !card) return null;
    body.scrollTop = 0;
    return {
      // The card must not overflow itself. It has no scroller of its own and
      // sits inside an overflow-hidden aside, so anything past its client box
      // is clipped with no way back to it.
      cardOverflow: card.scrollHeight - card.clientHeight,
      bodyScrolls: body.scrollHeight > body.clientHeight,
      firstChildTop: body.firstElementChild!.getBoundingClientRect().top,
      bodyTop: body.getBoundingClientRect().top,
    };
  });

  expect(reach).not.toBeNull();
  expect(reach!.cardOverflow).toBeLessThanOrEqual(1);
  // The overflow has to land on a real scroller rather than just disappearing.
  expect(reach!.bodyScrolls).toBe(true);
  // Scrolled to the very top, nothing may sit above the container's top edge --
  // centred overflow makes the start of the content permanently unreachable.
  expect(reach!.firstChildTop).toBeGreaterThanOrEqual(reach!.bodyTop - 1);
});

test("inspector empty panel minimizes and restores", async ({ page }) => {
  await setupWorkflowsPage(page);
  // Width must be >= 1520 so the flows container (minus ~240px sidebar) exceeds
  // the 1280px dock-mode threshold where minimize/expand controls appear.
  await page.setViewportSize({ width: 1600, height: 900 });
  await page.goto("/alex/workflows");
  await page.waitForLoadState("networkidle");

  const inspector = page.locator(".flows-inspector");
  await expect(inspector).toContainText("Workflow configuration");

  await page.getByRole("button", { name: "Minimize inspector" }).click();
  await expect(inspector).toBeHidden();

  await page.getByRole("button", { name: "Expand inspector" }).click();
  await expect(inspector).toContainText("Workflow configuration");

  // Selecting a node still opens the full inspector while minimized.
  await page.getByRole("button", { name: "Minimize inspector" }).click();
  await expect(inspector).toBeHidden();
  await page.locator('.react-flow__node[data-id="agent-1"]').click();
  await expect(page.getByLabel("Model", { exact: true })).toBeVisible();
});