Ctrl K
ComunidadGuías…

test/scripts/screenshots-layers-browser.contract.ts

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

Archivo original ↗Preguntar a KiBot
import { Buffer } from "node:buffer";
import { expect } from "@std/expect";
import { afterAll, beforeAll, describe, it as test } from "@std/testing/bdd";
import type { Browser, Page } from "playwright";
import sharp from "sharp";
import { capturePreparedLayers } from "#scripts/screenshots/capture.ts";
import { addScreenshotStyle } from "#scripts/screenshots/style.ts";
import {
  countRgbPixels,
  launchScreenshotBrowser,
  layerStyle,
  withLayer,
  withPage,
} from "./screenshots-browser-helpers.ts";

const expectScreenshotStyle = async (
  page: Page,
  css: string,
  expectedColor: string,
): Promise<void> => {
  const removeStyle = await addScreenshotStyle(page, css);
  try {
    expect(await layerStyle(page, "text", "p", "color")).toBe(expectedColor);
  } finally {
    await removeStyle();
  }
};

describe("screenshot layer browser contracts", () => {
  let browser: Browser;

  beforeAll(async () => {
    browser = await launchScreenshotBrowser();
  });

  afterAll(async () => {
    await browser.close();
  });

  test("keeps SVG control icons in the controls layer", async () => {
    await withPage(
      browser,
      '<button style="color: rgb(0, 0, 255)"><svg style="fill: currentColor"><use href="#save"></use></svg><span>Save</span></button>',
      async (page) => {
        expect(await layerStyle(page, "controls", "use", "fill")).toBe(
          "rgb(0, 0, 255)",
        );
        expect(
          await layerStyle(page, "controls", "span", "-webkit-text-fill-color"),
        ).toBe("rgba(0, 0, 0, 0)");
      },
    );
  });

  test("keeps generated control labels out of the controls layer", async () => {
    await withPage(
      browser,
      '<style>button::after { color: rgb(255, 0, 0); content: "3"; }</style><button>Cart</button>',
      async (page) => {
        expect(
          await layerStyle(
            page,
            "controls",
            "button",
            "-webkit-text-fill-color",
            "::after",
          ),
        ).toBe("rgba(0, 0, 0, 0)");
        expect(
          await layerStyle(page, "text", "button", "color", "::after"),
        ).toBe("rgb(255, 0, 0)");
      },
    );
  });

  test("keeps selection highlights out of the controls layer", async () => {
    await withPage(
      browser,
      `<style>input::selection { background: rgb(255, 255, 0); color: rgb(0, 0, 255); }</style>
       <input value="Selected words">`,
      async (page) => {
        await page
          .locator("input")
          .evaluate((input) => (input as HTMLInputElement).select());

        expect(
          await layerStyle(
            page,
            "controls",
            "input",
            "background-color",
            "::selection",
          ),
        ).toBe("rgba(0, 0, 0, 0)");
        expect(
          await layerStyle(
            page,
            "text",
            "input",
            "background-color",
            "::selection",
          ),
        ).toBe("rgb(255, 255, 0)");
      },
    );
  });

  test("keeps nested generated control labels out of the controls layer", async () => {
    await withPage(
      browser,
      `<style>
        button span::after {
          content: "3";
          -webkit-text-fill-color: rgb(255, 0, 0);
        }
      </style><button><span>Cart</span></button>`,
      async (page) => {
        expect(
          await layerStyle(
            page,
            "controls",
            "button span",
            "-webkit-text-fill-color",
            "::after",
          ),
        ).toBe("rgba(0, 0, 0, 0)");
      },
    );
  });

  test("keeps foreignObject labels in the text layer", async () => {
    await withPage(
      browser,
      '<svg><foreignObject><div xmlns="http://www.w3.org/1999/xhtml">Label</div></foreignObject></svg>',
      async (page) => {
        expect(
          await layerStyle(page, "text", "foreignObject", "visibility"),
        ).toBe("visible");
        expect(
          await layerStyle(page, "text", "foreignObject div", "color"),
        ).not.toBe("rgba(0, 0, 0, 0)");
      },
    );
  });

  test("keeps embedded documents out of the text layer", async () => {
    await withPage(
      browser,
      '<iframe srcdoc="<p>Embedded words</p>"></iframe>',
      async (page) => {
        expect(await layerStyle(page, "text", "iframe", "visibility")).toBe(
          "hidden",
        );
        expect(
          await layerStyle(page, "background", "iframe", "visibility"),
        ).toBe("visible");
      },
    );
  });

  test("puts disabled btn spans in the controls layer", async () => {
    await withPage(
      browser,
      '<span class="btn btn--disabled">Unavailable</span>',
      async (page) => {
        expect(await layerStyle(page, "background", ".btn", "visibility")).toBe(
          "hidden",
        );
        expect(await layerStyle(page, "controls", ".btn", "visibility")).toBe(
          "visible",
        );
      },
    );
  });

  test("applies layer masks without bypassing page CSP", async () => {
    await withPage(
      browser,
      `<meta http-equiv="Content-Security-Policy" content="style-src 'self'">
       <p style="color: rgb(255, 0, 0)">Words</p>`,
      async (page) => {
        expect(await layerStyle(page, "text", "p", "color")).toBe(
          "rgb(0, 0, 0)",
        );
        expect(
          await layerStyle(page, "background", "p", "-webkit-text-fill-color"),
        ).toBe("rgba(0, 0, 0, 0)");
      },
    );
  });

  test("puts label-backed and disclosure widgets in the controls layer", async () => {
    await withPage(
      browser,
      `<label class="order-card"><input type="checkbox">Ticket</label>
       <label class="row-select"><input type="checkbox">Row</label>
       <details><summary>More</summary><p>Details</p></details>`,
      async (page) => {
        for (const selector of [".order-card", ".row-select", "summary"]) {
          expect(
            await layerStyle(page, "background", selector, "visibility"),
          ).toBe("hidden");
          expect(
            await layerStyle(page, "controls", selector, "visibility"),
          ).toBe("visible");
        }
      },
    );
  });

  test("puts combobox options in the controls layer", async () => {
    await withPage(
      browser,
      '<div role="option">Matching attendee</div>',
      async (page) => {
        expect(
          await layerStyle(page, "background", '[role="option"]', "visibility"),
        ).toBe("hidden");
        expect(
          await layerStyle(page, "controls", '[role="option"]', "visibility"),
        ).toBe("visible");
      },
    );
  });

  test("preserves native control geometry in the text layer", async () => {
    await withPage(
      browser,
      '<label><input id="check" type="checkbox"><span>Remember me</span></label>',
      async (page) => {
        const geometry = () =>
          page.locator("label").evaluate((label) => {
            const control = label.querySelector("input");
            const words = label.querySelector("span");
            if (!control || !words) throw new Error("Missing test control.");
            const controlBox = control.getBoundingClientRect();
            const wordsBox = words.getBoundingClientRect();
            return {
              control: [
                controlBox.x,
                controlBox.y,
                controlBox.width,
                controlBox.height,
              ],
              words: [wordsBox.x, wordsBox.y, wordsBox.width, wordsBox.height],
            };
          });
        const normal = await geometry();

        expect(await withLayer(page, "text", geometry)).toEqual(normal);
      },
    );
  });

  test("keeps modal backdrops out of the text layer", async () => {
    await withPage(
      browser,
      "<style>dialog::backdrop { background: rgb(1, 2, 3); }</style><dialog>Words</dialog>",
      async (page) => {
        await page
          .locator("dialog")
          .evaluate((dialog) => (dialog as HTMLDialogElement).showModal());
        expect(
          await layerStyle(
            page,
            "text",
            "dialog",
            "background-color",
            "::backdrop",
          ),
        ).toBe("rgba(0, 0, 0, 0)");
        expect(
          await layerStyle(
            page,
            "background",
            "dialog",
            "background-color",
            "::backdrop",
          ),
        ).toBe("rgb(1, 2, 3)");
      },
    );
  });

  test("keeps date picker indicator pixels out of the text layer", async () => {
    await withPage(
      browser,
      `<style>
        input { background: transparent; border: 0; color: transparent; height: 30px; width: 120px; }
        input::-webkit-calendar-picker-indicator { background: rgb(255, 0, 0); opacity: 1; }
      </style><input type="date">`,
      async (page) => {
        const redPixels = async (layer: "controls" | "text") => {
          const png = await withLayer(page, layer, () =>
            page.locator("input").screenshot({ omitBackground: true }),
          );
          return await countRgbPixels(png, [255, 0, 0]);
        };

        expect(await redPixels("controls")).toBeGreaterThan(0);
        expect(await redPixels("text")).toBe(0);
      },
    );
  });

  test("preserves imports in screenshot CSS", async () => {
    await withPage(browser, "<p>Imported</p>", async (page) => {
      await expectScreenshotStyle(
        page,
        '@import url("data:text/css,p%7Bcolor%3Argb(1%2C2%2C3)%7D");',
        "rgb(1, 2, 3)",
      );
    });
  });

  test("adds screenshot CSS on a page with a URL fragment", async () => {
    await withPage(browser, "<p>Styled</p>", async (page) => {
      await page.goto(`${page.url()}#terms`);

      await expectScreenshotStyle(
        page,
        "p { color: rgb(1, 2, 3); }",
        "rgb(1, 2, 3)",
      );
    });
  });

  test("layer masks outrank existing important cascade layers", async () => {
    await withPage(
      browser,
      `<style>@layer __screenshot_layer__; @layer components {
        p { -webkit-text-fill-color: rgb(255, 0, 0) !important; }
      }
      @layer __screenshot_layer__ {
        body#priority-test { background: rgb(255, 0, 0) !important; }
      }</style><script>document.body.id = "priority-test";</script><p>Words</p>`,
      async (page) => {
        expect(
          await layerStyle(page, "background", "p", "-webkit-text-fill-color"),
        ).toBe("rgba(0, 0, 0, 0)");
        expect(
          await layerStyle(page, "controls", "body", "background-color"),
        ).toBe("rgba(0, 0, 0, 0)");
      },
    );
  });

  test("recombines an opacity group exactly", async () => {
    await withPage(
      browser,
      `<style>
        body { background: white; margin: 0; }
        #group { background: red; color: blue; height: 40px; opacity: 0.5; width: 100px; }
      </style><div id="group">Words</div>`,
      async (page) => {
        const { layers, png: normal } = await capturePreparedLayers(page);
        const combined = await sharp(layers.background)
          .composite([
            { input: Buffer.from(layers.controls) },
            { input: Buffer.from(layers.text) },
          ])
          .ensureAlpha()
          .raw()
          .toBuffer();
        expect(combined).toEqual(
          await sharp(normal).ensureAlpha().raw().toBuffer(),
        );
      },
    );
  });
});