Skip to content

Commit ff99e64

Browse files
author
SentienceDEV
committed
Merge pull request #90 from SentienceAPI/sdk_async
add viewport to browser init
2 parents 0841a6a + f810c95 commit ff99e64

File tree

7 files changed

+385
-114
lines changed

7 files changed

+385
-114
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "sentienceapi"
7-
version = "0.90.15"
7+
version = "0.90.16"
88
description = "Python SDK for Sentience AI Agent Browser Automation"
99
readme = "README.md"
1010
requires-python = ">=3.11"

sentience/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
)
7171
from .wait import wait_for
7272

73-
__version__ = "0.90.15"
73+
__version__ = "0.90.16"
7474

7575
__all__ = [
7676
# Core SDK

sentience/browser.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(
3535
storage_state: str | Path | StorageState | dict | None = None,
3636
record_video_dir: str | Path | None = None,
3737
record_video_size: dict[str, int] | None = None,
38+
viewport: dict[str, int] | None = None,
3839
):
3940
"""
4041
Initialize Sentience browser
@@ -67,6 +68,11 @@ def __init__(
6768
Examples: {"width": 1280, "height": 800} (default)
6869
{"width": 1920, "height": 1080} (1080p)
6970
If None, defaults to 1280x800.
71+
viewport: Optional viewport size as dict with 'width' and 'height' keys.
72+
Examples: {"width": 1280, "height": 800} (default)
73+
{"width": 1920, "height": 1080} (Full HD)
74+
{"width": 375, "height": 667} (iPhone)
75+
If None, defaults to 1280x800.
7076
"""
7177
self.api_key = api_key
7278
# Only set api_url if api_key is provided, otherwise None (free tier)
@@ -94,6 +100,9 @@ def __init__(
94100
self.record_video_dir = record_video_dir
95101
self.record_video_size = record_video_size or {"width": 1280, "height": 800}
96102

103+
# Viewport configuration
104+
self.viewport = viewport or {"width": 1280, "height": 800}
105+
97106
self.playwright: Playwright | None = None
98107
self.context: BrowserContext | None = None
99108
self.page: Page | None = None
@@ -211,7 +220,7 @@ def start(self) -> None:
211220
"user_data_dir": user_data_dir,
212221
"headless": False, # IMPORTANT: See note above
213222
"args": args,
214-
"viewport": {"width": 1280, "height": 800},
223+
"viewport": self.viewport,
215224
# Remove "HeadlessChrome" from User Agent automatically
216225
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
217226
}
@@ -480,6 +489,97 @@ def close(self, output_path: str | Path | None = None) -> str | None:
480489

481490
return final_path
482491

492+
@classmethod
493+
def from_existing(
494+
cls,
495+
context: BrowserContext,
496+
api_key: str | None = None,
497+
api_url: str | None = None,
498+
) -> "SentienceBrowser":
499+
"""
500+
Create SentienceBrowser from an existing Playwright BrowserContext.
501+
502+
This allows you to use Sentience SDK with a browser context you've already created,
503+
giving you more control over browser initialization.
504+
505+
Args:
506+
context: Existing Playwright BrowserContext
507+
api_key: Optional API key for server-side processing
508+
api_url: Optional API URL (defaults to https://api.sentienceapi.com if api_key provided)
509+
510+
Returns:
511+
SentienceBrowser instance configured to use the existing context
512+
513+
Example:
514+
from playwright.sync_api import sync_playwright
515+
from sentience import SentienceBrowser, snapshot
516+
517+
with sync_playwright() as p:
518+
context = p.chromium.launch_persistent_context(...)
519+
browser = SentienceBrowser.from_existing(context)
520+
browser.page.goto("https://example.com")
521+
snap = snapshot(browser)
522+
"""
523+
instance = cls(api_key=api_key, api_url=api_url)
524+
instance.context = context
525+
instance.page = context.pages[0] if context.pages else context.new_page()
526+
527+
# Apply stealth if available
528+
if STEALTH_AVAILABLE:
529+
stealth_sync(instance.page)
530+
531+
# Wait for extension to be ready (if extension is loaded)
532+
time.sleep(0.5)
533+
534+
return instance
535+
536+
@classmethod
537+
def from_page(
538+
cls,
539+
page: Page,
540+
api_key: str | None = None,
541+
api_url: str | None = None,
542+
) -> "SentienceBrowser":
543+
"""
544+
Create SentienceBrowser from an existing Playwright Page.
545+
546+
This allows you to use Sentience SDK with a page you've already created,
547+
giving you more control over browser initialization.
548+
549+
Args:
550+
page: Existing Playwright Page
551+
api_key: Optional API key for server-side processing
552+
api_url: Optional API URL (defaults to https://api.sentienceapi.com if api_key provided)
553+
554+
Returns:
555+
SentienceBrowser instance configured to use the existing page
556+
557+
Example:
558+
from playwright.sync_api import sync_playwright
559+
from sentience import SentienceBrowser, snapshot
560+
561+
with sync_playwright() as p:
562+
browser_instance = p.chromium.launch()
563+
context = browser_instance.new_context()
564+
page = context.new_page()
565+
page.goto("https://example.com")
566+
567+
browser = SentienceBrowser.from_page(page)
568+
snap = snapshot(browser)
569+
"""
570+
instance = cls(api_key=api_key, api_url=api_url)
571+
instance.page = page
572+
instance.context = page.context
573+
574+
# Apply stealth if available
575+
if STEALTH_AVAILABLE:
576+
stealth_sync(instance.page)
577+
578+
# Wait for extension to be ready (if extension is loaded)
579+
time.sleep(0.5)
580+
581+
return instance
582+
483583
def __enter__(self):
484584
"""Context manager entry"""
485585
self.start()

sentience/extension/background.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ async function handleScreenshotCapture(_tabId, options = {}) {
144144
async function handleSnapshotProcessing(rawData, options = {}) {
145145
const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs
146146
const startTime = performance.now();
147-
147+
148148
try {
149149
// Safety check: limit element count to prevent hangs
150150
if (!Array.isArray(rawData)) {
151151
throw new Error('rawData must be an array');
152152
}
153-
153+
154154
if (rawData.length > MAX_ELEMENTS) {
155155
console.warn(`[Sentience Background] ⚠️ Large dataset: ${rawData.length} elements. Limiting to ${MAX_ELEMENTS} to prevent hangs.`);
156156
rawData = rawData.slice(0, MAX_ELEMENTS);
@@ -186,7 +186,7 @@ async function handleSnapshotProcessing(rawData, options = {}) {
186186
// Add timeout protection (18 seconds - less than content.js timeout)
187187
analyzedElements = await Promise.race([
188188
wasmPromise,
189-
new Promise((_, reject) =>
189+
new Promise((_, reject) =>
190190
setTimeout(() => reject(new Error('WASM processing timeout (>18s)')), 18000)
191191
)
192192
]);

sentience/extension/content.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function handleSnapshotRequest(data) {
9292
if (responded) return; // Already responded via timeout
9393
responded = true;
9494
clearTimeout(timeoutId);
95-
95+
9696
const duration = performance.now() - startTime;
9797

9898
// Handle Chrome extension errors (e.g., background script crashed)

0 commit comments

Comments
 (0)