Ctrl K
ComunidadGuías…

scripts/testing/browser-identity.e2e.test.ts

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

Archivo original ↗Preguntar a KiBot
// agent-browser drives the Chrome OMB resolves, through the generated launcher and the identity connection, as the server
// does. Runs when agent-browser resolves (tools directory, OMB_AGENT_BROWSER_PATH or PATH) or OMB_UI_E2E=1 asks for the
// download. The Chrome is OMB_CHROME_PATH, else the one OMB picks on this machine (an installed Google Chrome), else the
// Chrome for Testing agent-browser installs into the tools directory, else an installed Google Chrome that policies manage
// (agent-browser installs nothing where it finds one, as on GitHub's Windows runners).
import { mkdtempSync } from "node:fs";
import { tmpdir, userInfo } from "node:os";
import { join } from "node:path";
import { afterAll, describe, expect, it } from "vitest";

import { resolveAgentBrowserBinary } from "../../server/browser-engine.ts";
import { connectCdp } from "../../server/browser-sites.ts";
import { ChromeLauncher } from "../../server/browser/chrome/launcher.ts";
import { chromeHost, installedGoogleChrome, isHeadlessShell } from "../../server/browser/chrome/resolve.ts";
import { IdentitySidecars, startIdentitySidecar } from "../../server/browser/chrome/sidecar.ts";
import { removeTempDir } from "../../server/testing/cleanup.ts";
import { verifyBrowserIdentity } from "../verify-browser-identity.ts";
import { expectedUserAgent } from "./browser-identity-probe.ts";
import { ensureUiBrowser, UI_TOOLS_DIR } from "./control-omb-ui.ts";

const enabled = process.env.OMB_UI_E2E === "1" || Boolean(resolveAgentBrowserBinary({ dataDir: UI_TOOLS_DIR, env: process.env }));
if (!enabled) console.log(`skipping browser identity e2e: no agent-browser binary resolves (looked in ${UI_TOOLS_DIR}, OMB_AGENT_BROWSER_PATH and PATH); set OMB_UI_E2E=1 to install the pinned release`);
const out = mkdtempSync(join(tmpdir(), "omb-identity-e2e-"));
afterAll(async () => { await removeTempDir(out); });

const home = userInfo().homedir;
const machineEnv = { ...process.env, HOME: home, USERPROFILE: home };

/** What OMB would run here for its own engine, read without starting anything. */
function chromeOmbPicks(): string | null {
  const plan = new ChromeLauncher({ dataDir: out, env: machineEnv, engine: () => ({ managed: true }), log: () => {} }).plan();
  return plan.kind === "chrome" ? plan.chrome.path : null;
}

/** The agent-browser and the full Chrome this machine would use. */
async function realBrowser(): Promise<{ engine: string; chrome: string }> {
  const picked = process.env.OMB_CHROME_PATH?.trim() || chromeOmbPicks();
  const engine = picked ? resolveAgentBrowserBinary({ dataDir: UI_TOOLS_DIR, env: process.env }) : null;
  const browser = engine ? { binary: engine, chrome: picked } : await ensureUiBrowser(process.env, () => {}, UI_TOOLS_DIR);
  const chrome = picked || browser.chrome || installedGoogleChrome(chromeHost({ dataDir: out, env: machineEnv }));
  if (!chrome || isHeadlessShell(chrome)) throw new Error(`a full Chrome is needed, not ${chrome ?? "nothing"}: set OMB_CHROME_PATH`);
  console.log(`browser identity e2e: agent-browser ${browser.binary}, Chrome ${chrome}`);
  return { engine: browser.binary, chrome };
}

/** The real connection, with a page's or a frame's override answered late, as on a busy machine. */
const slowOverride = (lateMs: number): typeof connectCdp => async (url, timeoutMs, listeners = {}) => {
  const types = new Map<string, string>();
  const cdp = await connectCdp(url, timeoutMs, { ...listeners, event(message) {
    if (message.method === "Target.attachedToTarget") {
      const { sessionId, targetInfo } = (message.params ?? {}) as { sessionId?: string; targetInfo?: { type?: string } };
      if (sessionId) types.set(sessionId, targetInfo?.type ?? "");
    }
    listeners.event?.(message);
  } });
  return {
    send: async (method, params, sessionId) => {
      if (method === "Emulation.setUserAgentOverride" && sessionId && ["page", "iframe"].includes(types.get(sessionId) ?? "")) await new Promise((resolve) => setTimeout(resolve, lateMs));
      return cdp.send(method, params, sessionId);
    },
    close: () => cdp.close(),
  };
};

describe.skipIf(!enabled)("a bot's browser on a real Chrome", () => {
  it("opens a page 1280x720 inside whose user agent is Chrome's own, byte for byte, with the same complete client hints in the page, a worker, a service worker and a cross-site frame", async () => {
    const { engine, chrome } = await realBrowser();
    const { readout } = await verifyBrowserIdentity({ engine, chrome, out, sessionPrefix: "omb-id-e2e" });
    const failed = readout.checks.filter((check) => !check.pass).map((check) => `${check.check}: ${check.detail}`);
    expect(failed).toEqual([]);
    const page = readout.reports.page!;
    const major = String(page.high?.uaFullVersion).split(".")[0]!;
    expect(page.ua).toBe(expectedUserAgent(major));
    expect(page.ua).toContain("(KHTML, like Gecko) Chrome/");
    expect(page.inner).toEqual([1280, 720]);
    for (const place of [readout.reports.worker, readout.reports.serviceworker, readout.reports.frame]) {
      expect(place?.ua).toBe(page.ua);
      expect(place?.high).toEqual(page.high);
    }
    expect(readout.reports.headers.document?.["user-agent"]).toBe(page.ua);
    expect(readout.reports.headers.document?.["sec-ch-ua"]).not.toMatch(/Headless/u);
  }, 240_000);

  it("gives the first page the same complete client hints when the identity is slow to land", async () => {
    const { engine, chrome } = await realBrowser();
    const sidecars = new IdentitySidecars({ start: (url, identity, _connect, options) => startIdentitySidecar(url, identity, slowOverride(600), options) });
    const { readout } = await verifyBrowserIdentity({ engine, chrome, out, sessionPrefix: "omb-id-slow", sidecars });
    expect(readout.checks.filter((check) => !check.pass).map((check) => `${check.check}: ${check.detail}`)).toEqual([]);
  }, 240_000);
});