Skip to content

Instantly share code, notes, and snippets.

@Hassan-Naeem-code
Created April 21, 2026 01:14
Show Gist options
  • Select an option

  • Save Hassan-Naeem-code/fe0fabac23ebd584b8af504c20b8fa64 to your computer and use it in GitHub Desktop.

Select an option

Save Hassan-Naeem-code/fe0fabac23ebd584b8af504c20b8fa64 to your computer and use it in GitHub Desktop.
Python: Playwright automation patterns — iframe traversal, humanlike drag, canvas screenshots
"""
Playwright browser automation starter — patterns I keep reusing.
Handles common real-world issues: iframes, nested iframes, canvas screenshots,
humanlike dragging, and graceful install-on-first-run.
Install:
pip install playwright
python -m playwright install
"""
import asyncio
import sys
import subprocess
from playwright.async_api import async_playwright, Page, Frame, Error as PWError
# --- Humanlike interactions -----------------------------------------
async def humanlike_drag(page: Page, src: tuple[float, float], dst: tuple[float, float],
steps: int = 40, step_delay: float = 0.01) -> None:
"""Drag with interpolated intermediate moves. Games & some anti-bot UIs require this."""
sx, sy = src
tx, ty = dst
await page.mouse.move(sx, sy)
await page.mouse.down()
for i in range(1, steps + 1):
x = sx + (tx - sx) * (i / steps)
y = sy + (ty - sy) * (i / steps)
await page.mouse.move(x, y)
await asyncio.sleep(step_delay)
await page.mouse.up()
async def type_like_human(page: Page, selector: str, text: str, delay_ms: int = 80) -> None:
await page.click(selector)
await page.type(selector, text, delay=delay_ms)
# --- Iframe traversal -----------------------------------------------
async def enter_frame(parent: Page | Frame, selector: str, wait_ms: int = 5000) -> Frame | None:
"""Find an iframe by selector and return its Frame context. Returns None on miss."""
await parent.wait_for_selector(selector, timeout=wait_ms)
el = await parent.query_selector(selector)
if not el:
return None
frame = await el.content_frame()
if frame is None:
return None
# Give the iframe content a moment to load
await asyncio.sleep(0.5)
return frame
async def enter_nested_frames(page: Page, chain: list[str]) -> Frame | None:
"""Walk a chain of iframe selectors. Returns the innermost Frame."""
ctx: Page | Frame = page
for sel in chain:
next_ctx = await enter_frame(ctx, sel)
if next_ctx is None:
return None
ctx = next_ctx
return ctx # type: ignore[return-value]
# --- Coordinates across iframes -------------------------------------
async def global_coords(outer_iframe_el, inner_box: dict,
frac_x: float = 0.5, frac_y: float = 0.5) -> tuple[float, float]:
"""
Given the page-level iframe element and an inner element's bounding_box
relative to its frame, compute page-global coordinates.
"""
iframe_box = await outer_iframe_el.bounding_box()
x = iframe_box["x"] + inner_box["x"] + inner_box["width"] * frac_x
y = iframe_box["y"] + inner_box["y"] + inner_box["height"] * frac_y
return x, y
# --- Runner with graceful browser install ---------------------------
async def run(job):
"""Wrap your async job so first-run install errors self-heal."""
try:
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(viewport={"width": 1280, "height": 800})
page = await context.new_page()
try:
await job(page)
finally:
await browser.close()
except PWError as e:
if "Executable doesn't exist" in str(e):
print("Browsers not installed; running `playwright install`...")
subprocess.run([sys.executable, "-m", "playwright", "install"], check=True)
print("Installed. Re-run the script.")
sys.exit(1)
raise
# --- Example job ----------------------------------------------------
async def example_job(page):
await page.goto("https://example.com")
print("title:", await page.title())
await page.screenshot(path="example.png")
if __name__ == "__main__":
asyncio.run(run(example_job))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment