@geometra/mcp 1.59.0 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,262 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { WebSocketServer } from 'ws';
3
- const mockState = vi.hoisted(() => ({
4
- startEmbeddedGeometraProxy: vi.fn(),
5
- spawnGeometraProxy: vi.fn(),
6
- }));
7
- vi.mock('../proxy-spawn.js', () => ({
8
- startEmbeddedGeometraProxy: mockState.startEmbeddedGeometraProxy,
9
- spawnGeometraProxy: mockState.spawnGeometraProxy,
10
- }));
11
- const { connectThroughProxy, disconnect, prewarmProxy } = await import('../session.js');
12
- function frame(pageUrl) {
13
- return {
14
- type: 'frame',
15
- layout: { x: 0, y: 0, width: 1280, height: 720, children: [] },
16
- tree: {
17
- kind: 'box',
18
- props: {},
19
- semantic: {
20
- tag: 'body',
21
- role: 'group',
22
- pageUrl,
23
- },
24
- children: [],
25
- },
26
- };
27
- }
28
- async function createProxyPeer(options) {
29
- const wss = new WebSocketServer({ port: 0 });
30
- wss.on('connection', ws => {
31
- if (options?.sendInitialFrame !== false) {
32
- ws.send(JSON.stringify(frame(options?.pageUrl ?? 'https://jobs.example.com/original')));
33
- }
34
- ws.on('message', raw => {
35
- const msg = JSON.parse(String(raw));
36
- if (msg.type === 'navigate') {
37
- options?.onNavigate?.(ws, msg);
38
- }
39
- else if (msg.type === 'resize') {
40
- options?.onResize?.(ws, msg);
41
- }
42
- });
43
- });
44
- const port = await new Promise((resolve, reject) => {
45
- wss.once('listening', () => {
46
- const address = wss.address();
47
- if (typeof address === 'object' && address)
48
- resolve(address.port);
49
- else
50
- reject(new Error('Failed to resolve ephemeral WebSocket port'));
51
- });
52
- wss.once('error', reject);
53
- });
54
- return {
55
- wss,
56
- wsUrl: `ws://127.0.0.1:${port}`,
57
- };
58
- }
59
- afterEach(async () => {
60
- disconnect({ closeProxy: true });
61
- vi.clearAllMocks();
62
- });
63
- async function closePeer(wss) {
64
- for (const client of wss.clients) {
65
- client.close();
66
- }
67
- await new Promise((resolve, reject) => wss.close(err => (err ? reject(err) : resolve())));
68
- }
69
- describe('connectThroughProxy recovery', () => {
70
- it('restarts from a fresh proxy when a reused browser session was already closed', async () => {
71
- const stalePeer = await createProxyPeer({
72
- pageUrl: 'https://jobs.example.com/original',
73
- onNavigate(ws, msg) {
74
- ws.send(JSON.stringify({
75
- type: 'error',
76
- requestId: msg.requestId,
77
- message: 'page.goto: Target page, context or browser has been closed',
78
- }));
79
- },
80
- });
81
- const freshPeer = await createProxyPeer({
82
- pageUrl: 'https://jobs.example.com/recovered',
83
- });
84
- const staleRuntime = {
85
- wsUrl: stalePeer.wsUrl,
86
- ready: Promise.resolve(),
87
- closed: false,
88
- close: vi.fn(async () => {
89
- staleRuntime.closed = true;
90
- }),
91
- };
92
- const freshRuntime = {
93
- wsUrl: freshPeer.wsUrl,
94
- ready: Promise.resolve(),
95
- closed: false,
96
- close: vi.fn(async () => {
97
- freshRuntime.closed = true;
98
- }),
99
- };
100
- mockState.startEmbeddedGeometraProxy
101
- .mockResolvedValueOnce({ runtime: staleRuntime, wsUrl: stalePeer.wsUrl })
102
- .mockResolvedValueOnce({ runtime: freshRuntime, wsUrl: freshPeer.wsUrl });
103
- mockState.spawnGeometraProxy.mockRejectedValue(new Error('spawn fallback should not be used'));
104
- try {
105
- const firstSession = await connectThroughProxy({
106
- pageUrl: 'https://jobs.example.com/original',
107
- headless: true,
108
- });
109
- expect(firstSession.proxyRuntime).toBe(staleRuntime);
110
- const recoveredSession = await connectThroughProxy({
111
- pageUrl: 'https://jobs.example.com/recovered',
112
- headless: true,
113
- });
114
- expect(recoveredSession.proxyRuntime).toBe(freshRuntime);
115
- expect(mockState.startEmbeddedGeometraProxy).toHaveBeenCalledTimes(2);
116
- expect(staleRuntime.close).toHaveBeenCalledTimes(1);
117
- expect(mockState.spawnGeometraProxy).not.toHaveBeenCalled();
118
- }
119
- finally {
120
- disconnect({ closeProxy: true });
121
- await closePeer(stalePeer.wss);
122
- await closePeer(freshPeer.wss);
123
- }
124
- });
125
- it('keeps separate warm proxies for compatible headed and headless reuse', async () => {
126
- const headedPeer = await createProxyPeer({
127
- pageUrl: 'https://jobs.example.com/headed',
128
- });
129
- const headlessPeer = await createProxyPeer({
130
- pageUrl: 'https://jobs.example.com/headless',
131
- });
132
- const headedRuntime = {
133
- wsUrl: headedPeer.wsUrl,
134
- ready: Promise.resolve(),
135
- closed: false,
136
- close: vi.fn(async () => {
137
- headedRuntime.closed = true;
138
- }),
139
- };
140
- const headlessRuntime = {
141
- wsUrl: headlessPeer.wsUrl,
142
- ready: Promise.resolve(),
143
- closed: false,
144
- close: vi.fn(async () => {
145
- headlessRuntime.closed = true;
146
- }),
147
- };
148
- mockState.startEmbeddedGeometraProxy
149
- .mockResolvedValueOnce({ runtime: headedRuntime, wsUrl: headedPeer.wsUrl })
150
- .mockResolvedValueOnce({ runtime: headlessRuntime, wsUrl: headlessPeer.wsUrl });
151
- mockState.spawnGeometraProxy.mockRejectedValue(new Error('spawn fallback should not be used'));
152
- try {
153
- const headedSession = await connectThroughProxy({
154
- pageUrl: 'https://jobs.example.com/headed',
155
- headless: false,
156
- });
157
- expect(headedSession.proxyRuntime).toBe(headedRuntime);
158
- disconnect();
159
- const headlessSession = await connectThroughProxy({
160
- pageUrl: 'https://jobs.example.com/headless',
161
- headless: true,
162
- });
163
- expect(headlessSession.proxyRuntime).toBe(headlessRuntime);
164
- disconnect();
165
- const reusedHeadedSession = await connectThroughProxy({
166
- pageUrl: 'https://jobs.example.com/headed',
167
- headless: false,
168
- });
169
- expect(reusedHeadedSession.proxyRuntime).toBe(headedRuntime);
170
- expect(mockState.startEmbeddedGeometraProxy).toHaveBeenCalledTimes(2);
171
- expect(headedRuntime.close).not.toHaveBeenCalled();
172
- expect(headlessRuntime.close).not.toHaveBeenCalled();
173
- }
174
- finally {
175
- disconnect({ closeProxy: true });
176
- expect(headedRuntime.close).toHaveBeenCalledTimes(1);
177
- expect(headlessRuntime.close).toHaveBeenCalledTimes(1);
178
- await closePeer(headedPeer.wss);
179
- await closePeer(headlessPeer.wss);
180
- }
181
- });
182
- it('can prewarm a reusable proxy before the first measured task', async () => {
183
- const preparedPeer = await createProxyPeer({
184
- pageUrl: 'https://jobs.example.com/prepared',
185
- });
186
- const preparedRuntime = {
187
- wsUrl: preparedPeer.wsUrl,
188
- ready: Promise.resolve(),
189
- closed: false,
190
- close: vi.fn(async () => {
191
- preparedRuntime.closed = true;
192
- }),
193
- };
194
- mockState.startEmbeddedGeometraProxy.mockResolvedValue({
195
- runtime: preparedRuntime,
196
- wsUrl: preparedPeer.wsUrl,
197
- });
198
- mockState.spawnGeometraProxy.mockRejectedValue(new Error('spawn fallback should not be used'));
199
- try {
200
- const prepared = await prewarmProxy({
201
- pageUrl: 'https://jobs.example.com/prepared',
202
- headless: true,
203
- });
204
- expect(prepared).toMatchObject({
205
- prepared: true,
206
- reused: false,
207
- transport: 'embedded',
208
- pageUrl: 'https://jobs.example.com/prepared',
209
- });
210
- const session = await connectThroughProxy({
211
- pageUrl: 'https://jobs.example.com/prepared',
212
- headless: true,
213
- });
214
- expect(session.proxyRuntime).toBe(preparedRuntime);
215
- expect(mockState.startEmbeddedGeometraProxy).toHaveBeenCalledTimes(1);
216
- expect(mockState.spawnGeometraProxy).not.toHaveBeenCalled();
217
- }
218
- finally {
219
- disconnect({ closeProxy: true });
220
- expect(preparedRuntime.close).toHaveBeenCalledTimes(1);
221
- await closePeer(preparedPeer.wss);
222
- }
223
- });
224
- it('starts without an eager initial extract when the caller defers the first frame', async () => {
225
- const lazyPeer = await createProxyPeer({
226
- pageUrl: 'https://jobs.example.com/lazy',
227
- sendInitialFrame: false,
228
- });
229
- const lazyRuntime = {
230
- wsUrl: lazyPeer.wsUrl,
231
- ready: Promise.resolve(),
232
- closed: false,
233
- close: vi.fn(async () => {
234
- lazyRuntime.closed = true;
235
- }),
236
- };
237
- mockState.startEmbeddedGeometraProxy.mockResolvedValueOnce({
238
- runtime: lazyRuntime,
239
- wsUrl: lazyPeer.wsUrl,
240
- });
241
- mockState.spawnGeometraProxy.mockRejectedValue(new Error('spawn fallback should not be used'));
242
- try {
243
- const session = await connectThroughProxy({
244
- pageUrl: 'https://jobs.example.com/lazy',
245
- headless: true,
246
- awaitInitialFrame: false,
247
- });
248
- expect(session.proxyRuntime).toBe(lazyRuntime);
249
- expect(mockState.startEmbeddedGeometraProxy).toHaveBeenCalledWith(expect.objectContaining({
250
- pageUrl: 'https://jobs.example.com/lazy',
251
- headless: true,
252
- eagerInitialExtract: false,
253
- }));
254
- expect(session.tree).toBeNull();
255
- }
256
- finally {
257
- disconnect({ closeProxy: true });
258
- expect(lazyRuntime.close).toHaveBeenCalledTimes(1);
259
- await closePeer(lazyPeer.wss);
260
- }
261
- });
262
- });
@@ -1 +0,0 @@
1
- export {};