barebrowse 0.2.0 → 0.2.1

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,108 +0,0 @@
1
- /**
2
- * Integration tests for the browse() pipeline.
3
- * Requires Chromium installed: sudo dnf install chromium
4
- *
5
- * Run: node --test test/integration/browse.test.js
6
- */
7
-
8
- import { describe, it } from 'node:test';
9
- import assert from 'node:assert/strict';
10
- import { browse, connect } from '../../src/index.js';
11
-
12
- describe('browse()', () => {
13
- it('returns ARIA snapshot for a public page', async () => {
14
- const snapshot = await browse('https://example.com');
15
- assert.ok(snapshot.length > 0, 'snapshot should not be empty');
16
- assert.ok(snapshot.includes('Example Domain'), 'should contain page title');
17
- });
18
-
19
- it('includes heading and ref markers', async () => {
20
- const snapshot = await browse('https://example.com');
21
- assert.ok(snapshot.includes('heading'), 'should have heading role');
22
- assert.ok(snapshot.includes('[ref='), 'should have ref markers for interaction');
23
- // In browse mode, links should also be present
24
- const browseSnap = await browse('https://example.com', { pruneMode: 'browse' });
25
- assert.ok(browseSnap.includes('link'), 'browse mode should have link role');
26
- });
27
-
28
- it('prunes by default (act mode)', async () => {
29
- const pruned = await browse('https://example.com');
30
- const raw = await browse('https://example.com', { prune: false });
31
- // Pruned should be smaller or equal (example.com is tiny, may not differ much)
32
- assert.ok(pruned.length <= raw.length, 'pruned should not be larger than raw');
33
- });
34
-
35
- it('browse mode preserves paragraphs', async () => {
36
- const snapshot = await browse('https://example.com', { pruneMode: 'browse' });
37
- assert.ok(snapshot.includes('paragraph'), 'browse mode should keep paragraphs');
38
- assert.ok(snapshot.includes('documentation examples'), 'should keep paragraph text');
39
- });
40
-
41
- it('act mode drops paragraphs', async () => {
42
- const snapshot = await browse('https://example.com', { pruneMode: 'act' });
43
- // Act mode on example.com: only heading survives (no interactive elements)
44
- assert.ok(snapshot.includes('heading'), 'should keep heading');
45
- // paragraph content should be gone
46
- assert.equal(snapshot.includes('documentation examples'), false, 'should drop paragraph text');
47
- });
48
-
49
- it('handles complex pages with significant token reduction', async () => {
50
- const pruned = await browse('https://news.ycombinator.com');
51
- const raw = await browse('https://news.ycombinator.com', { prune: false });
52
- const reduction = 1 - (pruned.length / raw.length);
53
- console.log(` HN reduction: ${Math.round(reduction * 100)}% (${raw.length} → ${pruned.length})`);
54
- assert.ok(reduction > 0.2, `should reduce by at least 20%, got ${Math.round(reduction * 100)}%`);
55
- });
56
-
57
- it('can disable cookies', async () => {
58
- // Should not throw even with cookies: false
59
- const snapshot = await browse('https://example.com', { cookies: false });
60
- assert.ok(snapshot.includes('Example Domain'));
61
- });
62
-
63
- it('can disable pruning', async () => {
64
- const snapshot = await browse('https://example.com', { prune: false });
65
- assert.ok(snapshot.includes('Example Domain'));
66
- // Raw output should have InlineTextBox filtered by aria.js but not tree-pruned
67
- assert.ok(snapshot.includes('RootWebArea'), 'raw should keep RootWebArea');
68
- });
69
- });
70
-
71
- describe('connect()', () => {
72
- it('creates a long-lived session and navigates', async () => {
73
- const page = await connect();
74
- try {
75
- await page.goto('https://example.com');
76
- const snapshot = await page.snapshot();
77
- assert.ok(snapshot.includes('Example Domain'), 'should see page content');
78
- } finally {
79
- await page.close();
80
- }
81
- });
82
-
83
- it('supports multiple navigations in one session', async () => {
84
- const page = await connect();
85
- try {
86
- await page.goto('https://example.com');
87
- const snap1 = await page.snapshot();
88
- assert.ok(snap1.includes('Example Domain'));
89
-
90
- await page.goto('https://news.ycombinator.com');
91
- const snap2 = await page.snapshot();
92
- assert.ok(snap2.includes('Hacker News'));
93
- } finally {
94
- await page.close();
95
- }
96
- });
97
-
98
- it('snapshot accepts prune: false for raw output', async () => {
99
- const page = await connect();
100
- try {
101
- await page.goto('https://example.com');
102
- const raw = await page.snapshot(false);
103
- assert.ok(raw.includes('RootWebArea'), 'raw should keep RootWebArea');
104
- } finally {
105
- await page.close();
106
- }
107
- });
108
- });
@@ -1,514 +0,0 @@
1
- /**
2
- * Integration tests for interaction primitives (click, type, press, scroll).
3
- * Uses data: URL fixtures for deterministic testing + real sites for validation.
4
- *
5
- * Run: node --test test/integration/interact.test.js
6
- */
7
-
8
- import { describe, it } from 'node:test';
9
- import assert from 'node:assert/strict';
10
- import { connect } from '../../src/index.js';
11
- import { extractCookies, injectCookies } from '../../src/auth.js';
12
-
13
- // --- Data URL fixtures ---
14
-
15
- const FIXTURE = `data:text/html,${encodeURIComponent(`<!DOCTYPE html>
16
- <html><head><style>
17
- .offscreen { margin-top: 2000px; }
18
- </style></head>
19
- <body>
20
- <button id="btn" onclick="document.getElementById('result').textContent='clicked'">Click Me</button>
21
- <div id="result"></div>
22
-
23
- <input id="text-input" type="text" aria-label="empty-input" value="" />
24
- <input id="prefilled" type="text" aria-label="prefilled-input" value="old text" />
25
-
26
- <a id="nav-link" href="data:text/html,<h1>Page Two</h1>">Go to page two</a>
27
-
28
- <button id="offscreen-btn" class="offscreen"
29
- onclick="document.getElementById('offscreen-result').textContent='scrolled-and-clicked'">
30
- Offscreen Button
31
- </button>
32
- <div id="offscreen-result" class="offscreen"></div>
33
-
34
- <form id="form" onsubmit="event.preventDefault(); document.getElementById('form-result').textContent='submitted'">
35
- <input id="form-input" type="text" aria-label="form-input" />
36
- <div id="form-result"></div>
37
- </form>
38
- </body></html>`)}`;
39
-
40
- /** Helper: evaluate JS in the page and return the result. */
41
- async function evaluate(page, expression) {
42
- const { result } = await page.cdp.send('Runtime.evaluate', { expression, returnByValue: true });
43
- return result.value;
44
- }
45
-
46
- /**
47
- * Find a ref by matching a pattern against the full snapshot text.
48
- * Uses regex to find [ref=X] near the search term, handling concatenated lines.
49
- * Matches role "name" [ref=X] pattern closest to the search term.
50
- */
51
- function findRef(snapshot, search) {
52
- // Find all occurrences of the search term with a nearby ref
53
- // Pattern: something "...search..." [ref=X] or search... [ref=X]
54
- const escaped = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
- // Look for ref right after a chunk containing the search term
56
- const re = new RegExp(`${escaped}[^\\[]*?\\[ref=([^\\]]+)\\]`);
57
- const m = snapshot.match(re);
58
- if (m) return m[1];
59
- // Fallback: look for ref just before the search term
60
- const re2 = new RegExp(`\\[ref=([^\\]]+)\\][^\\n]*?${escaped}`);
61
- const m2 = snapshot.match(re2);
62
- return m2 ? m2[1] : null;
63
- }
64
-
65
- /**
66
- * Find a ref for a specific role+name combo, e.g. findRoleRef(snap, 'button', 'Click Me').
67
- */
68
- function findRoleRef(snapshot, role, name) {
69
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
70
- const re = new RegExp(`${role} "${escaped}" \\[ref=([^\\]]+)\\]`);
71
- const m = snapshot.match(re);
72
- return m ? m[1] : null;
73
- }
74
-
75
- /**
76
- * Find a ref for a textbox by its aria-label name.
77
- */
78
- function findTextboxRef(snapshot, name) {
79
- return findRoleRef(snapshot, 'textbox', name);
80
- }
81
-
82
- // ===== Round 1: Controlled fixture (data: URL) =====
83
-
84
- describe('interact — data: URL fixture', () => {
85
- it('click sets button result text', async () => {
86
- const page = await connect();
87
- try {
88
- await page.goto(FIXTURE);
89
- const snap = await page.snapshot();
90
- const ref = findRoleRef(snap, 'button', 'Click Me');
91
- assert.ok(ref, 'should find Click Me button ref');
92
- await page.click(ref);
93
- await new Promise(r => setTimeout(r, 100));
94
- const result = await evaluate(page, 'document.getElementById("result").textContent');
95
- assert.equal(result, 'clicked');
96
- } finally {
97
- await page.close();
98
- }
99
- });
100
-
101
- it('type fills an empty input', async () => {
102
- const page = await connect();
103
- try {
104
- await page.goto(FIXTURE);
105
- const snap = await page.snapshot();
106
- const ref = findTextboxRef(snap, 'empty-input');
107
- assert.ok(ref, 'should find empty-input textbox ref');
108
- await page.type(ref, 'hello world');
109
- const value = await evaluate(page, 'document.getElementById("text-input").value');
110
- assert.equal(value, 'hello world');
111
- } finally {
112
- await page.close();
113
- }
114
- });
115
-
116
- it('type with clear replaces existing text', async () => {
117
- const page = await connect();
118
- try {
119
- await page.goto(FIXTURE);
120
- const snap = await page.snapshot();
121
- const ref = findTextboxRef(snap, 'prefilled-input');
122
- assert.ok(ref, 'should find prefilled-input textbox ref');
123
- await page.type(ref, 'new text', { clear: true });
124
- const value = await evaluate(page, 'document.getElementById("prefilled").value');
125
- assert.equal(value, 'new text');
126
- } finally {
127
- await page.close();
128
- }
129
- });
130
-
131
- it('click on offscreen element scrolls into view first', async () => {
132
- const page = await connect();
133
- try {
134
- await page.goto(FIXTURE);
135
- const snap = await page.snapshot();
136
- const ref = findRoleRef(snap, 'button', 'Offscreen Button');
137
- assert.ok(ref, 'should find offscreen button ref');
138
- await page.click(ref);
139
- await new Promise(r => setTimeout(r, 100));
140
- const result = await evaluate(page, 'document.getElementById("offscreen-result").textContent');
141
- assert.equal(result, 'scrolled-and-clicked');
142
- } finally {
143
- await page.close();
144
- }
145
- });
146
-
147
- it('press Enter submits a form', async () => {
148
- const page = await connect();
149
- try {
150
- await page.goto(FIXTURE);
151
- const snap = await page.snapshot();
152
- const ref = findTextboxRef(snap, 'form-input');
153
- assert.ok(ref, 'should find form-input textbox ref');
154
- await page.type(ref, 'test');
155
- await page.press('Enter');
156
- await new Promise(r => setTimeout(r, 100));
157
- const result = await evaluate(page, 'document.getElementById("form-result").textContent');
158
- assert.equal(result, 'submitted');
159
- } finally {
160
- await page.close();
161
- }
162
- });
163
-
164
- it('press throws on unknown key', async () => {
165
- const page = await connect();
166
- try {
167
- await page.goto(FIXTURE);
168
- await assert.rejects(() => page.press('FakeKey'), /Unknown key/);
169
- } finally {
170
- await page.close();
171
- }
172
- });
173
-
174
- it('link click + waitForNavigation navigates to new page', async () => {
175
- // Use example.com with browse mode to get link refs (act mode prunes them)
176
- const page = await connect();
177
- try {
178
- await page.goto('https://example.com');
179
- const snap = await page.snapshot({ mode: 'browse' });
180
- const ref = findRoleRef(snap, 'link', 'Learn more');
181
- assert.ok(ref, 'should find "Learn more" link ref');
182
- const navPromise = page.waitForNavigation(10000);
183
- await page.click(ref);
184
- await navPromise;
185
- const snap2 = await page.snapshot({ mode: 'browse' });
186
- // IANA page has different content than example.com (e.g. "IANA" or navigation elements)
187
- assert.ok(snap2.length > 100, 'new page should have content');
188
- assert.ok(
189
- snap2.includes('IANA') || snap2.includes('iana') || !snap2.includes('This domain is for use'),
190
- 'should have navigated to IANA page',
191
- );
192
- } finally {
193
- await page.close();
194
- }
195
- });
196
- });
197
-
198
- // ===== Round 2: Google Search =====
199
-
200
- describe('interact — Google Search', () => {
201
- it('search and navigate results', async () => {
202
- const page = await connect();
203
- try {
204
- await page.goto('https://www.google.com');
205
- let snap = await page.snapshot();
206
-
207
- // Handle cookie consent dialog if present
208
- const acceptRef = findRoleRef(snap, 'button', 'Accept all')
209
- || findRoleRef(snap, 'button', 'Alles accepteren')
210
- || findRoleRef(snap, 'button', 'Tout accepter')
211
- || findRoleRef(snap, 'button', 'Alle akzeptieren');
212
- if (acceptRef) {
213
- await page.click(acceptRef);
214
- // Consent acceptance may reload the page
215
- await page.waitForNavigation(5000).catch(() => {});
216
- await new Promise(r => setTimeout(r, 1000));
217
- snap = await page.snapshot();
218
- }
219
-
220
- // Find the search box — could be combobox or textbox, various names
221
- let searchRef = null;
222
- const searchPattern = /(?:combobox|textbox).*?\[ref=([^\]]+)\]/g;
223
- let m;
224
- while ((m = searchPattern.exec(snap)) !== null) {
225
- searchRef = m[1];
226
- break;
227
- }
228
- assert.ok(searchRef, `should find search box ref. Snapshot start: ${snap.substring(0, 500)}`);
229
-
230
- await page.type(searchRef, 'barebrowse github');
231
- const navPromise = page.waitForNavigation(15000);
232
- await page.press('Enter');
233
- await navPromise;
234
- // Settle time for results to render
235
- await new Promise(r => setTimeout(r, 1000));
236
-
237
- snap = await page.snapshot();
238
- // Google may block headless browsers with captcha, but navigation should succeed
239
- assert.ok(snap.length > 0, 'should have navigated to results/captcha page');
240
- } finally {
241
- await page.close();
242
- }
243
- });
244
- });
245
-
246
- // ===== Round 3: Wikipedia =====
247
-
248
- describe('interact — Wikipedia', () => {
249
- it('navigate article links', async () => {
250
- const page = await connect();
251
- try {
252
- await page.goto('https://en.wikipedia.org/wiki/Web_browser');
253
- let snap = await page.snapshot();
254
- assert.ok(snap.toLowerCase().includes('web browser'), 'should load Wikipedia article');
255
-
256
- // Find any article link to click
257
- let linkRef = null;
258
- const linkPattern = /link "[^"]*(?:software|internet|HTML|World Wide Web)[^"]*" \[ref=([^\]]+)\]/i;
259
- const lm = snap.match(linkPattern);
260
- if (lm) linkRef = lm[1];
261
- assert.ok(linkRef, 'should find an article link');
262
-
263
- const navPromise = page.waitForNavigation(10000);
264
- await page.click(linkRef);
265
- await navPromise;
266
-
267
- snap = await page.snapshot();
268
- assert.ok(snap.length > 100, 'new page should have content');
269
- } finally {
270
- await page.close();
271
- }
272
- });
273
- });
274
-
275
- // ===== Round 4: GitHub (SPA) =====
276
-
277
- describe('interact — GitHub', () => {
278
- it('navigate SPA repo links', async () => {
279
- const page = await connect();
280
- try {
281
- await page.goto('https://github.com/anthropics');
282
- let snap = await page.snapshot();
283
- assert.ok(snap.toLowerCase().includes('anthropic'), 'should load GitHub org page');
284
-
285
- // Find a repo link — match link with repo-like names
286
- let repoRef = null;
287
- const repoPattern = /link "(?:claude|anthropic|sdk)[^"]*" \[ref=([^\]]+)\]/i;
288
- const rm = snap.match(repoPattern);
289
- if (rm) repoRef = rm[1];
290
- assert.ok(repoRef, 'should find a repo link');
291
-
292
- await page.click(repoRef);
293
- // GitHub SPAs may not fire loadEventFired; use settle time
294
- await new Promise(r => setTimeout(r, 3000));
295
-
296
- snap = await page.snapshot();
297
- assert.ok(snap.length > 100, 'repo page should have content');
298
- } finally {
299
- await page.close();
300
- }
301
- });
302
- });
303
-
304
- // ===== Round 5: DuckDuckGo (headless-friendly search engine) =====
305
-
306
- describe('interact — DuckDuckGo Search', () => {
307
- it('search query and verify results page', async () => {
308
- const page = await connect();
309
- try {
310
- await page.goto('https://duckduckgo.com');
311
- // Use browse mode to get full tree including input elements
312
- let snap = await page.snapshot({ mode: 'browse' });
313
- assert.ok(snap.length > 50, 'DuckDuckGo homepage should load');
314
-
315
- // Find the search box — could be combobox, textbox, or searchbox
316
- let searchRef = null;
317
- const searchPattern = /(?:combobox|textbox|searchbox)[^[]*?\[ref=([^\]]+)\]/g;
318
- let m;
319
- while ((m = searchPattern.exec(snap)) !== null) {
320
- searchRef = m[1];
321
- break;
322
- }
323
- // Fallback: look for any input-like element inside a LabelText or search region
324
- if (!searchRef) {
325
- const labelPattern = /LabelText[^[]*?\[ref=([^\]]+)\]/;
326
- const lm = snap.match(labelPattern);
327
- if (lm) searchRef = lm[1];
328
- }
329
- assert.ok(searchRef, `should find search box ref. Snapshot start: ${snap.substring(0, 500)}`);
330
-
331
- await page.type(searchRef, 'node.js web framework');
332
- const navPromise = page.waitForNavigation(15000);
333
- await page.press('Enter');
334
- await navPromise;
335
- // Settle time for results to render
336
- await new Promise(r => setTimeout(r, 2000));
337
-
338
- snap = await page.snapshot();
339
- assert.ok(snap.length > 200, 'results page should have substantial content');
340
- // DuckDuckGo results should contain web-related terms
341
- const lower = snap.toLowerCase();
342
- assert.ok(
343
- lower.includes('node') || lower.includes('web') || lower.includes('result'),
344
- 'results page should contain relevant content',
345
- );
346
- } finally {
347
- await page.close();
348
- }
349
- });
350
- });
351
-
352
- // ===== Round 6: Hacker News (simple HTML, link navigation) =====
353
-
354
- describe('interact — Hacker News', () => {
355
- it('load homepage and navigate to a story', async () => {
356
- const page = await connect();
357
- try {
358
- await page.goto('https://news.ycombinator.com');
359
- let snap = await page.snapshot();
360
- assert.ok(
361
- snap.includes('Hacker News') || snap.includes('hacker news'),
362
- 'should find Hacker News in page content',
363
- );
364
-
365
- // Find a story link — HN stories are links, look for one with a ref
366
- // Story links typically have descriptive names; grab any link that is not nav/header
367
- let storyRef = null;
368
- const linkPattern = /link "[^"]{10,}" \[ref=([^\]]+)\]/g;
369
- let lm;
370
- const skipWords = /^(Hacker News|login|submit|new|past|comments|ask|show|jobs|guidelines|faq|lists|more|favorite|flag|hide|next)/i;
371
- while ((lm = linkPattern.exec(snap)) !== null) {
372
- // Extract the link name for filtering
373
- const nameMatch = lm[0].match(/link "([^"]+)"/);
374
- if (nameMatch && !skipWords.test(nameMatch[1])) {
375
- storyRef = lm[1];
376
- break;
377
- }
378
- }
379
- assert.ok(storyRef, 'should find a story link');
380
-
381
- const navPromise = page.waitForNavigation(10000).catch(() => {});
382
- await page.click(storyRef);
383
- await navPromise;
384
- // Some links are external sites; settle time for load
385
- await new Promise(r => setTimeout(r, 2000));
386
-
387
- snap = await page.snapshot();
388
- assert.ok(snap.length > 50, 'navigated page should have content');
389
- } finally {
390
- await page.close();
391
- }
392
- });
393
- });
394
-
395
- // ===== Round 7: Reddit (old.reddit.com, better headless support) =====
396
-
397
- describe('interact — Reddit (old)', () => {
398
- it('load old.reddit.com and navigate to a post or subreddit', async () => {
399
- const page = await connect();
400
- try {
401
- // old.reddit.com may redirect headless browsers; use waitForNavigation
402
- const navPromise = page.waitForNavigation(10000).catch(() => {});
403
- await page.goto('https://old.reddit.com');
404
- await navPromise;
405
- // Extra settle time — Reddit can be slow to render
406
- await new Promise(r => setTimeout(r, 3000));
407
- let snap = await page.snapshot({ mode: 'browse' });
408
-
409
- // Reddit may serve a different page to headless; check what we got
410
- if (snap.length < 200) {
411
- // Try www.reddit.com as fallback
412
- await page.goto('https://www.reddit.com');
413
- await new Promise(r => setTimeout(r, 4000));
414
- snap = await page.snapshot({ mode: 'browse' });
415
- }
416
- assert.ok(snap.length > 100, `Reddit should load with content. Got ${snap.length} chars`);
417
-
418
- // Find a content link — look for links with descriptive text
419
- let linkRef = null;
420
- const linkPattern = /link "[^"]{10,}" \[ref=([^\]]+)\]/g;
421
- let lm;
422
- const skipReddit = /^(reddit|log\s*in|sign\s*up|register|preferences|wiki|rules|mod|gilded|promoted|comments|share|save|hide|report|permalink|give|embed|crosspost|get the app|cookie|privacy|user agreement|advertise)/i;
423
- while ((lm = linkPattern.exec(snap)) !== null) {
424
- const nameMatch = lm[0].match(/link "([^"]+)"/);
425
- if (nameMatch && !skipReddit.test(nameMatch[1])) {
426
- linkRef = lm[1];
427
- break;
428
- }
429
- }
430
- assert.ok(linkRef, 'should find a content link');
431
-
432
- const clickNavPromise = page.waitForNavigation(10000).catch(() => {});
433
- await page.click(linkRef);
434
- await clickNavPromise;
435
- await new Promise(r => setTimeout(r, 3000));
436
-
437
- snap = await page.snapshot();
438
- assert.ok(snap.length > 50, 'navigated page should have content');
439
- } finally {
440
- await page.close();
441
- }
442
- });
443
- });
444
-
445
- // ===== Round 8: Cookie injection from Firefox =====
446
-
447
- describe('interact — Firefox cookie injection', () => {
448
- it('extract Firefox cookies and inject into CDP session', async () => {
449
- // Step 1: Extract Firefox cookies for github.com (best-effort)
450
- let cookies;
451
- try {
452
- cookies = extractCookies({ browser: 'firefox', domain: 'github.com' });
453
- } catch (err) {
454
- // Firefox DB may not exist or may be locked — skip gracefully
455
- console.log(`Skipping cookie injection test: ${err.message}`);
456
- return;
457
- }
458
- assert.ok(Array.isArray(cookies), 'extractCookies should return an array');
459
- // If no cookies for github.com, try a broader extraction
460
- if (cookies.length === 0) {
461
- try {
462
- cookies = extractCookies({ browser: 'firefox', domain: 'google.com' });
463
- } catch {
464
- // Still no luck — that's fine, verify the basic path works
465
- }
466
- }
467
-
468
- // Step 2: Connect without cookies (cookies: false)
469
- const page = await connect({ cookies: false });
470
- try {
471
- // Step 3: Inject cookies into the CDP session (should not throw even if empty)
472
- if (cookies && cookies.length > 0) {
473
- await injectCookies(page.cdp, cookies);
474
- // Verify cookies were injected by checking CDP's cookie jar
475
- const { cookies: cdpCookies } = await page.cdp.send('Network.getAllCookies');
476
- assert.ok(cdpCookies.length > 0, 'CDP session should have cookies after injection');
477
-
478
- // Step 4: Navigate to the domain and verify page loads
479
- await page.goto('https://github.com');
480
- await new Promise(r => setTimeout(r, 2000));
481
- const snap = await page.snapshot();
482
- assert.ok(snap.length > 100, 'GitHub should load after cookie injection');
483
- // Best-effort: if user is logged in, there might be username/avatar in snapshot
484
- // We don't assert auth state since we can't guarantee it
485
- } else {
486
- // No cookies extracted — just verify the functions don't throw
487
- await injectCookies(page.cdp, []);
488
- console.log('No Firefox cookies found for test domains; injection path verified with empty array');
489
- }
490
- } finally {
491
- await page.close();
492
- }
493
- });
494
-
495
- it('extractCookies with firefox browser returns array', () => {
496
- try {
497
- const cookies = extractCookies({ browser: 'firefox' });
498
- assert.ok(Array.isArray(cookies), 'should return an array');
499
- if (cookies.length > 0) {
500
- // Verify cookie shape
501
- const c = cookies[0];
502
- assert.ok(typeof c.name === 'string', 'cookie should have name');
503
- assert.ok(typeof c.value === 'string', 'cookie should have value');
504
- assert.ok(typeof c.domain === 'string', 'cookie should have domain');
505
- }
506
- } catch (err) {
507
- // Firefox not available or DB locked — acceptable
508
- assert.ok(
509
- err.message.includes('not found') || err.message.includes('locked') || err.message.includes('SQLITE'),
510
- `unexpected error: ${err.message}`,
511
- );
512
- }
513
- });
514
- });
@@ -1,66 +0,0 @@
1
- /**
2
- * Unit tests for cookie extraction.
3
- * Tests what's available on this system (Firefox on Fedora/KDE).
4
- *
5
- * Run: node --test test/unit/auth.test.js
6
- */
7
-
8
- import { describe, it } from 'node:test';
9
- import assert from 'node:assert/strict';
10
- import { extractCookies } from '../../src/auth.js';
11
-
12
- describe('extractCookies()', () => {
13
- it('auto-detects a browser and returns cookies', () => {
14
- const cookies = extractCookies();
15
- assert.ok(Array.isArray(cookies), 'should return an array');
16
- assert.ok(cookies.length > 0, 'should find at least some cookies');
17
- });
18
-
19
- it('returns cookies with correct shape', () => {
20
- const cookies = extractCookies();
21
- const cookie = cookies[0];
22
- assert.ok('name' in cookie, 'should have name');
23
- assert.ok('value' in cookie, 'should have value');
24
- assert.ok('domain' in cookie, 'should have domain');
25
- assert.ok('path' in cookie, 'should have path');
26
- assert.ok('secure' in cookie, 'should have secure');
27
- assert.ok('httpOnly' in cookie, 'should have httpOnly');
28
- assert.ok('sameSite' in cookie, 'should have sameSite');
29
- assert.ok('expires' in cookie, 'should have expires');
30
- });
31
-
32
- it('filters by domain', () => {
33
- const cookies = extractCookies({ domain: 'google.com' });
34
- for (const cookie of cookies) {
35
- assert.ok(cookie.domain.includes('google'), `${cookie.domain} should match google`);
36
- }
37
- });
38
-
39
- it('extracts from firefox explicitly', () => {
40
- const cookies = extractCookies({ browser: 'firefox' });
41
- assert.ok(cookies.length > 0, 'should find Firefox cookies');
42
- });
43
-
44
- it('throws for non-existent browser', () => {
45
- assert.throws(
46
- () => extractCookies({ browser: 'chrome' }),
47
- /not found/i,
48
- 'should throw for missing Chrome'
49
- );
50
- });
51
-
52
- it('cookies have non-empty values', () => {
53
- const cookies = extractCookies();
54
- for (const cookie of cookies) {
55
- assert.ok(cookie.value.length > 0, `cookie ${cookie.name} should have a value`);
56
- }
57
- });
58
-
59
- it('sameSite is a valid value', () => {
60
- const valid = new Set(['None', 'Lax', 'Strict']);
61
- const cookies = extractCookies();
62
- for (const cookie of cookies) {
63
- assert.ok(valid.has(cookie.sameSite), `${cookie.sameSite} should be valid sameSite`);
64
- }
65
- });
66
- });