Python SDK
A pure Python automation SDK independent of the desktop client. Shares the same Chrome core, sidecar, and gost proxy chain as the Desktop version.
Installation
pip install https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whlVerify:
python -c "import linege_sdk; print(linege_sdk.__version__)"
# Output: 0.3.21Core Flow
Login → Request Environment (envId) → Assign Proxy + Fingerprint → Launch Chrome → Sidecar Connection → Business Operations → Auto CleanupThe SDK automatically handles: environment creation, fingerprint distribution, gost proxy mounting, sidecar startup, and process cleanup. Users only need to focus on business logic.
Quick Start
Cloud Mode — Fully Automated
from linege_sdk import LinegeClient
client = LinegeClient(username="your_username", password="your_password")
# Check quota
profile = client.get_user_profile()
print(f"Quota: {profile['used']}/{profile['total']}")
# View available countries
countries = client.list_available_countries()
print(countries) # [{"code": "US", "proxies_available": 42, ...}, ...]
# Open browser — internally auto: request env → get envId → assign proxy + fingerprint → launch Chrome
with client.open_cloud_browser(
start_url="https://www.wikipedia.org",
preferred_countries=["US", "JP"], # Prefer proxies from these countries
) as browser:
browser.find("input#searchInput").wait().input("hello")
browser.find("button[type='submit']").click() # Use button click for form submission
print(browser.find("#firstHeading").text())When with block ends, everything is automatically cleaned up (Chrome + gost proxy + sidecar + cloud environment).
Local Mode — Specify envId + Proxy
# Create a local environment with a specified proxy, automatically mounted via gost
with client.open_local_browser(
env_id="shop-account-01",
proxy="socks5://user:pass@proxy-host:1080",
start_url="https://example.com",
) as browser:
print(browser.find("h1").text())Notes:
- When a Chrome core already exists locally, local mode does not require a cloud account
- If a country is passed to fetch cloud fingerprints, or the core is not installed locally, account credentials are still required
Local Mode — Reuse Existing Environments
from linege_sdk import list_local_environments
# List all local environments (same as shown in the desktop client)
envs = list_local_environments()
for env in envs:
print(f"{env['id']} proxy={env['proxy']} country={env['country']}")
# Launch using an existing environment's envId
target = envs[0]
with client.open_local_browser(
env_id=target["id"],
proxy=target["proxy"] or None,
start_url="https://example.com",
) as browser:
print(browser.find("h1").text())Finding Elements
Three methods, returning lazy Element objects (DOM is queried only when an action is called):
| Method | Purpose | Example |
|---|---|---|
find(selector) | CSS selector | browser.find("input#email") |
find_by_xpath(xpath) | XPath selector | browser.find_by_xpath("//div[@id='main']") |
find_by_text(text) | Find by visible text | browser.find_by_text("Login") |
find_by_text — Resilient to DOM Structure Changes
# Restrict to a specific tag
browser.find_by_text("Submit", tag="button").click()
# Exact match
browser.find_by_text("Login", exact=True).click()
# Multi-language candidates — matches whichever language the page displays
browser.find_by_text(["Login", "登录", "ログイン"], tag="button").click()| Parameter | Default | Description |
|---|---|---|
text | Required | String or list of strings (multi-language candidates) |
tag | "*" | Restrict to tag: "button" / "a" / "span" etc. |
exact | False | True = exact match, False = contains match |
Element Chaining
| Method | Action | Returns |
|---|---|---|
.click() | Human-like click (Bézier curve) | Element |
.input(text) | Human-like input (TypeText real keyboard) | Element |
.press_key(key) | Keyboard key press | Element |
.scroll(delta_y) | Scroll | Element |
.text() | Read text content | Optional[str] |
.wait(state, timeout) | Wait for element | Element |
.attribute(name) | Read attribute | Optional[str] |
.is_visible() | Check visibility | bool |
browser.find("input#email").wait().input("user@example.com")
browser.find("input#password").click().input("secret123")
browser.find("button[type='submit']").click() # Use button click for form submission
title = browser.find("h1").text()Browser API
| Method | Action | Example |
|---|---|---|
navigate(url) | Legacy behavior: initiate navigation (does not wait by default) | browser.navigate("https://example.com") |
goto(url) | Recommended: navigate and wait for page ready / URL match | browser.goto("https://polymarket.com", match="host") |
wait_for_url(url) | Wait for URL match (exact/prefix/contains/host) | browser.wait_for_url("https://example.com", match="host") |
scroll(delta_y) | Scroll | browser.scroll(delta_y=500) |
eval_js(expr) | Execute JS and return result | browser.eval_js("document.title") |
click(selector) | Direct click (configurable required) | browser.click("#btn", required=False) |
text(selector) | Direct text read (configurable required) | browser.text("#title", required=False) |
input(selector, value) | Direct input | browser.input("#email", "a@b.com") |
press_key(key) | Keyboard key press (regular characters) | browser.press_key("a") |
new_tab(url) | Open a new tab and return its index | idx = browser.new_tab("https://outlook.live.com") |
list_tabs() | View tab summary (count/active) | browser.list_tabs() |
switch_tab(index) | Switch to a specific tab | browser.switch_tab(idx) |
close_tab(index) | Close a specific tab | browser.close_tab(idx) |
Stable Navigation Mode (Recommended)
navigate() retains legacy behavior for backward compatibility (only initiates navigation, does not block and wait by default).
For client scripts, it is recommended to use:
# Navigate + wait + URL verification (host mode is ideal for anti-bot redirect scenarios)
browser.goto(
"https://polymarket.com/event/bitcoin-above-on-march-4",
timeout=45,
wait_until="domcontentloaded",
ensure_url=True,
match="host",
)If you prefer the legacy syntax, you can explicitly enable waiting:
browser.navigate(
"https://polymarket.com/event/bitcoin-above-on-march-4",
wait=True,
ensure_url=True,
match="host",
)Multi-Tab API (Cross-Site Switching Within the Same Environment)
# Main tab: Polymarket
browser.goto("https://polymarket.com", match="host")
# Open new tab: Outlook
outlook_tab = browser.new_tab("https://outlook.live.com")
browser.switch_tab(outlook_tab)
browser.wait_for_url("https://outlook.live.com", match="host")
# Switch back to main tab
browser.switch_tab(0)
browser.wait_for_url("https://polymarket.com", match="host")eval_js — Complex DOM Interactions
When find_by_text cannot precisely match (e.g., React nested text, buttons inside modals), use eval_js to directly manipulate the DOM:
# Precisely click a button inside a modal (scoped to [role=dialog])
result = browser.eval_js("""
(function() {
var dialog = document.querySelector('[role=dialog]');
if (!dialog) return null;
var btns = dialog.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
if (btns[i].textContent.trim() === 'Continue' && !btns[i].disabled) {
btns[i].click();
return 'OK';
}
}
return 'not found';
})()
""")Design Principles
| Principle | Description |
|---|---|
Use .click() for form submission | Click the submit button instead of press_key("Enter") |
| Keyboard for regular characters only | press_key is only for letters/numbers/symbols |
Prefer .wait() | Use instead of time.sleep(), continues immediately when element is ready |
Prefer goto() for navigation | navigate is only for backward compatibility; use goto in business scripts |
| Prefer tab API for cross-site switching | Use new_tab/switch_tab within the same environment to manage tabs |
Use eval_js for complex DOM | Precisely locate buttons inside modals, React nested text, etc. via JS |
Full Example — Object-Oriented Multi-Instance Business Flow
example_full.py is now an OOP orchestration script for real-world business scenarios:
- Account preflight checks (quota / country / core)
- Cloud business flow (auto environment + proxy + data collection)
- Local business flow (fixed envId workspace)
- Multi-instance parallel monitoring (3 instances with independent navigation)
- Error guardrails (silent mode and strict mode)
Only requires username and password by default, with no dependency on email verification codes or other conditional branches.
Full source code (download from site): example_full.py
Complete source code (same as downloadable file, auto-rendered):
"""
Phantomshield SDK Full Example (Object-Oriented + Multi-Instance Real Business Flow)
=================================================
Purpose:
- For business developers: log in with username/password, then run a complete business orchestration
- OOP structure: one orchestration class manages preflight, business flows, multi-instance, and error guardrails
- No email verification or conditional branches — only requires username and password
Usage:
python examples/example_full.py --username alice --password secret
LINEGE_USERNAME=alice LINEGE_PASSWORD=secret python examples/example_full.py
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import uuid
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from typing import Any, Callable, Dict, List, Optional
from linege_sdk import LinegeClient, LinegeSDKError, list_local_environments
def now_iso() -> str:
return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")
def jlog(trace_id: str, event: str, status: str = "OK", **payload: Any) -> None:
print(
json.dumps(
{
"ts": now_iso(),
"trace_id": trace_id,
"event": event,
"status": status,
**payload,
},
ensure_ascii=False,
)
)
@dataclass
class MultiInstanceTask:
env_id: str
start_url: str
next_url: str
@dataclass
class MultiInstanceResult:
env_id: str
start_url: str
title_before: str
url_before: str
title_after: str
url_after: str
class FullBusinessWorkflow:
def __init__(self, username: str, password: str) -> None:
self.trace_id = f"full-{uuid.uuid4().hex[:10]}"
self.client = LinegeClient(username=username, password=password)
self.report: Dict[str, Any] = {
"trace_id": self.trace_id,
"status": "running",
"steps": [],
"artifacts": {},
}
def run(self) -> int:
jlog(self.trace_id, "flow.start")
try:
self._run_step("preflight.profile", self._preflight_profile, required=True)
self._run_step("preflight.core", self._preflight_core, required=True)
self._run_step("preflight.countries", self._preflight_countries, required=True)
self._run_step("preflight.local_envs", self._preflight_local_envs, required=False)
self._run_step("biz.cloud_research", self._biz_cloud_research, required=True)
self._run_step("biz.local_workspace", self._biz_local_workspace, required=True)
self._run_step("biz.multi_instance_watch", self._biz_multi_instance_watch, required=True)
self._run_step("biz.error_guardrail", self._biz_error_guardrail, required=True)
self.report["status"] = "ok"
jlog(self.trace_id, "flow.success", step_count=len(self.report["steps"]))
return 0
except LinegeSDKError as exc:
self.report["status"] = "failed"
self.report["error"] = {
"type": "LinegeSDKError",
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
"detail": exc.detail,
}
jlog(self.trace_id, "flow.failed", status="ERROR", code=exc.code, message=exc.message)
return 1
except Exception as exc:
self.report["status"] = "failed"
self.report["error"] = {
"type": type(exc).__name__,
"message": str(exc),
}
jlog(self.trace_id, "flow.failed", status="ERROR", error_type=type(exc).__name__, message=str(exc))
return 1
finally:
try:
self.client.close_all()
except Exception:
pass
jlog(self.trace_id, "flow.finish", status=self.report["status"])
print(json.dumps(self.report, ensure_ascii=False, indent=2))
def _run_step(self, name: str, fn: Callable[[], Any], required: bool) -> Any:
started = time.perf_counter()
try:
value = fn()
elapsed_ms = round((time.perf_counter() - started) * 1000)
step_record = {
"name": name,
"required": required,
"ok": True,
"elapsed_ms": elapsed_ms,
"value": value,
}
self.report["steps"].append(step_record)
jlog(self.trace_id, "step.ok", step=name, elapsed_ms=elapsed_ms)
return value
except Exception as exc:
elapsed_ms = round((time.perf_counter() - started) * 1000)
step_record = {
"name": name,
"required": required,
"ok": False,
"elapsed_ms": elapsed_ms,
"error": str(exc),
}
self.report["steps"].append(step_record)
jlog(self.trace_id, "step.fail", status="ERROR", step=name, elapsed_ms=elapsed_ms, error=str(exc))
if required:
raise
return None
def _preflight_profile(self) -> Dict[str, Any]:
profile = self.client.get_user_profile()
return {
"username": profile["username"],
"quota": {
"used": profile["used"],
"total": profile["total"],
"remaining": profile["remaining"],
},
}
def _preflight_core(self) -> Dict[str, Any]:
core = self.client.ensure_core_latest()
return {
"updated": core["updated"],
"local_version": core["local_version"],
"remote_version": core["remote_version"],
}
def _preflight_countries(self) -> Dict[str, Any]:
countries = self.client.list_available_countries(preferred=["US", "JP"])
top = [
{
"code": c["code"],
"name": c["name"],
"proxies_available": c["proxies_available"],
"preferred": c["preferred"],
}
for c in countries[:8]
]
return {
"count": len(countries),
"top": top,
}
def _preflight_local_envs(self) -> Dict[str, Any]:
envs = list_local_environments()
return {
"count": len(envs),
"sample": [
{
"id": row.get("id"),
"proxy": bool(row.get("proxy")),
"country": row.get("country") or "-",
}
for row in envs[:8]
],
}
def _biz_cloud_research(self) -> Dict[str, Any]:
with self.client.open_cloud_browser(
start_url="https://www.wikipedia.org",
preferred_countries=["US", "JP"],
) as browser:
query = "Browser automation"
browser.find("input#searchInput").wait(timeout=30).input(query)
browser.find("button[type='submit'], input.searchButton, button.pure-button").click()
heading = browser.find("#firstHeading").wait(timeout=30).text() or ""
summary = (
browser.find_by_xpath(
"//div[contains(@class,'mw-parser-output')]//p[string-length(normalize-space()) > 30][1]"
).text()
or ""
)
refs_visible = browser.find_by_text(["References", "参考文献", "脚注"], tag="span").is_visible()
browser.find("body").scroll(delta_y=500)
result = {
"env_id": browser.env.env_id,
"mode": browser.env.mode,
"country": getattr(browser.env, "country", None),
"title": browser.eval_js("document.title"),
"url": browser.eval_js("window.location.href"),
"heading": heading,
"summary_preview": summary[:160],
"references_visible": refs_visible,
}
self.report["artifacts"]["cloud_env_id"] = browser.env.env_id
return result
def _biz_local_workspace(self) -> Dict[str, Any]:
with self.client.open_local_browser(
env_id="biz-workspace-main",
start_url="https://example.com",
) as browser:
homepage_h1 = browser.find("h1").wait(timeout=20).text() or ""
homepage_link = browser.find("a").wait(timeout=10).attribute("href")
homepage_visible = browser.find("h1").is_visible()
browser.goto("https://www.wikipedia.org", match="host")
input_box = browser.find("input#searchInput").wait(timeout=30)
input_box.click()
input_box.press_key("L")
browser.input("input#searchInput", "inege SDK", human=True)
browser.find("button[type='submit'], input.searchButton, button.pure-button").click()
browser.find("#firstHeading").wait(timeout=30)
# Multi-tab business flow: switch to Outlook in the same environment, then back to the main site
outlook_tab = browser.new_tab("https://outlook.live.com", activate=True)
browser.switch_tab(outlook_tab)
outlook_hosts = [
"https://outlook.live.com",
"https://login.live.com",
"https://www.microsoft.com",
]
outlook_ready = browser.wait_for_url(
outlook_hosts,
match="host",
timeout=30,
required=False,
)
tabs_after_open = browser.list_tabs()
browser.switch_tab(0)
wiki_ready = browser.wait_for_url("https://wikipedia.org", match="host", timeout=30, required=False)
browser.find("body").scroll(delta_y=600)
result_title = browser.find("#firstHeading").text() or ""
page_title = browser.eval_js("document.title")
page_url = browser.eval_js("window.location.href")
return {
"env_id": browser.env.env_id,
"mode": browser.env.mode,
"homepage_h1": homepage_h1,
"homepage_link": homepage_link,
"homepage_visible": homepage_visible,
"result_title": result_title,
"page_title": page_title,
"page_url": page_url,
"outlook_tab_index": outlook_tab,
"outlook_ready": outlook_ready,
"wiki_ready_after_switch": wiki_ready,
"tabs_after_open": tabs_after_open,
}
def _biz_multi_instance_watch(self) -> List[Dict[str, Any]]:
tasks = [
MultiInstanceTask(env_id="watch-us", start_url="https://example.com", next_url="https://www.wikipedia.org"),
MultiInstanceTask(env_id="watch-jp", start_url="https://www.wikipedia.org", next_url="https://httpbin.org/html"),
MultiInstanceTask(env_id="watch-eu", start_url="https://httpbin.org/html", next_url="https://example.com"),
]
sessions: List[tuple[MultiInstanceTask, Any]] = []
results: List[MultiInstanceResult] = []
try:
for task in tasks:
session = self.client.open_local_browser(env_id=task.env_id, start_url=task.start_url)
sessions.append((task, session))
for task, session in sessions:
session.wait("h1, h2, body", timeout=30, required=False)
title_before = str(session.eval_js("document.title", required=False) or "")
url_before = str(session.eval_js("window.location.href", required=False) or "")
session.goto(task.next_url, timeout=30, ensure_url=True, match="host", required=False)
session.wait("h1, h2, body", timeout=30, required=False)
title_after = str(session.eval_js("document.title", required=False) or "")
url_after = str(session.eval_js("window.location.href", required=False) or "")
results.append(
MultiInstanceResult(
env_id=task.env_id,
start_url=task.start_url,
title_before=title_before,
url_before=url_before,
title_after=title_after,
url_after=url_after,
)
)
finally:
for _, session in sessions:
try:
session.close()
except Exception:
pass
return [asdict(row) for row in results]
def _biz_error_guardrail(self) -> Dict[str, Any]:
bad_auth: Dict[str, Any] = {}
try:
LinegeClient(username="wrong_user", password="bad_password").get_user_profile()
except LinegeSDKError as exc:
bad_auth = {
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
}
with self.client.open_local_browser(
env_id="guardrail-silent",
start_url="https://example.com",
) as browser:
browser.find("h1").wait(timeout=10)
silent_click = browser.click("#nonexistent-element", required=False, timeout=3)
silent_text = browser.text("#nonexistent-element", required=False, timeout=3)
strict_error: Dict[str, Any] = {}
try:
with self.client.open_local_browser(
env_id="guardrail-strict",
start_url="https://example.com",
) as browser:
browser.find("h1").wait(timeout=10)
browser.click("#nonexistent-element", required=True, timeout=3)
except LinegeSDKError as exc:
strict_error = {
"code": exc.code,
"message": exc.message,
"hint": exc.hint,
}
return {
"bad_auth": bad_auth,
"silent_mode": {
"click_result": silent_click,
"text_result": silent_text,
},
"strict_mode": strict_error,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Phantomshield SDK Object-Oriented Multi-Instance Full Business Example")
parser.add_argument("--username", default=os.environ.get("LINEGE_USERNAME", ""))
parser.add_argument("--password", default=os.environ.get("LINEGE_PASSWORD", ""))
return parser.parse_args()
def main() -> int:
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
args = parse_args()
if not args.username or not args.password:
print("Missing credentials: please set --username/--password or LINEGE_USERNAME/LINEGE_PASSWORD")
return 2
workflow = FullBusinessWorkflow(username=args.username, password=args.password)
return workflow.run()
if __name__ == "__main__":
raise SystemExit(main())External Machine Verification Process (Bare Metal)
# 1) Create an isolated environment
python -m venv .venv
# Windows
.venv\Scripts\python -m pip install --upgrade pip
.venv\Scripts\python -m pip install "https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whl"
# 2) Download the full example script (from site)
powershell -Command "Invoke-WebRequest -Uri https://docs.sybilslayer.com/examples/example_full.py -OutFile example_full.py"
# 3) Set credentials and run the full example (username/password mode)
set LINEGE_USERNAME=your_username
set LINEGE_PASSWORD=your_password
.venv\Scripts\python example_full.pyExpected results:
- Outputs structured JSON logs (step.ok / step.fail)
- Shows flow.success upon normal completion
- Exit code is 0
LinegeClient Parameters
client = LinegeClient(
username="...", # or token="..."
password="...",
auto_cleanup_cloud_env=True, # Auto-delete cloud env when with block ends
enable_logging=True, # Structured JSON logging
sidecar_base_port=7788, # Auto-increments if port is occupied
max_startup_retries=1, # Retry count after startup failure
desktop_compatible=False, # True to use desktop-compatible presets
chrome_path=None, # Custom Chrome path
render_mode="software", # "software" or "gpu"
http_cache_mode="isolated", # "isolated" / "global" / "off"
shared_http_cache_root=None, # Shared HTTP cache directory
shared_http_cache_size_bytes=None, # Shared cache size (bytes)
restore_last_session=None, # Restore last session (defaults follow desktop_compatible)
)| Parameter | Type | Default | Description |
|---|---|---|---|
username | str | None | Username (mutually exclusive with token) |
password | str | None | Password |
token | str | None | Token (mutually exclusive with username/password) |
api_url | str | "https://api.sybilslayer.com" | API URL |
sidecar_base_port | int | 7788 | Sidecar starting port, auto-increments if occupied |
timeout | float | 20.0 | API request timeout (seconds) |
auto_cleanup_cloud_env | bool | True | Auto-delete cloud env when with block ends |
enable_logging | bool | True | Structured JSON logging |
max_startup_retries | int | 1 | Retry count after browser startup failure |
desktop_compatible | bool | False | Enable desktop-compatible presets (render_mode/cache/session, etc.) |
chrome_path | str | None | Custom Chrome executable path |
render_mode | str | "software" | Render mode: "software" or "gpu" |
http_cache_mode | str | "isolated" | HTTP cache mode: "isolated" / "global" / "off" |
shared_http_cache_root | str | None | Shared HTTP cache root directory |
shared_http_cache_size_bytes | int | None | Shared cache size limit (bytes) |
restore_last_session | bool | None | Restore last session; None follows desktop_compatible |
Instance Methods
| Method | Description |
|---|---|
get_user_profile() | Returns {username, used, total, remaining} |
ensure_core_latest() | Ensures Chrome core is up to date (auto-downloads on first use) |
list_available_countries() | Queries the list of available cloud countries |
open_cloud_browser(...) | Cloud mode (auto request env + proxy + fingerprint) |
open_local_browser(...) | Local mode (specify/auto-generate envId, optional proxy) |
open_cloud_browser Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
start_url | str | "about:blank" | URL to open after launch |
preferred_countries | List[str] | None | Preferred country code list |
sidecar_port | int | None | Specify sidecar port (None for auto-assign) |
env_name_prefix | str | "sdk-biz" | Cloud environment name prefix |
attempts_per_country | int | 2 | Attempts per country |
startup_retries | int | None | Startup retries (None uses constructor's max_startup_retries) |
desktop_compatible | bool | None | Override constructor's desktop_compatible |
chrome_path | str | None | Override constructor's chrome_path |
render_mode | str | None | Override constructor's render_mode |
http_cache_mode | str | None | Override constructor's http_cache_mode |
shared_http_cache_root | str | None | Override constructor's shared_http_cache_root |
shared_http_cache_size_bytes | int | None | Override constructor's cache size |
restore_last_session | bool | None | Override constructor's restore_last_session |
open_local_browser Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
start_url | str | "about:blank" | URL to open after launch |
env_id | str | None | Environment ID (None auto-generates local-{timestamp}-{hex}) |
proxy | str | None | Proxy URL (e.g., socks5://user:pass@host:port) |
sidecar_port | int | None | Specify sidecar port (None for auto-assign) |
env_name | str | "" | Environment display name |
country | str | "" | Country code (non-empty auto-fetches corresponding fingerprint from cloud) |
startup_retries | int | None | Startup retries (None uses constructor's max_startup_retries) |
desktop_compatible | bool | None | Override constructor's desktop_compatible |
chrome_path | str | None | Override constructor's chrome_path |
render_mode | str | None | Override constructor's render_mode |
http_cache_mode | str | None | Override constructor's http_cache_mode |
shared_http_cache_root | str | None | Override constructor's shared_http_cache_root |
shared_http_cache_size_bytes | int | None | Override constructor's cache size |
restore_last_session | bool | None | Override constructor's restore_last_session |
Standalone Functions
| Function | Description |
|---|---|
list_local_environments() | List all local environments (same as desktop client) |
create_local_environment(env_id, ...) | Manually create a local environment directory |
create_local_environment Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
env_id | str | Required | Unique environment identifier |
profile | Dict | None | Fingerprint profile config dict (None auto-generates minimal default) |
proxy | str | None | Proxy URL |
name | str | "" | Environment display name |
country | str | "" | Country code |
base_data_dir | str | None | Custom Linege data root directory |
get_user_profile Return Value
{
"username": "your_username",
"used": 5,
"total": 50,
"remaining": 45,
}list_available_countries Return Value
Returns a list of countries sorted by availability, each containing:
| Field | Type | Description |
|---|---|---|
code | str | Country code (e.g., "US") |
name | str | Country name |
proxies_available | int | Number of available proxies |
fingerprints | int | Number of available fingerprints |
preferred | bool | Whether it's in the preferred list |
GUI Compatibility Mode
When you need to replicate the login state/balance/page state from the desktop client GUI, it is recommended to:
- Reuse the same env_id
- Enable
desktop_compatible=True - Explicitly pass the same
chrome_pathused by the GUI - Use
start_url="about:blank"+restore_last_session=Truewhen restoring a previous session - If the GUI has shared cache enabled, also pass
shared_http_cache_root/shared_http_cache_size_bytes
Troubleshooting
| Issue | Solution |
|---|---|
| No Chrome core on first run | client.ensure_core_latest() auto-downloads |
| Port conflict | SDK auto-increments; or set sidecar_base_port=8899 |
| eval_js timeout | Verify Chrome has started and connected to sidecar |
navigate appears not to redirect | Use browser.goto(..., match="host"), or navigate(wait=True, ensure_url=True) |
| Need cross-site switching in the same environment (Polymarket ↔ Outlook) | Use new_tab/list_tabs/switch_tab/close_tab, don't rely on manual tab clicks |
Outlook opens but not on outlook.live.com | May redirect to login.live.com or microsoft.com; pass candidate host list to wait_for_url with required=False |
find_by_text can't match React buttons | React component text may be in nested <span>; use eval_js + textContent.trim() instead |
| Button click misaligned inside modals | Scope eval_js to document.querySelector('[role=dialog]') |
| Elements not ready after SPA page load | Use .wait(timeout=30) or appropriate time.sleep() to wait for initial render |