all repos — sz-chrome-remote-cdp @ master

pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search

test/cdp/commands-test.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { COMMANDS, decodeResult } from "../../src/cdp/commands";

describe("cdp command table", () => {
  test("decodes a well-formed response", () => {
    const r = decodeResult("Target.attachToTarget", { sessionId: "s1" });
    assert.equal(r.success, true);
    if (r.success) assert.equal(r.data.sessionId, "s1");
  });

  test("rejects a response with a wrong field type", () => {
    const r = decodeResult("Browser.getWindowForTarget", { windowId: "not-a-number" });
    assert.equal(r.success, false);
    if (!r.success) {
      assert.equal(r.error.kind, "invalid_response");
      assert.equal(r.error.method, "Browser.getWindowForTarget");
      assert.match(r.error.message, /must be number/);
    }
  });

  test("rejects a response missing a required field", () => {
    const r = decodeResult("Target.attachToTarget", {});
    assert.equal(r.success, false);
  });

  test("rejects a non-object response", () => {
    const r = decodeResult("Target.attachToTarget", null);
    assert.equal(r.success, false);
  });

  test("every table entry has both params and result schemas", () => {
    for (const [method, spec] of Object.entries(COMMANDS)) {
      assert.ok(spec.params, `${method} missing params schema`);
      assert.ok(spec.result, `${method} missing result schema`);
      assert.ok(spec.validate, `${method} missing compiled validator`);
    }
  });

  test("an unknown method decodes to an error rather than throwing", () => {
    // Types prevent this, but a daemon could echo an unexpected method name.
    const r = decodeResult("Nope.method" as "Page.enable", {});
    assert.equal(r.success, false);
  });

  test("accepts an empty Network.setExtraHTTPHeaders response", () => {
    const r = decodeResult("Network.setExtraHTTPHeaders", {});
    assert.equal(r.success, true);
  });
});

describe("Accessibility.getFullAXTree schema", () => {
  test("accepts a realistic Chrome AX node with unmodelled extra fields", () => {
    const r = decodeResult("Accessibility.getFullAXTree", {
      nodes: [
        {
          nodeId: "1",
          ignored: false,
          role: { type: "role", value: "RootWebArea" },
          chromeRole: { type: "internalRole", value: 144 },
          name: { type: "computedString", value: "Example", sources: [{ type: "relatedElement" }] },
          properties: [
            { name: "focusable", value: { type: "booleanOrUndefined", value: true } },
            { name: "level", value: { type: "integer", value: 2 } },
          ],
          childIds: ["2", "3"],
          backendDOMNodeId: 7,
          frameId: "F1",
        },
        { nodeId: "2", parentId: "1", ignored: true, ignoredReasons: [{ name: "notRendered" }] },
      ],
    });
    assert.equal(r.success, true);
    if (r.success) {
      assert.equal(r.data.nodes.length, 2);
      assert.equal(r.data.nodes[0]?.role?.value, "RootWebArea");
      assert.equal(r.data.nodes[0]?.backendDOMNodeId, 7);
      assert.deepEqual(r.data.nodes[0]?.childIds, ["2", "3"]);
    }
  });

  test("accepts an empty node list", () => {
    const r = decodeResult("Accessibility.getFullAXTree", { nodes: [] });
    assert.equal(r.success, true);
  });

  test("rejects a node whose nodeId is not a string", () => {
    const r = decodeResult("Accessibility.getFullAXTree", { nodes: [{ nodeId: 5 }] });
    assert.equal(r.success, false);
    if (!r.success) assert.equal(r.error.kind, "invalid_response");
  });

  test("rejects childIds that are not strings", () => {
    const r = decodeResult("Accessibility.getFullAXTree", { nodes: [{ nodeId: "1", childIds: [2] }] });
    assert.equal(r.success, false);
  });

  test("rejects a missing nodes array", () => {
    const r = decodeResult("Accessibility.getFullAXTree", {});
    assert.equal(r.success, false);
  });

  test("keeps an AxValue's value untyped so any Chrome value type decodes", () => {
    const r = decodeResult("Accessibility.getFullAXTree", {
      nodes: [{ nodeId: "1", value: { type: "number", value: 42 } }, { nodeId: "2", name: { type: "x", value: null } }],
    });
    assert.equal(r.success, true);
  });
});