diff options
Diffstat (limited to 'playwright-py/scripts/safe_actions.py')
| -rw-r--r-- | playwright-py/scripts/safe_actions.py | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/playwright-py/scripts/safe_actions.py b/playwright-py/scripts/safe_actions.py new file mode 100644 index 0000000..c3f72bf --- /dev/null +++ b/playwright-py/scripts/safe_actions.py @@ -0,0 +1,100 @@ +"""Retry-wrapped Playwright action helpers + common convenience utilities. + +Usage: + from scripts.safe_actions import ( + safe_click, safe_type, handle_cookie_banner, build_context_with_headers + ) +""" + +import json +import os +import time + + +def safe_click(page, selector, retries: int = 3, delay: float = 0.5, timeout: int = 5000): + """Click SELECTOR. Retry up to RETRIES times with DELAY seconds between. + + Raises the last exception if all attempts fail. + """ + last_err = None + for attempt in range(retries): + try: + page.wait_for_selector(selector, timeout=timeout) + page.click(selector) + return + except Exception as err: + last_err = err + if attempt < retries - 1: + time.sleep(delay) + raise last_err # type: ignore[misc] + + +def safe_type(page, selector, value: str, retries: int = 3, delay: float = 0.5, timeout: int = 5000): + """Fill SELECTOR with VALUE. Retry on failure.""" + last_err = None + for attempt in range(retries): + try: + page.wait_for_selector(selector, timeout=timeout) + page.fill(selector, value) + return + except Exception as err: + last_err = err + if attempt < retries - 1: + time.sleep(delay) + raise last_err # type: ignore[misc] + + +def handle_cookie_banner(page, selectors=None) -> bool: + """Try common cookie-banner accept selectors; click the first that exists. + + Returns True if a banner was found and clicked, False otherwise. + Does not raise on failure — many pages have no banner. + """ + selectors = selectors or [ + "#onetrust-accept-btn-handler", + 'button[aria-label*="ccept" i]', + 'button:has-text("Accept")', + 'button:has-text("I agree")', + 'button:has-text("Got it")', + '[data-testid="uc-accept-all-button"]', + "#cookie-accept", + ".cookie-accept", + ] + for selector in selectors: + try: + if page.locator(selector).count() > 0: + page.click(selector, timeout=1000) + return True + except Exception: + continue + return False + + +def build_context_with_headers(browser, extra_kwargs=None): + """Create a browser context with extra HTTP headers from env vars. + + Reads: + PW_HEADER_NAME / PW_HEADER_VALUE — single header + PW_EXTRA_HEADERS='{"X-A":"1","X-B":"2"}' — JSON object of headers + + Unset env vars → plain context with no extra headers. + extra_kwargs, if supplied, are passed to browser.new_context(). + """ + headers: dict[str, str] = {} + name = os.environ.get("PW_HEADER_NAME") + value = os.environ.get("PW_HEADER_VALUE") + if name and value: + headers[name] = value + extra = os.environ.get("PW_EXTRA_HEADERS") + if extra: + try: + parsed = json.loads(extra) + if isinstance(parsed, dict): + headers.update({str(k): str(v) for k, v in parsed.items()}) + except json.JSONDecodeError: + pass + + kwargs: dict = dict(extra_kwargs or {}) + if headers: + kwargs["extra_http_headers"] = headers + return browser.new_context(**kwargs) |
