mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
fix: use binary flags for timezone/locale instead of detectable CDP emulation
- Remove locale and timezone_id from Playwright context kwargs (CDP) - Pass timezone via --fingerprint-timezone binary flag (process-wide) - Pass locale via --lang + --fingerprint-locale binary flags - Accept both timezone and timezone_id param names silently (no deprecation) - Update all wrapper tests to verify binary args, not CDP context params
This commit is contained in:
+19
-29
@@ -16,7 +16,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import warnings
|
|
||||||
from typing import Any, Literal, TypedDict
|
from typing import Any, Literal, TypedDict
|
||||||
from urllib.parse import unquote, urlparse, urlunparse
|
from urllib.parse import unquote, urlparse, urlunparse
|
||||||
|
|
||||||
@@ -26,10 +25,9 @@ from .download import ensure_binary
|
|||||||
logger = logging.getLogger("cloakbrowser")
|
logger = logging.getLogger("cloakbrowser")
|
||||||
|
|
||||||
|
|
||||||
def _migrate_timezone_id(timezone: str | None, kwargs: dict[str, Any]) -> str | None:
|
def _resolve_timezone(timezone: str | None, kwargs: dict[str, Any]) -> str | None:
|
||||||
"""Pop deprecated timezone_id from kwargs, warn, return resolved timezone."""
|
"""Accept both timezone and timezone_id — either works, no warning."""
|
||||||
if "timezone_id" in kwargs:
|
if "timezone_id" in kwargs:
|
||||||
warnings.warn("timezone_id is deprecated, use timezone instead", FutureWarning, stacklevel=3)
|
|
||||||
if timezone is None:
|
if timezone is None:
|
||||||
timezone = kwargs.pop("timezone_id")
|
timezone = kwargs.pop("timezone_id")
|
||||||
else:
|
else:
|
||||||
@@ -279,7 +277,7 @@ def launch_persistent_context(
|
|||||||
"""
|
"""
|
||||||
sync_playwright = _import_sync_playwright(_resolve_backend(backend))
|
sync_playwright = _import_sync_playwright(_resolve_backend(backend))
|
||||||
|
|
||||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
timezone = _resolve_timezone(timezone, kwargs)
|
||||||
|
|
||||||
binary_path = ensure_binary()
|
binary_path = ensure_binary()
|
||||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||||
@@ -291,14 +289,12 @@ def launch_persistent_context(
|
|||||||
user_data_dir,
|
user_data_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# locale and timezone are set via binary flags (--lang, --fingerprint-timezone)
|
||||||
|
# — NOT via Playwright context kwargs which use detectable CDP emulation.
|
||||||
context_kwargs: dict[str, Any] = {}
|
context_kwargs: dict[str, Any] = {}
|
||||||
if user_agent:
|
if user_agent:
|
||||||
context_kwargs["user_agent"] = user_agent
|
context_kwargs["user_agent"] = user_agent
|
||||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||||
if locale:
|
|
||||||
context_kwargs["locale"] = locale
|
|
||||||
if timezone:
|
|
||||||
context_kwargs["timezone_id"] = timezone
|
|
||||||
if color_scheme:
|
if color_scheme:
|
||||||
context_kwargs["color_scheme"] = color_scheme
|
context_kwargs["color_scheme"] = color_scheme
|
||||||
context_kwargs.update(kwargs)
|
context_kwargs.update(kwargs)
|
||||||
@@ -394,7 +390,7 @@ async def launch_persistent_context_async(
|
|||||||
"""
|
"""
|
||||||
async_playwright = _import_async_playwright(_resolve_backend(backend))
|
async_playwright = _import_async_playwright(_resolve_backend(backend))
|
||||||
|
|
||||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
timezone = _resolve_timezone(timezone, kwargs)
|
||||||
|
|
||||||
binary_path = ensure_binary()
|
binary_path = ensure_binary()
|
||||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||||
@@ -406,14 +402,12 @@ async def launch_persistent_context_async(
|
|||||||
user_data_dir,
|
user_data_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# locale and timezone are set via binary flags (--lang, --fingerprint-timezone)
|
||||||
|
# — NOT via Playwright context kwargs which use detectable CDP emulation.
|
||||||
context_kwargs: dict[str, Any] = {}
|
context_kwargs: dict[str, Any] = {}
|
||||||
if user_agent:
|
if user_agent:
|
||||||
context_kwargs["user_agent"] = user_agent
|
context_kwargs["user_agent"] = user_agent
|
||||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||||
if locale:
|
|
||||||
context_kwargs["locale"] = locale
|
|
||||||
if timezone:
|
|
||||||
context_kwargs["timezone_id"] = timezone
|
|
||||||
if color_scheme:
|
if color_scheme:
|
||||||
context_kwargs["color_scheme"] = color_scheme
|
context_kwargs["color_scheme"] = color_scheme
|
||||||
context_kwargs.update(kwargs)
|
context_kwargs.update(kwargs)
|
||||||
@@ -491,25 +485,21 @@ def launch_context(
|
|||||||
Returns:
|
Returns:
|
||||||
Playwright BrowserContext object.
|
Playwright BrowserContext object.
|
||||||
"""
|
"""
|
||||||
timezone = _migrate_timezone_id(timezone, kwargs)
|
timezone = _resolve_timezone(timezone, kwargs)
|
||||||
|
|
||||||
# Resolve geoip BEFORE launch() to avoid double-resolution and ensure
|
# Resolve geoip BEFORE launch() to avoid double-resolution and ensure
|
||||||
# resolved values flow to both binary flags AND context params
|
# resolved values flow to binary flags
|
||||||
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
timezone, locale = _maybe_resolve_geoip(geoip, proxy, timezone, locale)
|
||||||
# Skip --fingerprint-timezone binary flag: it only applies to the default
|
# --fingerprint-timezone is process-wide (reads CommandLine in renderer),
|
||||||
# context and interferes with Playwright's timezone_id on new contexts.
|
# so it applies to ALL contexts, not just the default one.
|
||||||
# Timezone is set via browser.new_context(timezone_id=...) below instead.
|
# locale and timezone are set via binary flags only — no CDP emulation.
|
||||||
browser = launch(headless=headless, proxy=proxy, args=args, stealth_args=stealth_args,
|
browser = launch(headless=headless, proxy=proxy, args=args, stealth_args=stealth_args,
|
||||||
timezone=None, locale=locale, backend=backend)
|
timezone=timezone, locale=locale, backend=backend)
|
||||||
|
|
||||||
context_kwargs: dict[str, Any] = {}
|
context_kwargs: dict[str, Any] = {}
|
||||||
if user_agent:
|
if user_agent:
|
||||||
context_kwargs["user_agent"] = user_agent
|
context_kwargs["user_agent"] = user_agent
|
||||||
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
context_kwargs["viewport"] = viewport or DEFAULT_VIEWPORT
|
||||||
if locale:
|
|
||||||
context_kwargs["locale"] = locale
|
|
||||||
if timezone:
|
|
||||||
context_kwargs["timezone_id"] = timezone
|
|
||||||
if color_scheme:
|
if color_scheme:
|
||||||
context_kwargs["color_scheme"] = color_scheme
|
context_kwargs["color_scheme"] = color_scheme
|
||||||
context_kwargs.update(kwargs)
|
context_kwargs.update(kwargs)
|
||||||
@@ -646,11 +636,11 @@ def _build_args(
|
|||||||
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
||||||
seen[key] = flag
|
seen[key] = flag
|
||||||
if locale:
|
if locale:
|
||||||
key = "--lang"
|
for key in ("--lang", "--fingerprint-locale"):
|
||||||
flag = f"{key}={locale}"
|
flag = f"{key}={locale}"
|
||||||
if key in seen:
|
if key in seen:
|
||||||
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
logger.debug("Arg override: %s -> %s", seen[key], flag)
|
||||||
seen[key] = flag
|
seen[key] = flag
|
||||||
|
|
||||||
return list(seen.values())
|
return list(seen.values())
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -38,12 +38,13 @@ export function buildArgs(options: LaunchOptions): string[] {
|
|||||||
seen.set(key, flag);
|
seen.set(key, flag);
|
||||||
}
|
}
|
||||||
if (options.locale) {
|
if (options.locale) {
|
||||||
const key = "--lang";
|
for (const k of ["--lang", "--fingerprint-locale"] as const) {
|
||||||
const flag = `${key}=${options.locale}`;
|
const flag = `${k}=${options.locale}`;
|
||||||
if (seen.has(key)) {
|
if (seen.has(k)) {
|
||||||
if (DEBUG) console.debug(`[cloakbrowser] Arg override: ${seen.get(key)} -> ${flag}`);
|
if (DEBUG) console.debug(`[cloakbrowser] Arg override: ${seen.get(k)} -> ${flag}`);
|
||||||
|
}
|
||||||
|
seen.set(k, flag);
|
||||||
}
|
}
|
||||||
seen.set(key, flag);
|
|
||||||
}
|
}
|
||||||
return [...seen.values()];
|
return [...seen.values()];
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-13
@@ -11,10 +11,9 @@ import { ensureBinary } from "./download.js";
|
|||||||
import { parseProxyUrl } from "./proxy.js";
|
import { parseProxyUrl } from "./proxy.js";
|
||||||
import { maybeResolveGeoip } from "./geoip.js";
|
import { maybeResolveGeoip } from "./geoip.js";
|
||||||
|
|
||||||
/** @internal Migrate deprecated timezoneId → timezone, warn once. Exported for testing. */
|
/** @internal Accept both timezone and timezoneId — either works, no warning. Exported for testing. */
|
||||||
export function migrateTimezoneId<T extends { timezone?: string; timezoneId?: string }>(options: T): T {
|
export function resolveTimezone<T extends { timezone?: string; timezoneId?: string }>(options: T): T {
|
||||||
if (options.timezoneId != null) {
|
if (options.timezoneId != null) {
|
||||||
console.warn("[cloakbrowser] timezoneId is deprecated, use timezone instead");
|
|
||||||
const merged = { ...options, timezone: options.timezone ?? options.timezoneId };
|
const merged = { ...options, timezone: options.timezone ?? options.timezoneId };
|
||||||
delete (merged as any).timezoneId;
|
delete (merged as any).timezoneId;
|
||||||
return merged;
|
return merged;
|
||||||
@@ -86,21 +85,19 @@ export async function launch(options: LaunchOptions = {}): Promise<Browser> {
|
|||||||
export async function launchContext(
|
export async function launchContext(
|
||||||
options: LaunchContextOptions = {}
|
options: LaunchContextOptions = {}
|
||||||
): Promise<BrowserContext> {
|
): Promise<BrowserContext> {
|
||||||
options = migrateTimezoneId(options);
|
options = resolveTimezone(options);
|
||||||
// Resolve geoip BEFORE launch() to avoid double-resolution
|
// Resolve geoip BEFORE launch() to avoid double-resolution
|
||||||
const resolved = await maybeResolveGeoip(options);
|
const resolved = await maybeResolveGeoip(options);
|
||||||
// Skip --fingerprint-timezone binary flag: it only applies to the default
|
// --fingerprint-timezone is process-wide (reads CommandLine in renderer),
|
||||||
// context and interferes with Playwright's timezoneId on new contexts.
|
// so it applies to ALL contexts, not just the default one.
|
||||||
// Timezone is set via browser.newContext(timezoneId: ...) below instead.
|
// locale and timezone are set via binary flags only — no CDP emulation.
|
||||||
const browser = await launch({ ...options, ...resolved, geoip: false, timezone: undefined });
|
const browser = await launch({ ...options, ...resolved, geoip: false });
|
||||||
|
|
||||||
let context: BrowserContext;
|
let context: BrowserContext;
|
||||||
try {
|
try {
|
||||||
context = await browser.newContext({
|
context = await browser.newContext({
|
||||||
...(options.userAgent ? { userAgent: options.userAgent } : {}),
|
...(options.userAgent ? { userAgent: options.userAgent } : {}),
|
||||||
viewport: options.viewport ?? DEFAULT_VIEWPORT,
|
viewport: options.viewport ?? DEFAULT_VIEWPORT,
|
||||||
...(resolved.locale ? { locale: resolved.locale } : {}),
|
|
||||||
...(resolved.timezone ? { timezoneId: resolved.timezone } : {}),
|
|
||||||
...(options.colorScheme ? { colorScheme: options.colorScheme } : {}),
|
...(options.colorScheme ? { colorScheme: options.colorScheme } : {}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -153,13 +150,15 @@ export async function launchContext(
|
|||||||
export async function launchPersistentContext(
|
export async function launchPersistentContext(
|
||||||
options: LaunchPersistentContextOptions
|
options: LaunchPersistentContextOptions
|
||||||
): Promise<BrowserContext> {
|
): Promise<BrowserContext> {
|
||||||
options = migrateTimezoneId(options);
|
options = resolveTimezone(options);
|
||||||
const { chromium } = await import("playwright-core");
|
const { chromium } = await import("playwright-core");
|
||||||
|
|
||||||
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
const binaryPath = process.env.CLOAKBROWSER_BINARY_PATH || (await ensureBinary());
|
||||||
const resolved = await maybeResolveGeoip(options);
|
const resolved = await maybeResolveGeoip(options);
|
||||||
const args = buildArgs({ ...options, ...resolved });
|
const args = buildArgs({ ...options, ...resolved });
|
||||||
|
|
||||||
|
// locale and timezone are set via binary flags (--lang, --fingerprint-timezone)
|
||||||
|
// — NOT via Playwright context kwargs which use detectable CDP emulation.
|
||||||
const context = await chromium.launchPersistentContext(options.userDataDir, {
|
const context = await chromium.launchPersistentContext(options.userDataDir, {
|
||||||
executablePath: binaryPath,
|
executablePath: binaryPath,
|
||||||
headless: options.headless ?? true,
|
headless: options.headless ?? true,
|
||||||
@@ -170,8 +169,6 @@ export async function launchPersistentContext(
|
|||||||
: {}),
|
: {}),
|
||||||
...(options.userAgent ? { userAgent: options.userAgent } : {}),
|
...(options.userAgent ? { userAgent: options.userAgent } : {}),
|
||||||
viewport: options.viewport ?? DEFAULT_VIEWPORT,
|
viewport: options.viewport ?? DEFAULT_VIEWPORT,
|
||||||
...(resolved.locale ? { locale: resolved.locale } : {}),
|
|
||||||
...(resolved.timezone ? { timezoneId: resolved.timezone } : {}),
|
|
||||||
...(options.colorScheme ? { colorScheme: options.colorScheme } : {}),
|
...(options.colorScheme ? { colorScheme: options.colorScheme } : {}),
|
||||||
...options.launchOptions,
|
...options.launchOptions,
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ export interface LaunchContextOptions extends LaunchOptions {
|
|||||||
viewport?: { width: number; height: number };
|
viewport?: { width: number; height: number };
|
||||||
/** Browser locale, e.g. "en-US". */
|
/** Browser locale, e.g. "en-US". */
|
||||||
locale?: string;
|
locale?: string;
|
||||||
/** @deprecated Use `timezone` (inherited from LaunchOptions) instead. */
|
/** IANA timezone — alias for `timezone`. Either works. */
|
||||||
timezoneId?: string;
|
timezoneId?: string;
|
||||||
/** Color scheme preference — 'light', 'dark', or 'no-preference'. */
|
/** Color scheme preference — 'light', 'dark', or 'no-preference'. */
|
||||||
colorScheme?: "light" | "dark" | "no-preference";
|
colorScheme?: "light" | "dark" | "no-preference";
|
||||||
|
|||||||
+17
-10
@@ -9,7 +9,7 @@ import {
|
|||||||
getDownloadUrl,
|
getDownloadUrl,
|
||||||
getFallbackDownloadUrl,
|
getFallbackDownloadUrl,
|
||||||
} from "../src/config.js";
|
} from "../src/config.js";
|
||||||
import { _buildArgsForTest, migrateTimezoneId } from "../src/playwright.js";
|
import { _buildArgsForTest, resolveTimezone } from "../src/playwright.js";
|
||||||
|
|
||||||
describe("config", () => {
|
describe("config", () => {
|
||||||
it("CHROMIUM_VERSION matches expected format", () => {
|
it("CHROMIUM_VERSION matches expected format", () => {
|
||||||
@@ -97,21 +97,24 @@ describe("buildArgs timezone/locale", () => {
|
|||||||
expect(args).toContain("--fingerprint-timezone=America/New_York");
|
expect(args).toContain("--fingerprint-timezone=America/New_York");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("injects --lang when locale is set", () => {
|
it("injects --lang and --fingerprint-locale when locale is set", () => {
|
||||||
const args = _buildArgsForTest({ locale: "en-US" });
|
const args = _buildArgsForTest({ locale: "en-US" });
|
||||||
expect(args).toContain("--lang=en-US");
|
expect(args).toContain("--lang=en-US");
|
||||||
|
expect(args).toContain("--fingerprint-locale=en-US");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("injects both when both are set", () => {
|
it("injects both when both are set", () => {
|
||||||
const args = _buildArgsForTest({ timezone: "Europe/Berlin", locale: "de-DE" });
|
const args = _buildArgsForTest({ timezone: "Europe/Berlin", locale: "de-DE" });
|
||||||
expect(args).toContain("--fingerprint-timezone=Europe/Berlin");
|
expect(args).toContain("--fingerprint-timezone=Europe/Berlin");
|
||||||
expect(args).toContain("--lang=de-DE");
|
expect(args).toContain("--lang=de-DE");
|
||||||
|
expect(args).toContain("--fingerprint-locale=de-DE");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("injects timezone/locale even when stealthArgs=false", () => {
|
it("injects timezone/locale even when stealthArgs=false", () => {
|
||||||
const args = _buildArgsForTest({ stealthArgs: false, timezone: "America/New_York", locale: "en-US" });
|
const args = _buildArgsForTest({ stealthArgs: false, timezone: "America/New_York", locale: "en-US" });
|
||||||
expect(args).toContain("--fingerprint-timezone=America/New_York");
|
expect(args).toContain("--fingerprint-timezone=America/New_York");
|
||||||
expect(args).toContain("--lang=en-US");
|
expect(args).toContain("--lang=en-US");
|
||||||
|
expect(args).toContain("--fingerprint-locale=en-US");
|
||||||
expect(args.some(a => a.startsWith("--fingerprint="))).toBe(false);
|
expect(args.some(a => a.startsWith("--fingerprint="))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,6 +122,7 @@ describe("buildArgs timezone/locale", () => {
|
|||||||
const args = _buildArgsForTest({});
|
const args = _buildArgsForTest({});
|
||||||
expect(args.some(a => a.startsWith("--fingerprint-timezone="))).toBe(false);
|
expect(args.some(a => a.startsWith("--fingerprint-timezone="))).toBe(false);
|
||||||
expect(args.some(a => a.startsWith("--lang="))).toBe(false);
|
expect(args.some(a => a.startsWith("--lang="))).toBe(false);
|
||||||
|
expect(args.some(a => a.startsWith("--fingerprint-locale="))).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,14 +151,17 @@ describe("buildArgs deduplication", () => {
|
|||||||
expect(tzArgs[0]).toBe("--fingerprint-timezone=America/New_York");
|
expect(tzArgs[0]).toBe("--fingerprint-timezone=America/New_York");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("locale param overrides user --lang arg", () => {
|
it("locale param overrides user --lang and --fingerprint-locale args", () => {
|
||||||
const args = _buildArgsForTest({
|
const args = _buildArgsForTest({
|
||||||
args: ["--lang=de-DE"],
|
args: ["--lang=de-DE", "--fingerprint-locale=de-DE"],
|
||||||
locale: "en-US",
|
locale: "en-US",
|
||||||
});
|
});
|
||||||
const langArgs = args.filter(a => a.startsWith("--lang="));
|
const langArgs = args.filter(a => a.startsWith("--lang="));
|
||||||
expect(langArgs).toHaveLength(1);
|
expect(langArgs).toHaveLength(1);
|
||||||
expect(langArgs[0]).toBe("--lang=en-US");
|
expect(langArgs[0]).toBe("--lang=en-US");
|
||||||
|
const localeArgs = args.filter(a => a.startsWith("--fingerprint-locale="));
|
||||||
|
expect(localeArgs).toHaveLength(1);
|
||||||
|
expect(localeArgs[0]).toBe("--fingerprint-locale=en-US");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no duplicate flag keys in output", () => {
|
it("no duplicate flag keys in output", () => {
|
||||||
@@ -175,29 +182,29 @@ describe("buildArgs deduplication", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("migrateTimezoneId deprecation", () => {
|
describe("resolveTimezone alias", () => {
|
||||||
it("migrates timezoneId to timezone", () => {
|
it("resolves timezoneId to timezone", () => {
|
||||||
const result = migrateTimezoneId({ timezoneId: "Europe/Paris" });
|
const result = resolveTimezone({ timezoneId: "Europe/Paris" });
|
||||||
expect(result.timezone).toBe("Europe/Paris");
|
expect(result.timezone).toBe("Europe/Paris");
|
||||||
expect(result).not.toHaveProperty("timezoneId");
|
expect(result).not.toHaveProperty("timezoneId");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves explicit timezone over timezoneId", () => {
|
it("preserves explicit timezone over timezoneId", () => {
|
||||||
const result = migrateTimezoneId({ timezone: "UTC", timezoneId: "Europe/Paris" });
|
const result = resolveTimezone({ timezone: "UTC", timezoneId: "Europe/Paris" });
|
||||||
expect(result.timezone).toBe("UTC");
|
expect(result.timezone).toBe("UTC");
|
||||||
expect(result).not.toHaveProperty("timezoneId");
|
expect(result).not.toHaveProperty("timezoneId");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns options unchanged when no timezoneId", () => {
|
it("returns options unchanged when no timezoneId", () => {
|
||||||
const opts = { timezone: "UTC" };
|
const opts = { timezone: "UTC" };
|
||||||
const result = migrateTimezoneId(opts);
|
const result = resolveTimezone(opts);
|
||||||
expect(result).toBe(opts); // same reference, no copy
|
expect(result).toBe(opts); // same reference, no copy
|
||||||
expect(result.timezone).toBe("UTC");
|
expect(result.timezone).toBe("UTC");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns options unchanged when neither is set", () => {
|
it("returns options unchanged when neither is set", () => {
|
||||||
const opts = {};
|
const opts = {};
|
||||||
const result = migrateTimezoneId(opts);
|
const result = resolveTimezone(opts);
|
||||||
expect(result).toBe(opts);
|
expect(result).toBe(opts);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-10
@@ -96,20 +96,20 @@ describe("launchContext (unit)", () => {
|
|||||||
expect(ctxArgs.userAgent).toBe("Custom/1.0");
|
expect(ctxArgs.userAgent).toBe("Custom/1.0");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes timezone to context timezoneId, not to launch", async () => {
|
it("passes timezone via binary flag, not CDP context", async () => {
|
||||||
const { launchContext } = await import("../src/playwright.js");
|
const { launchContext } = await import("../src/playwright.js");
|
||||||
await launchContext({ timezone: "America/New_York" });
|
await launchContext({ timezone: "America/New_York" });
|
||||||
|
|
||||||
// launch() called with timezone: undefined (skipped for binary flag)
|
// launch() called with --fingerprint-timezone binary flag
|
||||||
const launchArgs = mockChromium.launch.mock.calls[0][0];
|
const launchArgs = mockChromium.launch.mock.calls[0][0];
|
||||||
const hasTimezoneFlag = launchArgs.args.some((a: string) =>
|
const hasTimezoneFlag = launchArgs.args.some((a: string) =>
|
||||||
a.startsWith("--fingerprint-timezone=")
|
a.startsWith("--fingerprint-timezone=America/New_York")
|
||||||
);
|
);
|
||||||
expect(hasTimezoneFlag).toBe(false);
|
expect(hasTimezoneFlag).toBe(true);
|
||||||
|
|
||||||
// newContext() gets timezoneId
|
// NOT in newContext() — no CDP emulation
|
||||||
const ctxArgs = mockBrowser.newContext.mock.calls[0][0];
|
const ctxArgs = mockBrowser.newContext.mock.calls[0][0];
|
||||||
expect(ctxArgs.timezoneId).toBe("America/New_York");
|
expect(ctxArgs.timezoneId).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards colorScheme to newContext", async () => {
|
it("forwards colorScheme to newContext", async () => {
|
||||||
@@ -165,7 +165,7 @@ describe("launchPersistentContext (unit)", () => {
|
|||||||
expect(args.viewport).toEqual(DEFAULT_VIEWPORT);
|
expect(args.viewport).toEqual(DEFAULT_VIEWPORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes timezone and locale to context", async () => {
|
it("passes timezone and locale via binary args, not CDP context", async () => {
|
||||||
const { launchPersistentContext } = await import("../src/playwright.js");
|
const { launchPersistentContext } = await import("../src/playwright.js");
|
||||||
await launchPersistentContext({
|
await launchPersistentContext({
|
||||||
userDataDir: "/tmp/profile",
|
userDataDir: "/tmp/profile",
|
||||||
@@ -174,11 +174,12 @@ describe("launchPersistentContext (unit)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const args = mockChromium.launchPersistentContext.mock.calls[0][1];
|
const args = mockChromium.launchPersistentContext.mock.calls[0][1];
|
||||||
expect(args.timezoneId).toBe("Asia/Tokyo");
|
// Binary args (native, undetectable)
|
||||||
expect(args.locale).toBe("ja-JP");
|
|
||||||
// Also in binary args
|
|
||||||
expect(args.args).toContain("--fingerprint-timezone=Asia/Tokyo");
|
expect(args.args).toContain("--fingerprint-timezone=Asia/Tokyo");
|
||||||
expect(args.args).toContain("--lang=ja-JP");
|
expect(args.args).toContain("--lang=ja-JP");
|
||||||
|
// NOT in context kwargs (would trigger detectable CDP emulation)
|
||||||
|
expect(args.timezoneId).toBeUndefined();
|
||||||
|
expect(args.locale).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards proxy string", async () => {
|
it("forwards proxy string", async () => {
|
||||||
|
|||||||
+25
-31
@@ -1,8 +1,6 @@
|
|||||||
"""Unit tests for _build_args timezone/locale injection and deprecation compat."""
|
"""Unit tests for _build_args timezone/locale injection and timezone alias."""
|
||||||
|
|
||||||
import warnings
|
from cloakbrowser.browser import _build_args, _resolve_timezone
|
||||||
|
|
||||||
from cloakbrowser.browser import _build_args, _migrate_timezone_id
|
|
||||||
|
|
||||||
|
|
||||||
def test_timezone_injected():
|
def test_timezone_injected():
|
||||||
@@ -12,9 +10,10 @@ def test_timezone_injected():
|
|||||||
|
|
||||||
|
|
||||||
def test_locale_injected():
|
def test_locale_injected():
|
||||||
"""--lang flag should appear when locale is set."""
|
"""--lang and --fingerprint-locale flags should appear when locale is set."""
|
||||||
args = _build_args(stealth_args=True, extra_args=None, locale="en-US")
|
args = _build_args(stealth_args=True, extra_args=None, locale="en-US")
|
||||||
assert "--lang=en-US" in args
|
assert "--lang=en-US" in args
|
||||||
|
assert "--fingerprint-locale=en-US" in args
|
||||||
|
|
||||||
|
|
||||||
def test_both_injected():
|
def test_both_injected():
|
||||||
@@ -22,6 +21,7 @@ def test_both_injected():
|
|||||||
args = _build_args(stealth_args=True, extra_args=None, timezone="Europe/Berlin", locale="de-DE")
|
args = _build_args(stealth_args=True, extra_args=None, timezone="Europe/Berlin", locale="de-DE")
|
||||||
assert "--fingerprint-timezone=Europe/Berlin" in args
|
assert "--fingerprint-timezone=Europe/Berlin" in args
|
||||||
assert "--lang=de-DE" in args
|
assert "--lang=de-DE" in args
|
||||||
|
assert "--fingerprint-locale=de-DE" in args
|
||||||
|
|
||||||
|
|
||||||
def test_timezone_independent_of_stealth_args():
|
def test_timezone_independent_of_stealth_args():
|
||||||
@@ -29,15 +29,17 @@ def test_timezone_independent_of_stealth_args():
|
|||||||
args = _build_args(stealth_args=False, extra_args=None, timezone="America/New_York", locale="en-US")
|
args = _build_args(stealth_args=False, extra_args=None, timezone="America/New_York", locale="en-US")
|
||||||
assert "--fingerprint-timezone=America/New_York" in args
|
assert "--fingerprint-timezone=America/New_York" in args
|
||||||
assert "--lang=en-US" in args
|
assert "--lang=en-US" in args
|
||||||
|
assert "--fingerprint-locale=en-US" in args
|
||||||
# No stealth fingerprint args
|
# No stealth fingerprint args
|
||||||
assert not any(a.startswith("--fingerprint=") for a in args)
|
assert not any(a.startswith("--fingerprint=") for a in args)
|
||||||
|
|
||||||
|
|
||||||
def test_no_flags_when_not_set():
|
def test_no_flags_when_not_set():
|
||||||
"""No timezone/lang flags when params are None."""
|
"""No timezone/lang/fingerprint-locale flags when params are None."""
|
||||||
args = _build_args(stealth_args=True, extra_args=None)
|
args = _build_args(stealth_args=True, extra_args=None)
|
||||||
assert not any(a.startswith("--fingerprint-timezone=") for a in args)
|
assert not any(a.startswith("--fingerprint-timezone=") for a in args)
|
||||||
assert not any(a.startswith("--lang=") for a in args)
|
assert not any(a.startswith("--lang=") for a in args)
|
||||||
|
assert not any(a.startswith("--fingerprint-locale=") for a in args)
|
||||||
|
|
||||||
|
|
||||||
def test_extra_args_preserved():
|
def test_extra_args_preserved():
|
||||||
@@ -46,52 +48,41 @@ def test_extra_args_preserved():
|
|||||||
assert "--disable-gpu" in args
|
assert "--disable-gpu" in args
|
||||||
assert "--fingerprint-timezone=Asia/Tokyo" in args
|
assert "--fingerprint-timezone=Asia/Tokyo" in args
|
||||||
assert "--lang=ja-JP" in args
|
assert "--lang=ja-JP" in args
|
||||||
|
assert "--fingerprint-locale=ja-JP" in args
|
||||||
|
|
||||||
|
|
||||||
# --- _migrate_timezone_id deprecation compat ---
|
# --- _resolve_timezone alias ---
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_old_param_only():
|
def test_resolve_timezone_id_alias():
|
||||||
"""timezone_id in kwargs should be promoted to timezone."""
|
"""timezone_id in kwargs should be promoted to timezone."""
|
||||||
kwargs = {"timezone_id": "Europe/Paris"}
|
kwargs = {"timezone_id": "Europe/Paris"}
|
||||||
with warnings.catch_warnings(record=True) as w:
|
result = _resolve_timezone(None, kwargs)
|
||||||
warnings.simplefilter("always")
|
|
||||||
result = _migrate_timezone_id(None, kwargs)
|
|
||||||
assert result == "Europe/Paris"
|
assert result == "Europe/Paris"
|
||||||
assert "timezone_id" not in kwargs
|
assert "timezone_id" not in kwargs
|
||||||
assert len(w) == 1 and issubclass(w[0].category, FutureWarning)
|
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_new_param_wins():
|
def test_resolve_timezone_wins_over_alias():
|
||||||
"""Explicit timezone takes precedence; timezone_id is still popped."""
|
"""Explicit timezone takes precedence; timezone_id is still popped."""
|
||||||
kwargs = {"timezone_id": "Europe/Paris"}
|
kwargs = {"timezone_id": "Europe/Paris"}
|
||||||
with warnings.catch_warnings(record=True) as w:
|
result = _resolve_timezone("UTC", kwargs)
|
||||||
warnings.simplefilter("always")
|
|
||||||
result = _migrate_timezone_id("UTC", kwargs)
|
|
||||||
assert result == "UTC"
|
assert result == "UTC"
|
||||||
assert "timezone_id" not in kwargs
|
assert "timezone_id" not in kwargs
|
||||||
assert len(w) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_no_old_param():
|
def test_resolve_no_alias():
|
||||||
"""No warning when timezone_id is absent."""
|
"""No-op when timezone_id is absent."""
|
||||||
kwargs = {"other": "value"}
|
kwargs = {"other": "value"}
|
||||||
with warnings.catch_warnings(record=True) as w:
|
result = _resolve_timezone("UTC", kwargs)
|
||||||
warnings.simplefilter("always")
|
|
||||||
result = _migrate_timezone_id("UTC", kwargs)
|
|
||||||
assert result == "UTC"
|
assert result == "UTC"
|
||||||
assert "other" in kwargs
|
assert "other" in kwargs
|
||||||
assert len(w) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_both_none():
|
def test_resolve_both_none():
|
||||||
"""Neither param set — returns None, no warning."""
|
"""Neither param set — returns None."""
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
with warnings.catch_warnings(record=True) as w:
|
result = _resolve_timezone(None, kwargs)
|
||||||
warnings.simplefilter("always")
|
|
||||||
result = _migrate_timezone_id(None, kwargs)
|
|
||||||
assert result is None
|
assert result is None
|
||||||
assert len(w) == 0
|
|
||||||
|
|
||||||
|
|
||||||
# --- Deduplication tests ---
|
# --- Deduplication tests ---
|
||||||
@@ -126,15 +117,18 @@ def test_timezone_param_overrides_user_arg():
|
|||||||
|
|
||||||
|
|
||||||
def test_locale_param_overrides_user_arg():
|
def test_locale_param_overrides_user_arg():
|
||||||
"""Dedicated locale param should override user --lang arg."""
|
"""Dedicated locale param should override user --lang and --fingerprint-locale args."""
|
||||||
args = _build_args(
|
args = _build_args(
|
||||||
stealth_args=True,
|
stealth_args=True,
|
||||||
extra_args=["--lang=de-DE"],
|
extra_args=["--lang=de-DE", "--fingerprint-locale=de-DE"],
|
||||||
locale="en-US",
|
locale="en-US",
|
||||||
)
|
)
|
||||||
lang_args = [a for a in args if a.startswith("--lang=")]
|
lang_args = [a for a in args if a.startswith("--lang=")]
|
||||||
assert len(lang_args) == 1
|
assert len(lang_args) == 1
|
||||||
assert lang_args[0] == "--lang=en-US"
|
assert lang_args[0] == "--lang=en-US"
|
||||||
|
locale_args = [a for a in args if a.startswith("--fingerprint-locale=")]
|
||||||
|
assert len(locale_args) == 1
|
||||||
|
assert locale_args[0] == "--fingerprint-locale=en-US"
|
||||||
|
|
||||||
|
|
||||||
def test_no_duplicate_flags():
|
def test_no_duplicate_flags():
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Unit tests for launch_context() — context kwargs, viewport defaults, close cleanup."""
|
"""Unit tests for launch_context() — context kwargs, viewport defaults, close cleanup."""
|
||||||
|
|
||||||
import warnings
|
|
||||||
from unittest.mock import MagicMock, call, patch
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -66,7 +65,7 @@ def test_user_agent(mock_launch, _mock_bin):
|
|||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@patch("cloakbrowser.browser.launch")
|
@patch("cloakbrowser.browser.launch")
|
||||||
def test_locale_forwarded(mock_launch, _mock_bin):
|
def test_locale_forwarded(mock_launch, _mock_bin):
|
||||||
"""locale flows to both launch() binary args AND new_context()."""
|
"""locale flows to launch() for --lang binary flag, NOT to new_context() CDP."""
|
||||||
browser, context = _make_mock_browser()
|
browser, context = _make_mock_browser()
|
||||||
mock_launch.return_value = browser
|
mock_launch.return_value = browser
|
||||||
|
|
||||||
@@ -75,18 +74,18 @@ def test_locale_forwarded(mock_launch, _mock_bin):
|
|||||||
|
|
||||||
# Locale in launch() call (for --lang binary flag)
|
# Locale in launch() call (for --lang binary flag)
|
||||||
assert mock_launch.call_args[1]["locale"] == "de-DE"
|
assert mock_launch.call_args[1]["locale"] == "de-DE"
|
||||||
# Locale in new_context() call
|
# NOT in new_context() — would trigger detectable CDP emulation
|
||||||
ctx_kwargs = browser.new_context.call_args
|
ctx_kwargs = browser.new_context.call_args
|
||||||
assert ctx_kwargs[1]["locale"] == "de-DE"
|
assert "locale" not in ctx_kwargs[1]
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@patch("cloakbrowser.browser.launch")
|
@patch("cloakbrowser.browser.launch")
|
||||||
def test_timezone_via_context_not_binary(mock_launch, _mock_bin):
|
def test_timezone_via_binary_not_cdp(mock_launch, _mock_bin):
|
||||||
"""timezone passed to new_context(timezone_id=...) but NOT to launch(timezone=...).
|
"""timezone passed to launch() for binary flag, NOT to new_context() CDP.
|
||||||
|
|
||||||
This is intentional: the --fingerprint-timezone binary flag only applies to the
|
--fingerprint-timezone is process-wide (reads CommandLine in renderer),
|
||||||
default context and would conflict with Playwright's timezone_id on new contexts.
|
so it applies to ALL contexts, not just the default one.
|
||||||
"""
|
"""
|
||||||
browser, context = _make_mock_browser()
|
browser, context = _make_mock_browser()
|
||||||
mock_launch.return_value = browser
|
mock_launch.return_value = browser
|
||||||
@@ -94,11 +93,11 @@ def test_timezone_via_context_not_binary(mock_launch, _mock_bin):
|
|||||||
from cloakbrowser.browser import launch_context
|
from cloakbrowser.browser import launch_context
|
||||||
launch_context(timezone="America/New_York")
|
launch_context(timezone="America/New_York")
|
||||||
|
|
||||||
# timezone=None in launch() — binary flag skipped
|
# timezone in launch() — binary flag set
|
||||||
assert mock_launch.call_args[1]["timezone"] is None
|
assert mock_launch.call_args[1]["timezone"] == "America/New_York"
|
||||||
# timezone_id in new_context()
|
# NOT in new_context() — no CDP emulation
|
||||||
ctx_kwargs = browser.new_context.call_args
|
ctx_kwargs = browser.new_context.call_args
|
||||||
assert ctx_kwargs[1]["timezone_id"] == "America/New_York"
|
assert "timezone_id" not in ctx_kwargs[1]
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@@ -119,40 +118,37 @@ def test_color_scheme(mock_launch, _mock_bin):
|
|||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@patch("cloakbrowser.browser.launch")
|
@patch("cloakbrowser.browser.launch")
|
||||||
def test_geoip_resolution(mock_launch, _mock_bin, _mock_geoip):
|
def test_geoip_resolution(mock_launch, _mock_bin, _mock_geoip):
|
||||||
"""geoip fills timezone+locale, both flow to correct places."""
|
"""geoip fills timezone+locale, both flow to binary args only."""
|
||||||
browser, context = _make_mock_browser()
|
browser, context = _make_mock_browser()
|
||||||
mock_launch.return_value = browser
|
mock_launch.return_value = browser
|
||||||
|
|
||||||
from cloakbrowser.browser import launch_context
|
from cloakbrowser.browser import launch_context
|
||||||
launch_context(proxy="http://proxy:8080", geoip=True)
|
launch_context(proxy="http://proxy:8080", geoip=True)
|
||||||
|
|
||||||
# Locale goes to launch() for binary flag
|
# Both go to launch() for binary flags
|
||||||
assert mock_launch.call_args[1]["locale"] == "de-DE"
|
assert mock_launch.call_args[1]["locale"] == "de-DE"
|
||||||
# Timezone goes to context, not binary
|
assert mock_launch.call_args[1]["timezone"] == "Europe/Berlin"
|
||||||
assert mock_launch.call_args[1]["timezone"] is None
|
# Neither in context — no CDP emulation
|
||||||
ctx_kwargs = browser.new_context.call_args
|
ctx_kwargs = browser.new_context.call_args
|
||||||
assert ctx_kwargs[1]["timezone_id"] == "Europe/Berlin"
|
assert "timezone_id" not in ctx_kwargs[1]
|
||||||
assert ctx_kwargs[1]["locale"] == "de-DE"
|
assert "locale" not in ctx_kwargs[1]
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@patch("cloakbrowser.browser.launch")
|
@patch("cloakbrowser.browser.launch")
|
||||||
def test_timezone_id_deprecation(mock_launch, _mock_bin):
|
def test_timezone_id_alias(mock_launch, _mock_bin):
|
||||||
"""timezone_id kwarg triggers FutureWarning, value migrated to timezone."""
|
"""timezone_id kwarg accepted as alias for timezone."""
|
||||||
browser, context = _make_mock_browser()
|
browser, context = _make_mock_browser()
|
||||||
mock_launch.return_value = browser
|
mock_launch.return_value = browser
|
||||||
|
|
||||||
from cloakbrowser.browser import launch_context
|
from cloakbrowser.browser import launch_context
|
||||||
with warnings.catch_warnings(record=True) as w:
|
launch_context(timezone_id="Europe/Paris")
|
||||||
warnings.simplefilter("always")
|
|
||||||
launch_context(timezone_id="Europe/Paris")
|
|
||||||
|
|
||||||
assert len(w) == 1
|
# Resolved value flows to launch() for binary flag
|
||||||
assert issubclass(w[0].category, FutureWarning)
|
assert mock_launch.call_args[1]["timezone"] == "Europe/Paris"
|
||||||
assert "timezone_id" in str(w[0].message)
|
# NOT in context — no CDP emulation
|
||||||
# Migrated value flows to context
|
|
||||||
ctx_kwargs = browser.new_context.call_args
|
ctx_kwargs = browser.new_context.call_args
|
||||||
assert ctx_kwargs[1]["timezone_id"] == "Europe/Paris"
|
assert "timezone_id" not in ctx_kwargs[1]
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
All tests mock playwright to avoid needing a binary.
|
All tests mock playwright to avoid needing a binary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import warnings
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -87,7 +86,7 @@ def test_persistent_context_user_agent(_mock_geoip, _mock_bin):
|
|||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
def test_persistent_context_locale_and_timezone(_mock_bin):
|
def test_persistent_context_locale_and_timezone(_mock_bin):
|
||||||
"""Both timezone and locale flow to context kwargs and binary args."""
|
"""Timezone and locale flow to binary args only, NOT to CDP context kwargs."""
|
||||||
pw_cm, pw, context = _make_mock_pw_and_context()
|
pw_cm, pw, context = _make_mock_pw_and_context()
|
||||||
|
|
||||||
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
||||||
@@ -95,12 +94,12 @@ def test_persistent_context_locale_and_timezone(_mock_bin):
|
|||||||
launch_persistent_context("/tmp/profile", timezone="Asia/Tokyo", locale="ja-JP")
|
launch_persistent_context("/tmp/profile", timezone="Asia/Tokyo", locale="ja-JP")
|
||||||
|
|
||||||
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
||||||
# Context kwargs
|
# Binary args (native, undetectable)
|
||||||
assert call_kwargs["timezone_id"] == "Asia/Tokyo"
|
|
||||||
assert call_kwargs["locale"] == "ja-JP"
|
|
||||||
# Binary args
|
|
||||||
assert "--fingerprint-timezone=Asia/Tokyo" in call_kwargs["args"]
|
assert "--fingerprint-timezone=Asia/Tokyo" in call_kwargs["args"]
|
||||||
assert "--lang=ja-JP" in call_kwargs["args"]
|
assert "--lang=ja-JP" in call_kwargs["args"]
|
||||||
|
# NOT in context kwargs (would trigger detectable CDP emulation)
|
||||||
|
assert "timezone_id" not in call_kwargs
|
||||||
|
assert "locale" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@@ -120,7 +119,7 @@ def test_persistent_context_color_scheme(_mock_geoip, _mock_bin):
|
|||||||
@patch("cloakbrowser.browser._maybe_resolve_geoip", return_value=("Europe/Berlin", "de-DE"))
|
@patch("cloakbrowser.browser._maybe_resolve_geoip", return_value=("Europe/Berlin", "de-DE"))
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
def test_persistent_context_geoip(_mock_bin, _mock_geoip):
|
def test_persistent_context_geoip(_mock_bin, _mock_geoip):
|
||||||
"""geoip fills missing tz/locale."""
|
"""geoip fills missing tz/locale — flows to binary args, not CDP context."""
|
||||||
pw_cm, pw, context = _make_mock_pw_and_context()
|
pw_cm, pw, context = _make_mock_pw_and_context()
|
||||||
|
|
||||||
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
||||||
@@ -128,25 +127,26 @@ def test_persistent_context_geoip(_mock_bin, _mock_geoip):
|
|||||||
launch_persistent_context("/tmp/profile", proxy="http://proxy:8080", geoip=True)
|
launch_persistent_context("/tmp/profile", proxy="http://proxy:8080", geoip=True)
|
||||||
|
|
||||||
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
||||||
assert call_kwargs["timezone_id"] == "Europe/Berlin"
|
# Binary args
|
||||||
assert call_kwargs["locale"] == "de-DE"
|
assert "--fingerprint-timezone=Europe/Berlin" in call_kwargs["args"]
|
||||||
|
assert "--lang=de-DE" in call_kwargs["args"]
|
||||||
|
# NOT in context kwargs
|
||||||
|
assert "timezone_id" not in call_kwargs
|
||||||
|
assert "locale" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
def test_persistent_context_timezone_id_deprecation(_mock_bin):
|
def test_persistent_context_timezone_id_alias(_mock_bin):
|
||||||
"""Old timezone_id kwarg migrated with warning."""
|
"""timezone_id kwarg accepted as alias for timezone."""
|
||||||
pw_cm, pw, context = _make_mock_pw_and_context()
|
pw_cm, pw, context = _make_mock_pw_and_context()
|
||||||
|
|
||||||
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
with patch("playwright.sync_api.sync_playwright", return_value=pw_cm):
|
||||||
from cloakbrowser.browser import launch_persistent_context
|
from cloakbrowser.browser import launch_persistent_context
|
||||||
with warnings.catch_warnings(record=True) as w:
|
launch_persistent_context("/tmp/profile", timezone_id="Europe/Paris")
|
||||||
warnings.simplefilter("always")
|
|
||||||
launch_persistent_context("/tmp/profile", timezone_id="Europe/Paris")
|
|
||||||
|
|
||||||
assert len(w) == 1
|
|
||||||
assert issubclass(w[0].category, FutureWarning)
|
|
||||||
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
||||||
assert call_kwargs["timezone_id"] == "Europe/Paris"
|
assert "--fingerprint-timezone=Europe/Paris" in call_kwargs["args"]
|
||||||
|
assert "timezone_id" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
@@ -246,17 +246,14 @@ async def test_persistent_context_async_close_stops_pw(_mock_geoip, _mock_bin):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
@patch("cloakbrowser.browser.ensure_binary", return_value="/fake/chrome")
|
||||||
async def test_persistent_context_async_timezone_id_deprecation(_mock_bin):
|
async def test_persistent_context_async_timezone_id_alias(_mock_bin):
|
||||||
"""Deprecated timezone_id kwarg migrated with warning in async path."""
|
"""timezone_id kwarg accepted as alias in async path."""
|
||||||
pw_cm, pw, context = _make_mock_async_pw_and_context()
|
pw_cm, pw, context = _make_mock_async_pw_and_context()
|
||||||
|
|
||||||
with patch("playwright.async_api.async_playwright", return_value=pw_cm):
|
with patch("playwright.async_api.async_playwright", return_value=pw_cm):
|
||||||
from cloakbrowser.browser import launch_persistent_context_async
|
from cloakbrowser.browser import launch_persistent_context_async
|
||||||
with warnings.catch_warnings(record=True) as w:
|
await launch_persistent_context_async("/tmp/profile", timezone_id="Europe/Paris")
|
||||||
warnings.simplefilter("always")
|
|
||||||
await launch_persistent_context_async("/tmp/profile", timezone_id="Europe/Paris")
|
|
||||||
|
|
||||||
assert len(w) == 1
|
|
||||||
assert issubclass(w[0].category, FutureWarning)
|
|
||||||
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
call_kwargs = pw.chromium.launch_persistent_context.call_args[1]
|
||||||
assert call_kwargs["timezone_id"] == "Europe/Paris"
|
assert "--fingerprint-timezone=Europe/Paris" in call_kwargs["args"]
|
||||||
|
assert "timezone_id" not in call_kwargs
|
||||||
|
|||||||
Reference in New Issue
Block a user