mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
refactor: unify timezone parameter naming across Python and JS wrappers
- Rename timezone_id → timezone in launch_context(), launch_persistent_context(), and launch_persistent_context_async() (Python) - Extract _migrate_timezone_id() helper for deprecation compat (DRY) - Always pop timezone_id from kwargs to prevent override via context_kwargs.update() - Use FutureWarning (visible by default) instead of DeprecationWarning - JS: deprecate timezoneId on LaunchContextOptions with runtime shim - Extract migrateTimezoneId<T>() shared helper in playwright.ts (DRY) - Bump version to 0.3.7 in _version.py and package.json - Add 4 Python + 4 JS unit tests for deprecation compat behavior
This commit is contained in:
@@ -6,6 +6,10 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
|
||||
---
|
||||
|
||||
## [0.3.7] — 2026-03-05
|
||||
|
||||
- **[wrapper]** Unify timezone parameter: rename `timezone_id` to `timezone` in `launch_context()`, `launch_persistent_context()`, and `launch_persistent_context_async()` (Python). Old `timezone_id` still works with a deprecation warning. JS: deprecate `timezoneId` on `LaunchContextOptions` — use `timezone` (inherited from `LaunchOptions`)
|
||||
|
||||
## [0.3.6] — 2026-03-04
|
||||
|
||||
- **[wrapper]** `proxy` parameter now accepts a Playwright proxy dict (`{server, bypass, username, password}`) in addition to URL strings — enables bypass lists and separate auth fields (PR #24). **TS note:** type changed from `string` to `string | object` — code that assumed `proxy` is always a string may need a `typeof` narrowing check
|
||||
|
||||
@@ -251,7 +251,7 @@ context = launch_context(
|
||||
user_agent="Custom UA",
|
||||
viewport={"width": 1920, "height": 1080},
|
||||
locale="en-US",
|
||||
timezone_id="America/New_York",
|
||||
timezone="America/New_York",
|
||||
)
|
||||
page = context.new_page()
|
||||
page.goto("https://protected-site.com")
|
||||
@@ -281,7 +281,7 @@ ctx.close() # profile saved
|
||||
ctx = launch_persistent_context("./my-profile", headless=False)
|
||||
```
|
||||
|
||||
Supports all the same options as `launch_context()`: `proxy`, `user_agent`, `viewport`, `locale`, `timezone_id`, `color_scheme`, `geoip`.
|
||||
Supports all the same options as `launch_context()`: `proxy`, `user_agent`, `viewport`, `locale`, `timezone`, `color_scheme`, `geoip`.
|
||||
|
||||
Async version: `launch_persistent_context_async()`.
|
||||
|
||||
@@ -327,7 +327,7 @@ const context = await launchContext({
|
||||
userAgent: 'Custom UA',
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
locale: 'en-US',
|
||||
timezoneId: 'America/New_York',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.6"
|
||||
__version__ = "0.3.7"
|
||||
|
||||
+35
-17
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
from typing import Any, Literal, TypedDict
|
||||
from urllib.parse import unquote, urlparse, urlunparse
|
||||
|
||||
@@ -25,6 +26,17 @@ from .download import ensure_binary
|
||||
logger = logging.getLogger("cloakbrowser")
|
||||
|
||||
|
||||
def _migrate_timezone_id(timezone: str | None, kwargs: dict[str, Any]) -> str | None:
|
||||
"""Pop deprecated timezone_id from kwargs, warn, return resolved timezone."""
|
||||
if "timezone_id" in kwargs:
|
||||
warnings.warn("timezone_id is deprecated, use timezone instead", FutureWarning, stacklevel=3)
|
||||
if timezone is None:
|
||||
timezone = kwargs.pop("timezone_id")
|
||||
else:
|
||||
kwargs.pop("timezone_id")
|
||||
return timezone
|
||||
|
||||
|
||||
class _ProxySettingsRequired(TypedDict):
|
||||
server: str
|
||||
|
||||
@@ -184,7 +196,7 @@ def launch_persistent_context(
|
||||
user_agent: str | None = None,
|
||||
viewport: dict | None = None,
|
||||
locale: str | None = None,
|
||||
timezone_id: str | None = None,
|
||||
timezone: str | None = None,
|
||||
color_scheme: Literal["light", "dark", "no-preference"] | None = None,
|
||||
geoip: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -206,7 +218,7 @@ def launch_persistent_context(
|
||||
user_agent: Custom user agent string.
|
||||
viewport: Viewport size dict, e.g. {"width": 1920, "height": 1080}.
|
||||
locale: Browser locale, e.g. "en-US".
|
||||
timezone_id: Timezone, e.g. "America/New_York".
|
||||
timezone: IANA timezone (e.g. 'America/New_York').
|
||||
color_scheme: Color scheme preference — 'light', 'dark', or 'no-preference'.
|
||||
Default: None (uses Chromium default, which is 'light').
|
||||
geoip: Auto-detect timezone/locale from proxy IP (default False).
|
||||
@@ -226,9 +238,11 @@ def launch_persistent_context(
|
||||
"""
|
||||
from patchright.sync_api import sync_playwright
|
||||
|
||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
||||
|
||||
binary_path = ensure_binary()
|
||||
timezone_id, locale = _maybe_resolve_geoip(geoip, proxy, timezone_id, locale)
|
||||
chrome_args = _build_args(stealth_args, args, timezone=timezone_id, locale=locale)
|
||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||
chrome_args = _build_args(stealth_args, args, timezone=timezone, locale=locale)
|
||||
|
||||
logger.debug(
|
||||
"Launching persistent stealth Chromium (headless=%s, user_data_dir=%s)",
|
||||
@@ -242,8 +256,8 @@ def launch_persistent_context(
|
||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||
if locale:
|
||||
context_kwargs["locale"] = locale
|
||||
if timezone_id:
|
||||
context_kwargs["timezone_id"] = timezone_id
|
||||
if timezone:
|
||||
context_kwargs["timezone_id"] = timezone
|
||||
if color_scheme:
|
||||
context_kwargs["color_scheme"] = color_scheme
|
||||
context_kwargs.update(kwargs)
|
||||
@@ -280,7 +294,7 @@ async def launch_persistent_context_async(
|
||||
user_agent: str | None = None,
|
||||
viewport: dict | None = None,
|
||||
locale: str | None = None,
|
||||
timezone_id: str | None = None,
|
||||
timezone: str | None = None,
|
||||
color_scheme: Literal["light", "dark", "no-preference"] | None = None,
|
||||
geoip: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -301,7 +315,7 @@ async def launch_persistent_context_async(
|
||||
user_agent: Custom user agent string.
|
||||
viewport: Viewport size dict, e.g. {"width": 1920, "height": 1080}.
|
||||
locale: Browser locale, e.g. "en-US".
|
||||
timezone_id: Timezone, e.g. "America/New_York".
|
||||
timezone: IANA timezone (e.g. 'America/New_York').
|
||||
color_scheme: Color scheme preference — 'light', 'dark', or 'no-preference'.
|
||||
geoip: Auto-detect timezone/locale from proxy IP (default False).
|
||||
**kwargs: Passed directly to playwright.chromium.launch_persistent_context().
|
||||
@@ -324,9 +338,11 @@ async def launch_persistent_context_async(
|
||||
"""
|
||||
from patchright.async_api import async_playwright
|
||||
|
||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
||||
|
||||
binary_path = ensure_binary()
|
||||
timezone_id, locale = _maybe_resolve_geoip(geoip, proxy, timezone_id, locale)
|
||||
chrome_args = _build_args(stealth_args, args, timezone=timezone_id, locale=locale)
|
||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||
chrome_args = _build_args(stealth_args, args, timezone=timezone, locale=locale)
|
||||
|
||||
logger.debug(
|
||||
"Launching persistent stealth Chromium async (headless=%s, user_data_dir=%s)",
|
||||
@@ -340,8 +356,8 @@ async def launch_persistent_context_async(
|
||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||
if locale:
|
||||
context_kwargs["locale"] = locale
|
||||
if timezone_id:
|
||||
context_kwargs["timezone_id"] = timezone_id
|
||||
if timezone:
|
||||
context_kwargs["timezone_id"] = timezone
|
||||
if color_scheme:
|
||||
context_kwargs["color_scheme"] = color_scheme
|
||||
context_kwargs.update(kwargs)
|
||||
@@ -377,7 +393,7 @@ def launch_context(
|
||||
user_agent: str | None = None,
|
||||
viewport: dict | None = None,
|
||||
locale: str | None = None,
|
||||
timezone_id: str | None = None,
|
||||
timezone: str | None = None,
|
||||
color_scheme: Literal["light", "dark", "no-preference"] | None = None,
|
||||
geoip: bool = False,
|
||||
**kwargs: Any,
|
||||
@@ -395,7 +411,7 @@ def launch_context(
|
||||
user_agent: Custom user agent string.
|
||||
viewport: Viewport size dict, e.g. {"width": 1920, "height": 1080}.
|
||||
locale: Browser locale, e.g. "en-US".
|
||||
timezone_id: Timezone, e.g. "America/New_York".
|
||||
timezone: IANA timezone (e.g. 'America/New_York').
|
||||
color_scheme: Color scheme preference — 'light', 'dark', or 'no-preference'.
|
||||
Default: None (uses Chromium default, which is 'light').
|
||||
Note: 'no-preference' doesn't work in Patchright (falls back to 'light').
|
||||
@@ -405,9 +421,11 @@ def launch_context(
|
||||
Returns:
|
||||
Playwright BrowserContext object.
|
||||
"""
|
||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
||||
|
||||
# Resolve geoip BEFORE launch() to avoid double-resolution and ensure
|
||||
# resolved values flow to both binary flags AND context params
|
||||
timezone_id, locale = _maybe_resolve_geoip(geoip, proxy, timezone_id, locale)
|
||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||
# Skip --fingerprint-timezone binary flag: it only applies to the default
|
||||
# context and interferes with Playwright's timezone_id on new contexts.
|
||||
# Timezone is set via browser.new_context(timezone_id=...) below instead.
|
||||
@@ -420,8 +438,8 @@ def launch_context(
|
||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||
if locale:
|
||||
context_kwargs["locale"] = locale
|
||||
if timezone_id:
|
||||
context_kwargs["timezone_id"] = timezone_id
|
||||
if timezone:
|
||||
context_kwargs["timezone_id"] = timezone
|
||||
if color_scheme:
|
||||
context_kwargs["color_scheme"] = color_scheme
|
||||
context_kwargs.update(kwargs)
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ const context = await launchContext({
|
||||
userAgent: 'Custom UA',
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
locale: 'en-US',
|
||||
timezoneId: 'America/New_York',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
|
||||
// Persistent profile — stay logged in, bypass incognito detection, load extensions
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloakbrowser",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.7",
|
||||
"description": "Stealth Chromium that passes every bot detection test. Drop-in Playwright/Puppeteer replacement with source-level fingerprint patches.",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -9,6 +9,17 @@ import { DEFAULT_VIEWPORT, getDefaultStealthArgs } from "./config.js";
|
||||
import { ensureBinary } from "./download.js";
|
||||
import { parseProxyUrl } from "./proxy.js";
|
||||
|
||||
/** @internal Migrate deprecated timezoneId → timezone, warn once. Exported for testing. */
|
||||
export function migrateTimezoneId<T extends { timezone?: string; timezoneId?: string }>(options: T): T {
|
||||
if (options.timezoneId != null) {
|
||||
console.warn("[cloakbrowser] timezoneId is deprecated, use timezone instead");
|
||||
const merged = { ...options, timezone: options.timezone ?? options.timezoneId };
|
||||
delete (merged as any).timezoneId;
|
||||
return merged;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch stealth Chromium browser via Playwright.
|
||||
*
|
||||
@@ -62,6 +73,7 @@ export async function launch(options: LaunchOptions = {}): Promise<Browser> {
|
||||
export async function launchContext(
|
||||
options: LaunchContextOptions = {}
|
||||
): Promise<BrowserContext> {
|
||||
options = migrateTimezoneId(options);
|
||||
// Resolve geoip BEFORE launch() to avoid double-resolution
|
||||
const resolved = await maybeResolveGeoip(options);
|
||||
// Skip --fingerprint-timezone binary flag: it only applies to the default
|
||||
@@ -117,6 +129,7 @@ export async function launchContext(
|
||||
export async function launchPersistentContext(
|
||||
options: LaunchPersistentContextOptions
|
||||
): Promise<BrowserContext> {
|
||||
options = migrateTimezoneId(options);
|
||||
const { chromium } = await import("playwright-core");
|
||||
|
||||
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export interface LaunchContextOptions extends LaunchOptions {
|
||||
viewport?: { width: number; height: number };
|
||||
/** Browser locale, e.g. "en-US". */
|
||||
locale?: string;
|
||||
/** Timezone, e.g. "America/New_York". */
|
||||
/** @deprecated Use `timezone` (inherited from LaunchOptions) instead. */
|
||||
timezoneId?: string;
|
||||
/** Color scheme preference — 'light', 'dark', or 'no-preference'. */
|
||||
colorScheme?: "light" | "dark" | "no-preference";
|
||||
|
||||
+28
-1
@@ -7,7 +7,7 @@ import {
|
||||
getBinaryDir,
|
||||
getDownloadUrl,
|
||||
} from "../src/config.js";
|
||||
import { _buildArgsForTest } from "../src/playwright.js";
|
||||
import { _buildArgsForTest, migrateTimezoneId } from "../src/playwright.js";
|
||||
|
||||
describe("config", () => {
|
||||
it("CHROMIUM_VERSION matches expected format", () => {
|
||||
@@ -98,3 +98,30 @@ describe("buildArgs timezone/locale", () => {
|
||||
expect(args.some(a => a.startsWith("--lang="))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrateTimezoneId deprecation", () => {
|
||||
it("migrates timezoneId to timezone", () => {
|
||||
const result = migrateTimezoneId({ timezoneId: "Europe/Paris" });
|
||||
expect(result.timezone).toBe("Europe/Paris");
|
||||
expect(result).not.toHaveProperty("timezoneId");
|
||||
});
|
||||
|
||||
it("preserves explicit timezone over timezoneId", () => {
|
||||
const result = migrateTimezoneId({ timezone: "UTC", timezoneId: "Europe/Paris" });
|
||||
expect(result.timezone).toBe("UTC");
|
||||
expect(result).not.toHaveProperty("timezoneId");
|
||||
});
|
||||
|
||||
it("returns options unchanged when no timezoneId", () => {
|
||||
const opts = { timezone: "UTC" };
|
||||
const result = migrateTimezoneId(opts);
|
||||
expect(result).toBe(opts); // same reference, no copy
|
||||
expect(result.timezone).toBe("UTC");
|
||||
});
|
||||
|
||||
it("returns options unchanged when neither is set", () => {
|
||||
const opts = {};
|
||||
const result = migrateTimezoneId(opts);
|
||||
expect(result).toBe(opts);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Unit tests for _build_args timezone/locale injection."""
|
||||
"""Unit tests for _build_args timezone/locale injection and deprecation compat."""
|
||||
|
||||
from cloakbrowser.browser import _build_args
|
||||
import warnings
|
||||
|
||||
from cloakbrowser.browser import _build_args, _migrate_timezone_id
|
||||
|
||||
|
||||
def test_timezone_injected():
|
||||
@@ -44,3 +46,49 @@ def test_extra_args_preserved():
|
||||
assert "--disable-gpu" in args
|
||||
assert "--fingerprint-timezone=Asia/Tokyo" in args
|
||||
assert "--lang=ja-JP" in args
|
||||
|
||||
|
||||
# --- _migrate_timezone_id deprecation compat ---
|
||||
|
||||
|
||||
def test_migrate_old_param_only():
|
||||
"""timezone_id in kwargs should be promoted to timezone."""
|
||||
kwargs = {"timezone_id": "Europe/Paris"}
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
result = _migrate_timezone_id(None, kwargs)
|
||||
assert result == "Europe/Paris"
|
||||
assert "timezone_id" not in kwargs
|
||||
assert len(w) == 1 and issubclass(w[0].category, FutureWarning)
|
||||
|
||||
|
||||
def test_migrate_new_param_wins():
|
||||
"""Explicit timezone takes precedence; timezone_id is still popped."""
|
||||
kwargs = {"timezone_id": "Europe/Paris"}
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
result = _migrate_timezone_id("UTC", kwargs)
|
||||
assert result == "UTC"
|
||||
assert "timezone_id" not in kwargs
|
||||
assert len(w) == 1
|
||||
|
||||
|
||||
def test_migrate_no_old_param():
|
||||
"""No warning when timezone_id is absent."""
|
||||
kwargs = {"other": "value"}
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
result = _migrate_timezone_id("UTC", kwargs)
|
||||
assert result == "UTC"
|
||||
assert "other" in kwargs
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_migrate_both_none():
|
||||
"""Neither param set — returns None, no warning."""
|
||||
kwargs = {}
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
result = _migrate_timezone_id(None, kwargs)
|
||||
assert result is None
|
||||
assert len(w) == 0
|
||||
|
||||
Reference in New Issue
Block a user