Skip to content

Commit

Permalink
ft: don't bother injecting an empty script
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattwmaster58 committed Oct 18, 2024
1 parent 15ee6c7 commit db1cf96
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 15 deletions.
50 changes: 41 additions & 9 deletions playwright_stealth/stealth.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def from_file(name) -> str:

class Stealth:
"""
Playwright stealth configuration that applies stealth strategies to playwright page objects.
Playwright stealth configuration that applies stealth strategies to Playwright.
The stealth strategies are contained in ./js package and are basic javascript scripts that are executed
on every page.goto() called.
Note:
Expand Down Expand Up @@ -119,10 +119,13 @@ def __init__(
@property
def script_payload(self) -> str:
"""
Returns:
enabled scripts in an immediately invoked function expression
Generates an immediately invoked function expression for all enabled scripts
Returns: string of enabled scripts in IIFE
"""
return "(() => {\n" + "\n".join(self.enabled_scripts) + "\n})();"
scripts_block = "\n".join(self.enabled_scripts)
if len(scripts_block) == 0:
return ""
return "(() => {\n" + scripts_block + "\n})();"

@property
def options_payload(self) -> str:
Expand All @@ -141,11 +144,16 @@ def options_payload(self) -> str:

@property
def enabled_scripts(self):
evasion_script_block = "\n".join(self._evasion_scripts)
if len(evasion_script_block) == 0:
return ""

yield self.options_payload
# init utils and generate_magic_arrays helper
yield SCRIPTS["utils"]
yield SCRIPTS["generate_magic_arrays"]

@property
def _evasion_scripts(self) -> str:
if self.chrome_app:
yield SCRIPTS["chrome_app"]
if self.chrome_csi:
Expand Down Expand Up @@ -200,10 +208,13 @@ def use_sync(self, ctx: sync_api.PlaywrightContextManager) -> SyncWrappingContex
return SyncWrappingContextManager(self, ctx)

async def apply_stealth_async(self, page_or_context: Union[async_api.Page, async_api.BrowserContext]) -> None:
if len(self.script_payload) > 0:
return
await page_or_context.add_init_script(self.script_payload)

def stealth_sync(self, page_or_context: Union[sync_api.Page, sync_api.BrowserContext]) -> None:
page_or_context.add_init_script(self.script_payload)
def apply_stealth_sync(self, page_or_context: Union[sync_api.Page, sync_api.BrowserContext]) -> None:
if len(self.script_payload) > 0:
page_or_context.add_init_script(self.script_payload)

def _kwargs_with_patched_cli_arg(self, method: Callable, packed_kwargs: Dict[str, Any], chromium_mode: bool) -> \
Dict[str, Any]:
Expand Down Expand Up @@ -297,7 +308,7 @@ async def hooked_browser_method_async(*args, **kwargs):

def hooked_browser_method_sync(*args, **kwargs):
page_or_context = new_page_method(*args, **kwargs)
self.stealth_sync(page_or_context)
self.apply_stealth_sync(page_or_context)
return page_or_context

if inspect.iscoroutinefunction(new_page_method):
Expand All @@ -319,7 +330,8 @@ def _patch_blink_features_cli_args(existing_args: Optional[List[str]]) -> List[s
else:
new_args.append(arg)
else: # no break
# no blink features disabled, no need to be careful how we modify the command line
# the user has specified no extra blink features disabled,
# so no need to be careful how we modify the command line
new_args.append(f"{disable_blink_features_prefix}{automation_controlled_feature_name}")
return new_args

Expand All @@ -342,3 +354,23 @@ def _patch_cli_arg(existing_args: List[str], flag: str) -> List[str]:
# none of the existing switches overlap with the one we're trying to set
new_args.append(flag)
return new_args


ALL_DISABLED_KWARGS = {
"navigator_webdriver": False,
"webgl_vendor": False,
"chrome_app": False,
"chrome_csi": False,
"chrome_load_times": False,
"chrome_runtime": False,
"iframe_content_window": False,
"media_codecs": False,
"navigator_hardware_concurrency": False,
"navigator_languages": False,
"navigator_permissions": False,
"navigator_platform": False,
"navigator_plugins": False,
"navigator_user_agent": False,
"navigator_vendor": False,
"hairline": False,
}
2 changes: 1 addition & 1 deletion tests/test_arg_patching.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def collect_log_message(msg: async_api.ConsoleMessage):
await page.goto("http://example.org")
webdriver_js_was_patched = not any(map(lambda x: "not patching navigator.webdriver" in x, console_messages))
languages_js_was_patched = not any(map(lambda x: "not patching navigator.languages" in x, console_messages))
# if browser is chromium, we should patch the CLI args
# iff browser is chromium, we should patch the CLI args
if hooked_async_browser.browser_type == "chromium":
assert not webdriver_js_was_patched
assert not languages_js_was_patched
Expand Down
26 changes: 21 additions & 5 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import logging

import pytest
from playwright import sync_api
from playwright.async_api import async_playwright
from playwright.sync_api import sync_playwright
from playwright.sync_api import sync_playwright, Browser

from playwright_stealth.stealth import Stealth
from playwright_stealth.stealth import Stealth, ALL_DISABLED_KWARGS


@pytest.mark.parametrize("browser_type", ["chromium", "firefox"])
Expand All @@ -29,14 +30,29 @@ def test_sync_smoketest(browser_type: str):

async def test_async_navigator_webdriver_smoketest(hooked_async_browser):
for page in [await hooked_async_browser.new_page(), await (await hooked_async_browser.new_context()).new_page()]:
logging.getLogger(__name__).warning("hello")
page.on("console", lambda x: logging.getLogger(__name__).warning(x.text))
await page.goto("http://example.org")
await asyncio.sleep(1)
assert await page.evaluate("navigator.webdriver") is False


def test_sync_navigator_webdriver_smoketest(hooked_sync_browser):
for page in [hooked_sync_browser.new_page(), hooked_sync_browser.new_context().new_page()]:
page.goto("http://example.org")
assert page.evaluate("navigator.webdriver") is False


def test_payload_is_empty_when_no_evasions_active():
assert len(Stealth(**ALL_DISABLED_KWARGS).script_payload) == 0

def test_empty_payload_not_injected():
init_script_added = False

class MockBrowser:
def add_init_script(self, *args, **kwargs):
nonlocal init_script_added
init_script_added = True

# noinspection PyTypeChecker
Stealth(**ALL_DISABLED_KWARGS).apply_stealth_sync(MockBrowser())
assert not init_script_added


0 comments on commit db1cf96

Please sign in to comment.