aboutsummaryrefslogtreecommitdiff
path: root/test/manual/e2e-test.ts
blob: 02e04a40b1cf8a2a4b603ce0bfbae22a2e194365 (plain)
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { createIpcServer } from "../../src/daemon/server";
import { createCdpBridge, type SendToClient } from "../../src/daemon/bridge";
import { createDaemonTransport } from "../../src/daemon/transport";
import { DAEMON_SOCKET_PATH } from "../../src/daemon/protocol";
import type { WireRequest } from "../../src/daemon/protocol";
import { unlinkSync } from "node:fs";

let passed = 0;
let failed = 0;
const check = (cond: boolean, label: string) => {
  if (cond) { passed++; console.log(`  ✓ ${label}`); }
  else { failed++; console.error(`  ✗ ${label}`); }
};

async function main() {
  try { unlinkSync(DAEMON_SOCKET_PATH); } catch {}

  console.log("Starting daemon (IpcServer + CdpBridge)...");
  const ipcServer = createIpcServer();
  const cdpBridge = createCdpBridge();

  ipcServer.onMessage((msg, client) => {
    if (msg.type !== "request") return;
    const send: SendToClient = (cid, resp) => ipcServer.send(cid, resp);
    cdpBridge.handleRequest(msg, client.id, send);
  });

  cdpBridge.onEvent((event, targetClientIds) => {
    for (const cid of targetClientIds) ipcServer.send(cid, event);
  });

  ipcServer.onDisconnect((client) => cdpBridge.removeClient(client.id));

  await ipcServer.start();
  console.log("Daemon started ✓");

  console.log("\nConnecting pi client (DaemonTransport)...");
  const transport = createDaemonTransport("pi-e2e-test");

  const connectResult = await transport.connect("");
  check(connectResult.success, `Client connect: ${connectResult.success ? "ok" : connectResult.error.message}`);
  check(transport.state() === "open", "Transport state is open");

  let rawRequests: WireRequest[] = [];
  ipcServer.onMessage((msg, client) => {
    if (msg.type === "request") {
      rawRequests.push(msg);
      ipcServer.send(client.id, {
        type: "response",
        id: msg.id,
        result: { simulated: true, method: msg.method },
      });
    }
  });

  const res1 = await transport.request("Page.navigate", { url: "https://example.com" });
  check(res1.success, "CDP request succeeded");
  if (res1.success) {
    const d = res1.data as any;
    check(d?.simulated === true, "Response contains simulated flag");
    check(d?.method === "Page.navigate", "Response echoes method");
  }
  check(rawRequests.length === 1, "Server received exactly 1 request");
  check(rawRequests[0]!.method === "Page.navigate", "Correct method forwarded");

  rawRequests.length = 0;
  const res2 = await transport.request("Runtime.evaluate", { expression: "1+1" }, { sessionId: "session-abc" });
  check(res2.success, "Session-scoped request succeeded");
  check(rawRequests[0]?.sessionId === "session-abc", "SessionId forwarded correctly");

  rawRequests.length = 0;
  const [r3a, r3b, r3c] = await Promise.all([
    transport.request("Target.getTargets", {}),
    transport.request("Browser.getVersion", {}),
    transport.request("Page.captureScreenshot", {}),
  ]);
  check(r3a.success && r3b.success && r3c.success, "All 3 parallel requests succeeded");
  check(rawRequests.length === 3, "Server received all 3 requests");

  const eventPromise = new Promise<string | null>((resolve) => {
    const timeout = setTimeout(() => resolve(null), 2000);
    (async () => {
      for await (const evt of transport.events()) {
        clearTimeout(timeout);
        resolve(evt.method);
        break;
      }
    })();
  });

  ipcServer.broadcast({
    type: "event",
    method: "Target.targetCreated",
    params: { targetInfo: { type: "page", targetId: "e2e-test-target" } },
  });

  const eventMethod = await eventPromise;
  check(eventMethod === "Target.targetCreated", `Event received: ${eventMethod ?? "TIMEOUT"}`);

  await transport.close();
  check(transport.state() === "closed", "Transport closed");

  await new Promise(r => setTimeout(r, 500));

  const reconnectResult = await transport.connect("");
  check(reconnectResult.success, "Transport reconnected (simulated session restart)");

  const res4 = await transport.request("Target.getTargets", {});
  check(res4.success, "Request after reconnect succeeded");
  check(transport.state() === "open", "Transport state is open after reconnect");

  check(ipcServer.clientCount() === 1, "Server reports 1 connected client");

  await transport.close();
  await new Promise(r => setTimeout(r, 500));
  check(ipcServer.clientCount() === 0, "Server reports 0 clients after disconnect");

  await ipcServer.stop();
  console.log("\nDaemon stopped ✓");

  console.log(`\n${passed} passed, ${failed} failed`);
  process.exit(failed > 0 ? 1 : 0);
}

main().catch((e) => {
  console.error("E2E test failed:", e);
  process.exit(1);
});