Ctrl K
ComunidadGuías…

tests/e2e/marketing-run-layout.spec.ts

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

Archivo original ↗Preguntar a KiBot
import { expect, test } from "@playwright/test";
import { textContrast } from "./helpers/text-contrast";

for (const theme of ["light", "dark"] as const) {
  for (const width of [1440, 768, 390, 320]) {
    test(`run explanation stays readable and navigable in ${theme} at ${width}px`, async ({
      page,
    }, testInfo) => {
      await page.setViewportSize({ width, height: 1000 });
      await page.emulateMedia({ colorScheme: theme, reducedMotion: "reduce" });
      await page.addInitScript(
        (value) => localStorage.setItem("theme", value),
        theme
      );
      await page.goto("/how-it-works");
      await expect(page.locator("html")).toHaveClass(new RegExp(theme));

      // Compare painted frame pixels with the uncovered header segment. DOM
      // visibility alone misses an opaque content surface covering the frame.
      const guide = await page.locator(".run-guide").boundingBox();
      for (const side of ["left", "right"]) {
        const frame = await page.locator(`.mpx-frame.is-${side}`).boundingBox();
        const pixel = (x: number, y: number) =>
          page.screenshot({ clip: { x, y, width: 1, height: 1 } });
        const reference = await pixel(frame!.x, 32);
        const besideFrame = await pixel(frame!.x - 2, 32);
        expect(reference.equals(besideFrame)).toBe(false);
        expect(
          (await pixel(frame!.x, guide!.y + 32)).equals(reference),
          `${side} frame stays painted beside the reading surface`
        ).toBe(true);
      }
      if (width > 900) {
        const grid = await page.locator(".mpx-quarter").first().boundingBox();
        const strip = (x: number) =>
          page.screenshot({
            clip: { x, y: guide!.y + 32, width: 2, height: 16 },
          });
        expect(
          (await strip(Math.floor(grid!.x))).equals(
            await strip(Math.floor(grid!.x) + 4)
          ),
          "interior grid stays hidden behind the reading surface"
        ).toBe(true);
      } else {
        await expect(page.locator(".mpx-quarter").first()).toBeHidden();
      }

      const stages = page.locator(".stage");
      await expect(stages).toHaveCount(6);
      for (const stage of await stages.all()) {
        const heading = await stage.locator("h2").boundingBox();
        const description = await stage.locator(".stage-desc").boundingBox();
        expect(heading).not.toBeNull();
        expect(description).not.toBeNull();
        expect(Math.abs(heading!.x - description!.x)).toBeLessThanOrEqual(1);
        expect(description!.y).toBeGreaterThanOrEqual(
          heading!.y + heading!.height
        );
        for (const copy of await stage
          .locator(".stage-desc, .stage-fact")
          .all()) {
          expect(await textContrast(copy)).toBeGreaterThanOrEqual(4.5);
        }
      }

      const overview = page.getByRole("navigation", { name: "Run overview" });
      const links = overview.getByRole("link");
      await expect(links).toHaveCount(6);
      await links.first().hover();
      await links.first().evaluate(async (link) => {
        await Promise.all(
          link.getAnimations().map((animation) => animation.finished)
        );
      });
      const surfaces = await links.first().evaluate((link) => {
        const canvas = document.createElement("canvas");
        canvas.width = 1;
        canvas.height = 1;
        const context = canvas.getContext("2d")!;
        return [link.closest(".run-guide")!, link].map((element) => {
          context.clearRect(0, 0, 1, 1);
          const ancestors: Element[] = [];
          for (
            let node: Element | null = element;
            node;
            node = node.parentElement
          )
            ancestors.unshift(node);
          for (const node of ancestors) {
            context.fillStyle = getComputedStyle(node).backgroundColor;
            context.fillRect(0, 0, 1, 1);
          }
          return Array.from(context.getImageData(0, 0, 1, 1).data);
        });
      });
      expect(surfaces[1]).not.toEqual(surfaces[0]);
      // Keyboard users can jump from the overview to every stage. The target
      // scroll margin should leave clear space above its visible heading.
      for (const link of await links.all()) {
        const href = await link.getAttribute("href");
        expect(href).toMatch(/^#run-/);
        await link.focus();
        await link.press("Enter");
        const heading = page.locator(`${href} h2`);
        await expect(heading).toBeInViewport();
        expect((await heading.boundingBox())!.y).toBeGreaterThanOrEqual(72);
      }

      expect(
        await page.evaluate(() => document.documentElement.scrollWidth)
      ).toBeLessThanOrEqual(width);
      await page.goto("/how-it-works");
      await page.screenshot({ path: testInfo.outputPath("run-overview.png") });
    });
  }
}

for (const theme of ["light", "dark"] as const) {
  test(`frame markers remain fully painted in ${theme}`, async ({ page }) => {
    await page.setViewportSize({ width: 1440, height: 1000 });
    await page.emulateMedia({ colorScheme: theme, reducedMotion: "reduce" });
    await page.addInitScript(
      (value) => localStorage.setItem("theme", value),
      theme
    );
    await page.goto("/how-it-works");
    await expect(page.locator("html")).toHaveClass(new RegExp(theme));
    // Match the backdrop on either side of the frame so these comparisons
    // isolate occlusion of the marker, rather than the surface color change.
    await page.addStyleTag({
      content: ".mpx-landing { background: var(--card); }",
    });
    for (const marker of [
      page.locator(".mpx-blueprint > span"),
      page.locator(".mpx-xh").first(),
    ]) {
      await expect(marker).toBeVisible();
      const covered = await marker.screenshot({ animations: "disabled" });
      const uncover = await page.addStyleTag({
        content: ".run-guide { background: transparent !important; }",
      });
      const reference = await marker.screenshot({ animations: "disabled" });
      await uncover.evaluate((element) => {
        (element as HTMLStyleElement).remove();
      });
      expect(covered.equals(reference), "frame marker is not sliced").toBe(
        true
      );
    }
  });
}

test("solid homepage dark sections remain distinct from the page", async ({
  page,
}) => {
  await page.emulateMedia({ colorScheme: "dark", reducedMotion: "reduce" });
  await page.addInitScript(() => localStorage.setItem("theme", "dark"));
  await page.goto("/");
  await expect(page.locator("html")).toHaveClass(/dark/);
  const colors = await page
    .locator(".mpx-harness-inner")
    .evaluate((section) => {
      const canvas = document.createElement("canvas");
      canvas.width = 1;
      canvas.height = 1;
      const context = canvas.getContext("2d")!;
      return [section.closest(".mpx-landing")!, section].map((element) => {
        context.clearRect(0, 0, 1, 1);
        context.fillStyle = getComputedStyle(element).backgroundColor;
        context.fillRect(0, 0, 1, 1);
        return Array.from(context.getImageData(0, 0, 1, 1).data);
      });
    });
  expect(colors[1][3]).toBe(255);
  expect(colors[1][0] - colors[0][0]).toBeGreaterThanOrEqual(8);
  for (const copy of await page
    .locator(
      ".mpx-harnesses .mpx-eyebrow, .mpx-harness-lede, .mpx-harness-tabs button[aria-selected='false'], .mpx-code-panel header > span, .mpx-code-panel .t-root, .mpx-harness-id p, .mpx-harness-desc"
    )
    .all()) {
    expect(
      await textContrast(copy),
      (await copy.textContent()) ?? undefined
    ).toBeGreaterThanOrEqual(4.5);
  }
  const tab = page.getByRole("tab", { name: "Claude Code", exact: true });
  const restingColor = await tab.evaluate(
    (element) => getComputedStyle(element).color
  );
  await tab.hover();
  await tab.evaluate(async (element) => {
    await Promise.all(
      element.getAnimations().map((animation) => animation.finished)
    );
  });
  expect(
    await tab.evaluate((element) => getComputedStyle(element).color)
  ).not.toBe(restingColor);
});