@olib-ai/owl-browser-sdk 1.0.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.
@@ -0,0 +1,1017 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BrowserContext = void 0;
4
+ /**
5
+ * Browser context (tab/window) for automation
6
+ */
7
+ class BrowserContext {
8
+ constructor(contextId, core) {
9
+ this.contextId = contextId;
10
+ this.core = core;
11
+ }
12
+ /**
13
+ * Get the context ID
14
+ */
15
+ getId() {
16
+ return this.contextId;
17
+ }
18
+ // ==================== NAVIGATION ====================
19
+ /**
20
+ * Navigate to a URL
21
+ * @param url - URL to navigate to
22
+ * @param options - Navigation options
23
+ */
24
+ async goto(url, options) {
25
+ // Auto-add protocol if missing
26
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
27
+ url = 'https://' + url;
28
+ }
29
+ await this.core.sendCommand('navigate', {
30
+ context_id: this.contextId,
31
+ url,
32
+ });
33
+ }
34
+ /**
35
+ * Reload the current page
36
+ * @param ignoreCache - Bypass cache (hard reload)
37
+ */
38
+ async reload(ignoreCache = false) {
39
+ await this.core.sendCommand('reload', {
40
+ context_id: this.contextId,
41
+ ignore_cache: ignoreCache,
42
+ });
43
+ }
44
+ /**
45
+ * Navigate back in history
46
+ */
47
+ async goBack() {
48
+ await this.core.sendCommand('goBack', {
49
+ context_id: this.contextId,
50
+ });
51
+ }
52
+ /**
53
+ * Navigate forward in history
54
+ */
55
+ async goForward() {
56
+ await this.core.sendCommand('goForward', {
57
+ context_id: this.contextId,
58
+ });
59
+ }
60
+ // ==================== INTERACTIONS ====================
61
+ /**
62
+ * Click an element using CSS selector, position coordinates, or natural language description
63
+ * @param selector - CSS selector, position coordinates (e.g., "100x200"), or natural description (e.g., "search button", "login link")
64
+ */
65
+ async click(selector) {
66
+ await this.core.sendCommand('click', {
67
+ context_id: this.contextId,
68
+ selector,
69
+ });
70
+ }
71
+ /**
72
+ * Type text into an input field
73
+ * @param selector - CSS selector, position coordinates (e.g., "100x200"), or natural description (e.g., "search box", "email input")
74
+ * @param text - Text to type
75
+ */
76
+ async type(selector, text) {
77
+ await this.core.sendCommand('type', {
78
+ context_id: this.contextId,
79
+ selector,
80
+ text,
81
+ });
82
+ }
83
+ /**
84
+ * Select an option from a dropdown/select element
85
+ * @param selector - CSS selector or natural description (e.g., "country dropdown", "state list")
86
+ * @param value - Value or visible text of the option to select (e.g., "Morocco", "California")
87
+ */
88
+ async pick(selector, value) {
89
+ await this.core.sendCommand('pick', {
90
+ context_id: this.contextId,
91
+ selector,
92
+ value,
93
+ });
94
+ }
95
+ /**
96
+ * Press a special key
97
+ * @param key - Key name (e.g., 'Enter', 'Tab', 'Escape')
98
+ */
99
+ async pressKey(key) {
100
+ await this.core.sendCommand('pressKey', {
101
+ context_id: this.contextId,
102
+ key,
103
+ });
104
+ }
105
+ /**
106
+ * Submit the currently focused form by pressing Enter
107
+ * Useful for search boxes and forms that submit on Enter
108
+ */
109
+ async submitForm() {
110
+ await this.core.sendCommand('submitForm', {
111
+ context_id: this.contextId,
112
+ });
113
+ }
114
+ /**
115
+ * Highlight an element for debugging
116
+ * @param selector - CSS selector or natural description
117
+ * @param borderColor - Border color (default: "#FF0000")
118
+ * @param backgroundColor - Background color (default: "rgba(255, 0, 0, 0.2)")
119
+ */
120
+ async highlight(selector, borderColor = '#FF0000', backgroundColor = 'rgba(255, 0, 0, 0.2)') {
121
+ await this.core.sendCommand('highlight', {
122
+ context_id: this.contextId,
123
+ selector,
124
+ border_color: borderColor,
125
+ background_color: backgroundColor,
126
+ });
127
+ }
128
+ // ==================== CONTENT EXTRACTION ====================
129
+ /**
130
+ * Extract text content from the page
131
+ * @param selector - CSS selector or natural description (default: 'body')
132
+ */
133
+ async extractText(selector = 'body') {
134
+ return await this.core.sendCommand('extractText', {
135
+ context_id: this.contextId,
136
+ selector,
137
+ });
138
+ }
139
+ /**
140
+ * Get HTML content from the page
141
+ * @param cleanLevel - Cleaning level: 'minimal', 'basic', or 'aggressive'
142
+ */
143
+ async getHTML(cleanLevel = 'basic') {
144
+ return await this.core.sendCommand('getHTML', {
145
+ context_id: this.contextId,
146
+ clean_level: cleanLevel,
147
+ });
148
+ }
149
+ /**
150
+ * Get page content as Markdown
151
+ * @param options - Markdown extraction options
152
+ */
153
+ async getMarkdown(options) {
154
+ return await this.core.sendCommand('getMarkdown', {
155
+ context_id: this.contextId,
156
+ include_links: options?.includeLinks ?? true,
157
+ include_images: options?.includeImages ?? true,
158
+ max_length: options?.maxLength ?? -1,
159
+ });
160
+ }
161
+ /**
162
+ * Extract structured JSON data using templates
163
+ * @param template - Template name or empty for auto-detection
164
+ */
165
+ async extractJSON(template = '') {
166
+ const result = await this.core.sendCommand('extractJSON', {
167
+ context_id: this.contextId,
168
+ template_name: template,
169
+ });
170
+ return result;
171
+ }
172
+ /**
173
+ * Detect website type
174
+ */
175
+ async detectWebsiteType() {
176
+ return await this.core.sendCommand('detectWebsiteType', {
177
+ context_id: this.contextId,
178
+ });
179
+ }
180
+ /**
181
+ * Get intelligent, structured summary of the current page using LLM
182
+ * The summary is cached per URL for fast repeat access
183
+ * @param forceRefresh - Force refresh the summary (ignore cache)
184
+ */
185
+ async summarizePage(forceRefresh = false) {
186
+ return await this.core.sendCommand('summarizePage', {
187
+ context_id: this.contextId,
188
+ force_refresh: forceRefresh,
189
+ });
190
+ }
191
+ /**
192
+ * List all available extraction templates
193
+ */
194
+ async listTemplates() {
195
+ return await this.core.sendCommand('listTemplates', {});
196
+ }
197
+ // ==================== AI FEATURES ====================
198
+ /**
199
+ * Query the page using on-device LLM
200
+ * @param query - Natural language question about the page
201
+ */
202
+ async queryPage(query) {
203
+ // Let the browser handle LLM readiness - it will wait internally
204
+ return await this.core.sendCommand('queryPage', {
205
+ context_id: this.contextId,
206
+ query,
207
+ });
208
+ }
209
+ /**
210
+ * Check if the on-device LLM is ready to use
211
+ * @returns Status: "ready", "loading", or "unavailable"
212
+ */
213
+ async llmStatus() {
214
+ return await this.core.sendCommand('llmStatus', {});
215
+ }
216
+ /**
217
+ * Execute natural language automation command
218
+ * @param command - Natural language command (e.g., "go to google.com and search for banana")
219
+ */
220
+ async executeNLA(command) {
221
+ // Let the browser handle LLM readiness and execution - it will wait internally
222
+ return await this.core.sendCommand('executeNLA', {
223
+ context_id: this.contextId,
224
+ query: command,
225
+ });
226
+ }
227
+ // ==================== SCREENSHOT & VIDEO ====================
228
+ /**
229
+ * Take a screenshot
230
+ * @param options - Screenshot options
231
+ * @returns Buffer (or void if path is specified)
232
+ */
233
+ async screenshot(options) {
234
+ const result = await this.core.sendCommand('screenshot', {
235
+ context_id: this.contextId,
236
+ });
237
+ const buffer = Buffer.from(result, 'base64');
238
+ // If path is specified, save to file
239
+ if (options?.path) {
240
+ const fs = require('fs');
241
+ fs.writeFileSync(options.path, buffer);
242
+ return;
243
+ }
244
+ return buffer;
245
+ }
246
+ /**
247
+ * Start video recording
248
+ * @param options - Video recording options
249
+ */
250
+ async startVideoRecording(options) {
251
+ await this.core.sendCommand('startVideoRecording', {
252
+ context_id: this.contextId,
253
+ fps: options?.fps ?? 30,
254
+ codec: options?.codec ?? 'libx264',
255
+ });
256
+ }
257
+ /**
258
+ * Pause video recording
259
+ */
260
+ async pauseVideoRecording() {
261
+ await this.core.sendCommand('pauseVideoRecording', {
262
+ context_id: this.contextId,
263
+ });
264
+ }
265
+ /**
266
+ * Resume video recording
267
+ */
268
+ async resumeVideoRecording() {
269
+ await this.core.sendCommand('resumeVideoRecording', {
270
+ context_id: this.contextId,
271
+ });
272
+ }
273
+ /**
274
+ * Stop video recording and get video path
275
+ */
276
+ async stopVideoRecording() {
277
+ return await this.core.sendCommand('stopVideoRecording', {
278
+ context_id: this.contextId,
279
+ });
280
+ }
281
+ /**
282
+ * Get video recording statistics
283
+ */
284
+ async getVideoStats() {
285
+ return await this.core.sendCommand('getVideoRecordingStats', {
286
+ context_id: this.contextId,
287
+ });
288
+ }
289
+ // ==================== SCROLLING ====================
290
+ /**
291
+ * Scroll by specified pixels
292
+ * @param x - Horizontal scroll amount
293
+ * @param y - Vertical scroll amount
294
+ */
295
+ async scrollBy(x, y) {
296
+ await this.core.sendCommand('scrollBy', {
297
+ context_id: this.contextId,
298
+ x,
299
+ y,
300
+ });
301
+ }
302
+ /**
303
+ * Scroll to absolute position
304
+ * @param x - Horizontal position
305
+ * @param y - Vertical position
306
+ */
307
+ async scrollTo(x, y) {
308
+ await this.core.sendCommand('scrollTo', {
309
+ context_id: this.contextId,
310
+ x,
311
+ y,
312
+ });
313
+ }
314
+ /**
315
+ * Scroll element into view
316
+ * @param selector - CSS selector or natural description
317
+ */
318
+ async scrollToElement(selector) {
319
+ await this.core.sendCommand('scrollToElement', {
320
+ context_id: this.contextId,
321
+ selector,
322
+ });
323
+ }
324
+ /**
325
+ * Scroll to top of page
326
+ */
327
+ async scrollToTop() {
328
+ await this.core.sendCommand('scrollToTop', {
329
+ context_id: this.contextId,
330
+ });
331
+ }
332
+ /**
333
+ * Scroll to bottom of page
334
+ */
335
+ async scrollToBottom() {
336
+ await this.core.sendCommand('scrollToBottom', {
337
+ context_id: this.contextId,
338
+ });
339
+ }
340
+ // ==================== WAITING ====================
341
+ /**
342
+ * Wait for element to appear
343
+ * @param selector - CSS selector or natural description
344
+ * @param options - Wait options
345
+ */
346
+ async waitForSelector(selector, options) {
347
+ await this.core.sendCommand('waitForSelector', {
348
+ context_id: this.contextId,
349
+ selector,
350
+ timeout: options?.timeout ?? 5000,
351
+ });
352
+ }
353
+ /**
354
+ * Wait for specified time (use sparingly - prefer waitForSelector)
355
+ * @param timeout - Time to wait in milliseconds
356
+ */
357
+ async wait(timeout) {
358
+ // Only use when explicitly needed by user
359
+ await this.core.sendCommand('waitForTimeout', {
360
+ context_id: this.contextId,
361
+ timeout,
362
+ });
363
+ }
364
+ // ==================== PAGE STATE ====================
365
+ /**
366
+ * Get current URL
367
+ */
368
+ async getCurrentURL() {
369
+ return await this.core.sendCommand('getCurrentURL', {
370
+ context_id: this.contextId,
371
+ });
372
+ }
373
+ /**
374
+ * Get page title
375
+ */
376
+ async getTitle() {
377
+ return await this.core.sendCommand('getPageTitle', {
378
+ context_id: this.contextId,
379
+ });
380
+ }
381
+ /**
382
+ * Get comprehensive page information
383
+ */
384
+ async getPageInfo() {
385
+ return await this.core.sendCommand('getPageInfo', {
386
+ context_id: this.contextId,
387
+ });
388
+ }
389
+ // ==================== VIEWPORT ====================
390
+ /**
391
+ * Set viewport size
392
+ * @param viewport - Viewport dimensions
393
+ */
394
+ async setViewport(viewport) {
395
+ await this.core.sendCommand('setViewport', {
396
+ context_id: this.contextId,
397
+ width: viewport.width,
398
+ height: viewport.height,
399
+ });
400
+ }
401
+ /**
402
+ * Get current viewport size
403
+ */
404
+ async getViewport() {
405
+ return await this.core.sendCommand('getViewport', {
406
+ context_id: this.contextId,
407
+ });
408
+ }
409
+ // ==================== DEMOGRAPHICS ====================
410
+ /**
411
+ * Get user demographics and context (location, time, weather)
412
+ * Useful for location-aware searches like "find me a restaurant" or "book a hotel"
413
+ */
414
+ async getDemographics() {
415
+ return await this.core.sendCommand('getDemographics', {});
416
+ }
417
+ /**
418
+ * Get user's current location based on IP address
419
+ */
420
+ async getLocation() {
421
+ return await this.core.sendCommand('getLocation', {});
422
+ }
423
+ /**
424
+ * Get current date and time information
425
+ */
426
+ async getDateTime() {
427
+ return await this.core.sendCommand('getDateTime', {});
428
+ }
429
+ /**
430
+ * Get current weather for the user's location
431
+ */
432
+ async getWeather() {
433
+ return await this.core.sendCommand('getWeather', {});
434
+ }
435
+ // ==================== CAPTCHA SOLVING ====================
436
+ /**
437
+ * Detect if the current page has a CAPTCHA using heuristic analysis
438
+ * @returns Detection result with confidence score
439
+ */
440
+ async detectCaptcha() {
441
+ return await this.core.sendCommand('detectCaptcha', {
442
+ context_id: this.contextId,
443
+ });
444
+ }
445
+ /**
446
+ * Classify the type of CAPTCHA on the page
447
+ * @returns Classification result with CAPTCHA type and element selectors
448
+ */
449
+ async classifyCaptcha() {
450
+ return await this.core.sendCommand('classifyCaptcha', {
451
+ context_id: this.contextId,
452
+ });
453
+ }
454
+ /**
455
+ * Solve a text-based CAPTCHA using vision model
456
+ * @param maxAttempts - Maximum number of attempts (default: 3)
457
+ */
458
+ async solveTextCaptcha(maxAttempts = 3) {
459
+ return await this.core.sendCommand('solveTextCaptcha', {
460
+ context_id: this.contextId,
461
+ max_attempts: maxAttempts,
462
+ });
463
+ }
464
+ /**
465
+ * Solve an image-selection CAPTCHA (e.g., "select all images with traffic lights")
466
+ * @param maxAttempts - Maximum number of attempts (default: 3)
467
+ */
468
+ async solveImageCaptcha(maxAttempts = 3) {
469
+ return await this.core.sendCommand('solveImageCaptcha', {
470
+ context_id: this.contextId,
471
+ max_attempts: maxAttempts,
472
+ });
473
+ }
474
+ /**
475
+ * Auto-detect and solve any supported CAPTCHA type
476
+ * Automatically detects, classifies, and solves the CAPTCHA
477
+ * @param maxAttempts - Maximum number of attempts (default: 3)
478
+ */
479
+ async solveCaptcha(maxAttempts = 3) {
480
+ return await this.core.sendCommand('solveCaptcha', {
481
+ context_id: this.contextId,
482
+ max_attempts: maxAttempts,
483
+ });
484
+ }
485
+ // ==================== TEST EXECUTION ====================
486
+ /**
487
+ * Execute a test from Developer Playground JSON template
488
+ * @param test - Test template (JSON object or file path)
489
+ * @param options - Execution options
490
+ * @returns Test execution result
491
+ *
492
+ * @example
493
+ * ```typescript
494
+ * // Load test from JSON file
495
+ * const testJson = JSON.parse(fs.readFileSync('test.json', 'utf-8'));
496
+ * const result = await page.runTest(testJson);
497
+ * console.log(`Test: ${result.testName}`);
498
+ * console.log(`Success: ${result.successfulSteps}/${result.totalSteps}`);
499
+ * ```
500
+ *
501
+ * @example
502
+ * ```typescript
503
+ * // Define test inline
504
+ * const test = {
505
+ * name: "Login Test",
506
+ * description: "Test user login flow",
507
+ * steps: [
508
+ * { type: "navigate", url: "https://example.com/login" },
509
+ * { type: "type", selector: "#email", text: "user@example.com" },
510
+ * { type: "type", selector: "#password", text: "password123" },
511
+ * { type: "click", selector: "button[type='submit']" },
512
+ * { type: "wait", duration: 2000 },
513
+ * { type: "screenshot", filename: "after-login.png" }
514
+ * ]
515
+ * };
516
+ * const result = await page.runTest(test);
517
+ * ```
518
+ */
519
+ async runTest(test, options) {
520
+ // Parse test if string (file path)
521
+ let testData;
522
+ if (typeof test === 'string') {
523
+ const fs = require('fs');
524
+ testData = JSON.parse(fs.readFileSync(test, 'utf-8'));
525
+ }
526
+ else {
527
+ testData = test;
528
+ }
529
+ const opts = {
530
+ continueOnError: options?.continueOnError ?? false,
531
+ screenshotOnError: options?.screenshotOnError ?? true,
532
+ verbose: options?.verbose ?? false,
533
+ };
534
+ const result = {
535
+ testName: testData.name,
536
+ totalSteps: testData.steps.filter(s => s.selected !== false).length,
537
+ executedSteps: 0,
538
+ successfulSteps: 0,
539
+ failedSteps: 0,
540
+ executionTime: 0,
541
+ success: true,
542
+ errors: [],
543
+ };
544
+ const startTime = Date.now();
545
+ if (opts.verbose) {
546
+ console.log(`[Test] Starting: ${testData.name}`);
547
+ if (testData.description) {
548
+ console.log(`[Test] Description: ${testData.description}`);
549
+ }
550
+ }
551
+ for (let i = 0; i < testData.steps.length; i++) {
552
+ const step = testData.steps[i];
553
+ // Skip unselected steps
554
+ if (step.selected === false) {
555
+ if (opts.verbose) {
556
+ console.log(`[Test] Step ${i + 1}: Skipped (not selected)`);
557
+ }
558
+ continue;
559
+ }
560
+ result.executedSteps++;
561
+ if (opts.verbose) {
562
+ console.log(`[Test] Step ${i + 1}/${result.totalSteps}: ${step.type}`);
563
+ }
564
+ try {
565
+ switch (step.type) {
566
+ case 'navigate':
567
+ if (!step.url)
568
+ throw new Error('Navigate step requires url');
569
+ await this.goto(step.url);
570
+ break;
571
+ case 'click':
572
+ if (!step.selector)
573
+ throw new Error('Click step requires selector');
574
+ await this.click(step.selector);
575
+ break;
576
+ case 'type':
577
+ if (!step.selector)
578
+ throw new Error('Type step requires selector');
579
+ if (!step.text)
580
+ throw new Error('Type step requires text');
581
+ await this.type(step.selector, step.text);
582
+ break;
583
+ case 'pick':
584
+ if (!step.selector)
585
+ throw new Error('Pick step requires selector');
586
+ if (!step.value)
587
+ throw new Error('Pick step requires value');
588
+ await this.pick(step.selector, step.value);
589
+ break;
590
+ case 'wait':
591
+ const duration = step.duration ?? 2000;
592
+ await this.wait(duration);
593
+ break;
594
+ case 'screenshot':
595
+ const filename = step.filename ?? 'screenshot.png';
596
+ await this.screenshot({ path: filename });
597
+ break;
598
+ case 'extract':
599
+ const selector = step.selector ?? 'body';
600
+ const text = await this.extractText(selector);
601
+ if (opts.verbose) {
602
+ console.log(`[Test] Extracted text: ${text.substring(0, 100)}...`);
603
+ }
604
+ break;
605
+ case 'submit_form':
606
+ await this.submitForm();
607
+ break;
608
+ case 'query':
609
+ if (!step.query)
610
+ throw new Error('Query step requires query');
611
+ const answer = await this.queryPage(step.query);
612
+ if (opts.verbose) {
613
+ console.log(`[Test] Query result: ${answer}`);
614
+ }
615
+ break;
616
+ case 'nla':
617
+ if (!step.command)
618
+ throw new Error('NLA step requires command');
619
+ const nlaResult = await this.executeNLA(step.command);
620
+ if (opts.verbose) {
621
+ console.log(`[Test] NLA result: ${nlaResult}`);
622
+ }
623
+ break;
624
+ case 'solve_captcha':
625
+ await this.solveCaptcha();
626
+ break;
627
+ case 'highlight':
628
+ if (!step.selector)
629
+ throw new Error('Highlight step requires selector');
630
+ await this.highlight(step.selector);
631
+ break;
632
+ case 'scroll_up':
633
+ await this.scrollBy(0, -500);
634
+ break;
635
+ case 'scroll_down':
636
+ await this.scrollBy(0, 500);
637
+ break;
638
+ case 'record_video':
639
+ const fps = step.fps ?? 30;
640
+ await this.startVideoRecording({ fps });
641
+ break;
642
+ case 'stop_video':
643
+ const videoPath = await this.stopVideoRecording();
644
+ if (opts.verbose) {
645
+ console.log(`[Test] Video saved: ${videoPath}`);
646
+ }
647
+ break;
648
+ default:
649
+ throw new Error(`Unknown step type: ${step.type}`);
650
+ }
651
+ result.successfulSteps++;
652
+ if (opts.verbose) {
653
+ console.log(`[Test] Step ${i + 1}: Success`);
654
+ }
655
+ }
656
+ catch (error) {
657
+ result.failedSteps++;
658
+ result.success = false;
659
+ const errorMessage = error.message || String(error);
660
+ result.errors.push({
661
+ step: i + 1,
662
+ type: step.type,
663
+ message: errorMessage,
664
+ });
665
+ console.error(`[Test] Step ${i + 1} failed: ${errorMessage}`);
666
+ // Take screenshot on error if enabled
667
+ if (opts.screenshotOnError) {
668
+ try {
669
+ const errorScreenshot = `error-step-${i + 1}.png`;
670
+ await this.screenshot({ path: errorScreenshot });
671
+ console.log(`[Test] Error screenshot saved: ${errorScreenshot}`);
672
+ }
673
+ catch (screenshotError) {
674
+ console.error(`[Test] Failed to take error screenshot:`, screenshotError);
675
+ }
676
+ }
677
+ // Stop execution if continueOnError is false
678
+ if (!opts.continueOnError) {
679
+ console.error(`[Test] Stopping execution due to error`);
680
+ break;
681
+ }
682
+ }
683
+ }
684
+ result.executionTime = Date.now() - startTime;
685
+ if (opts.verbose) {
686
+ console.log(`[Test] Completed: ${testData.name}`);
687
+ console.log(`[Test] Time: ${result.executionTime}ms`);
688
+ console.log(`[Test] Success: ${result.successfulSteps}/${result.totalSteps}`);
689
+ if (result.failedSteps > 0) {
690
+ console.log(`[Test] Failed: ${result.failedSteps}`);
691
+ }
692
+ }
693
+ return result;
694
+ }
695
+ // ==================== COOKIE MANAGEMENT ====================
696
+ /**
697
+ * Get all cookies from the browser context
698
+ * @param url - Optional URL to filter cookies (returns all if not specified)
699
+ * @returns Array of cookies
700
+ *
701
+ * @example
702
+ * ```typescript
703
+ * // Get all cookies
704
+ * const allCookies = await page.getCookies();
705
+ *
706
+ * // Get cookies for a specific URL
707
+ * const siteCookies = await page.getCookies('https://example.com');
708
+ * ```
709
+ */
710
+ async getCookies(url) {
711
+ return await this.core.sendCommand('getCookies', {
712
+ context_id: this.contextId,
713
+ url: url || '',
714
+ });
715
+ }
716
+ /**
717
+ * Set a cookie in the browser context
718
+ * @param url - URL to associate with the cookie (used for domain validation)
719
+ * @param name - Cookie name
720
+ * @param value - Cookie value
721
+ * @param options - Cookie options (domain, path, secure, httpOnly, sameSite, expires)
722
+ * @returns true if cookie was set successfully
723
+ *
724
+ * @example
725
+ * ```typescript
726
+ * // Set a simple cookie
727
+ * await page.setCookie('https://example.com', 'sessionId', 'abc123');
728
+ *
729
+ * // Set a cookie with options
730
+ * await page.setCookie('https://example.com', 'prefs', 'dark-mode', {
731
+ * path: '/',
732
+ * secure: true,
733
+ * httpOnly: true,
734
+ * sameSite: 'strict',
735
+ * expires: Date.now() / 1000 + 86400 // 1 day from now
736
+ * });
737
+ * ```
738
+ */
739
+ async setCookie(url, name, value, options) {
740
+ return await this.core.sendCommand('setCookie', {
741
+ context_id: this.contextId,
742
+ url,
743
+ name,
744
+ value,
745
+ domain: options?.domain || '',
746
+ path: options?.path || '/',
747
+ secure: options?.secure || false,
748
+ httpOnly: options?.httpOnly || false,
749
+ sameSite: options?.sameSite || 'lax',
750
+ expires: options?.expires || -1,
751
+ });
752
+ }
753
+ /**
754
+ * Delete cookies from the browser context
755
+ * @param url - Optional URL to filter which cookies to delete
756
+ * @param name - Optional specific cookie name to delete
757
+ * @returns true if cookies were deleted successfully
758
+ *
759
+ * @example
760
+ * ```typescript
761
+ * // Delete all cookies
762
+ * await page.deleteCookies();
763
+ *
764
+ * // Delete cookies for a specific URL
765
+ * await page.deleteCookies('https://example.com');
766
+ *
767
+ * // Delete a specific cookie
768
+ * await page.deleteCookies('https://example.com', 'sessionId');
769
+ * ```
770
+ */
771
+ async deleteCookies(url, name) {
772
+ return await this.core.sendCommand('deleteCookies', {
773
+ context_id: this.contextId,
774
+ url: url || '',
775
+ cookie_name: name || '',
776
+ });
777
+ }
778
+ // ==================== PROXY MANAGEMENT ====================
779
+ /**
780
+ * Configure proxy settings for this browser context
781
+ * Includes stealth features to prevent proxy/VPN detection
782
+ * @param config - Proxy configuration
783
+ * @returns true if proxy was configured successfully
784
+ *
785
+ * @example
786
+ * ```typescript
787
+ * // Configure SOCKS5 proxy with stealth
788
+ * await page.setProxy({
789
+ * type: 'socks5h',
790
+ * host: 'proxy.example.com',
791
+ * port: 1080,
792
+ * username: 'user',
793
+ * password: 'pass',
794
+ * stealth: true,
795
+ * timezoneOverride: 'America/New_York'
796
+ * });
797
+ * ```
798
+ */
799
+ async setProxy(config) {
800
+ return await this.core.sendCommand('setProxy', {
801
+ context_id: this.contextId,
802
+ type: config.type,
803
+ host: config.host,
804
+ port: config.port,
805
+ username: config.username || '',
806
+ password: config.password || '',
807
+ stealth: config.stealth !== false,
808
+ block_webrtc: config.blockWebrtc !== false,
809
+ spoof_timezone: config.spoofTimezone || false,
810
+ spoof_language: config.spoofLanguage || false,
811
+ timezone_override: config.timezoneOverride || '',
812
+ language_override: config.languageOverride || '',
813
+ });
814
+ }
815
+ /**
816
+ * Get current proxy configuration and connection status
817
+ * @returns Proxy status object
818
+ *
819
+ * @example
820
+ * ```typescript
821
+ * const status = await page.getProxyStatus();
822
+ * if (status.enabled && status.connected) {
823
+ * console.log(`Connected to ${status.type}://${status.host}:${status.port}`);
824
+ * }
825
+ * ```
826
+ */
827
+ async getProxyStatus() {
828
+ return await this.core.sendCommand('getProxyStatus', {
829
+ context_id: this.contextId,
830
+ });
831
+ }
832
+ /**
833
+ * Enable/connect the configured proxy
834
+ * @returns true if proxy was connected successfully
835
+ */
836
+ async connectProxy() {
837
+ return await this.core.sendCommand('connectProxy', {
838
+ context_id: this.contextId,
839
+ });
840
+ }
841
+ /**
842
+ * Disable/disconnect the proxy, reverting to direct connection
843
+ * @returns true if proxy was disconnected successfully
844
+ */
845
+ async disconnectProxy() {
846
+ return await this.core.sendCommand('disconnectProxy', {
847
+ context_id: this.contextId,
848
+ });
849
+ }
850
+ // ==================== BROWSER PROFILE MANAGEMENT ====================
851
+ /**
852
+ * Save the current context state to a browser profile
853
+ *
854
+ * The profile includes fingerprints, cookies, and configuration settings.
855
+ * This enables persistent browser identities across sessions.
856
+ *
857
+ * @param profilePath - Path to save the profile JSON file. If not provided,
858
+ * uses the profile path set during context creation.
859
+ * @returns BrowserProfile object with the saved state
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * // Save profile to file
864
+ * const profile = await page.saveProfile('/path/to/profile.json');
865
+ * console.log(`Saved profile: ${profile.profileId}`);
866
+ * console.log(`Cookies: ${profile.cookies.length}`);
867
+ * ```
868
+ */
869
+ async saveProfile(profilePath) {
870
+ const result = await this.core.sendCommand('saveProfile', {
871
+ context_id: this.contextId,
872
+ profile_path: profilePath || '',
873
+ });
874
+ return this.parseProfile(result);
875
+ }
876
+ /**
877
+ * Get the current profile state for this context
878
+ *
879
+ * Returns the in-memory profile state without saving to disk.
880
+ *
881
+ * @returns BrowserProfile object with current state
882
+ *
883
+ * @example
884
+ * ```typescript
885
+ * const profile = await page.getProfile();
886
+ * console.log(`User Agent: ${profile.fingerprint?.userAgent}`);
887
+ * console.log(`Cookies: ${profile.cookies.length}`);
888
+ * ```
889
+ */
890
+ async getProfile() {
891
+ const result = await this.core.sendCommand('getProfile', {
892
+ context_id: this.contextId,
893
+ });
894
+ return this.parseProfile(result);
895
+ }
896
+ /**
897
+ * Update the profile file with current cookies from the browser
898
+ *
899
+ * This is useful when you want to persist cookie changes without
900
+ * saving the entire profile state.
901
+ *
902
+ * @returns true if cookies were updated successfully
903
+ *
904
+ * @example
905
+ * ```typescript
906
+ * // After logging in
907
+ * await page.goto('https://example.com/login');
908
+ * await page.type('email input', 'user@example.com');
909
+ * await page.type('password input', 'password');
910
+ * await page.click('login button');
911
+ *
912
+ * // Save the session cookies
913
+ * await page.updateProfileCookies();
914
+ * ```
915
+ */
916
+ async updateProfileCookies() {
917
+ return await this.core.sendCommand('updateProfileCookies', {
918
+ context_id: this.contextId,
919
+ });
920
+ }
921
+ /**
922
+ * Parse profile data from command result
923
+ */
924
+ parseProfile(data) {
925
+ if (typeof data === 'string') {
926
+ data = JSON.parse(data);
927
+ }
928
+ if (!data || typeof data !== 'object') {
929
+ return {
930
+ profileId: '',
931
+ profileName: '',
932
+ createdAt: '',
933
+ modifiedAt: '',
934
+ version: 1,
935
+ cookies: [],
936
+ hasLlmConfig: false,
937
+ hasProxyConfig: false,
938
+ autoSaveCookies: true,
939
+ persistLocalStorage: true,
940
+ };
941
+ }
942
+ // Parse fingerprint
943
+ let fingerprint;
944
+ const fp = data.fingerprint;
945
+ if (fp) {
946
+ fingerprint = {
947
+ userAgent: fp.user_agent || '',
948
+ platform: fp.platform || 'Win32',
949
+ vendor: fp.vendor || 'Google Inc.',
950
+ languages: fp.languages || ['en-US', 'en'],
951
+ hardwareConcurrency: fp.hardware_concurrency || 8,
952
+ deviceMemory: fp.device_memory || 8,
953
+ maxTouchPoints: fp.max_touch_points || 0,
954
+ canvasNoiseSeed: fp.canvas_noise_seed || 0,
955
+ gpuProfileIndex: fp.gpu_profile_index || 0,
956
+ webglVendor: fp.webgl_vendor || '',
957
+ webglRenderer: fp.webgl_renderer || '',
958
+ screenWidth: fp.screen_width || 1920,
959
+ screenHeight: fp.screen_height || 1080,
960
+ colorDepth: fp.color_depth || 24,
961
+ pixelRatio: fp.pixel_ratio || 1,
962
+ timezone: fp.timezone || '',
963
+ locale: fp.locale || 'en-US',
964
+ audioNoiseSeed: fp.audio_noise_seed || 0,
965
+ installedFonts: fp.installed_fonts || [],
966
+ hasPdfPlugin: fp.has_pdf_plugin !== false,
967
+ hasChromePdf: fp.has_chrome_pdf !== false,
968
+ };
969
+ }
970
+ // Parse cookies
971
+ const cookies = (data.cookies || []).map((c) => ({
972
+ name: c.name || '',
973
+ value: c.value || '',
974
+ domain: c.domain || '',
975
+ path: c.path || '/',
976
+ secure: c.secure || false,
977
+ httpOnly: c.httpOnly || false,
978
+ sameSite: c.sameSite || 'lax',
979
+ expires: c.expires || -1,
980
+ }));
981
+ return {
982
+ profileId: data.profile_id || '',
983
+ profileName: data.profile_name || '',
984
+ createdAt: data.created_at || '',
985
+ modifiedAt: data.modified_at || '',
986
+ version: data.version || 1,
987
+ fingerprint,
988
+ cookies,
989
+ hasLlmConfig: data.has_llm_config || false,
990
+ llmConfig: data.llm_config ? {
991
+ enabled: data.llm_config.enabled ?? true,
992
+ useBuiltin: data.llm_config.use_builtin ?? true,
993
+ externalEndpoint: data.llm_config.external_endpoint,
994
+ externalModel: data.llm_config.external_model,
995
+ } : undefined,
996
+ hasProxyConfig: data.has_proxy_config || false,
997
+ proxyConfig: data.proxy_config ? {
998
+ type: data.proxy_config.type || 'http',
999
+ host: data.proxy_config.host || '',
1000
+ port: data.proxy_config.port || 0,
1001
+ username: data.proxy_config.username,
1002
+ password: data.proxy_config.password,
1003
+ } : undefined,
1004
+ autoSaveCookies: data.auto_save_cookies !== false,
1005
+ persistLocalStorage: data.persist_local_storage !== false,
1006
+ };
1007
+ }
1008
+ // ==================== CLEANUP ====================
1009
+ /**
1010
+ * Close this context and release resources
1011
+ */
1012
+ async close() {
1013
+ await this.core.releaseContext(this.contextId);
1014
+ }
1015
+ }
1016
+ exports.BrowserContext = BrowserContext;
1017
+ //# sourceMappingURL=context.js.map