Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ on:
workflow_dispatch:
inputs:
playwright_version:
description: 'Playwright-Python version tag (e.g. v1.56.0)'
description: 'Playwright-Python version tag to build from (e.g. v1.61.0)'
required: true
default: 'v1.56.0'
default: 'v1.61.0'
patchright_release:
description: 'Patchright release version (e.g. 1.56.0)'
description: 'Patchright-Python release version to publish (e.g. 1.61.0)'
required: true
default: '1.56.0'
default: '1.61.0'
patchright_driver_version:
description: 'Patched driver version to bundle (bugbasesecurity/patchright release, e.g. 1.61.0). Defaults to patchright_release.'
required: false
default: ''

permissions:
contents: write
Expand All @@ -22,6 +26,7 @@ jobs:
env:
patchright_release: ${{ github.event.inputs.patchright_release }}
playwright_version: ${{ github.event.inputs.playwright_version }}
patchright_driver_version: ${{ github.event.inputs.patchright_driver_version }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
Expand Down
51 changes: 46 additions & 5 deletions patch_python_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,46 @@
import toml

patchright_version = os.environ.get('patchright_release') or os.environ.get('playwright_version')
patchright_driver_version = os.environ.get('patchright_driver_version') or patchright_version

# The bundled driver must be the PATCHED Patchright driver (stealth lives in the
# driver: navigator.webdriver=false, no Runtime.enable, --disable-blink-features
# =AutomationControlled in chromiumSwitches — the Python-layer patches alone do
# NOT cover these). Our nodejs fork publishes that driver, with the networkidle
# captcha exclusion already baked in, on its GitHub releases.
DRIVER_REPO = "bugbasesecurity/patchright"

def patch_file(file_path: str, patched_tree: ast.AST) -> None:
with open(file_path, "w") as f:
f.write(ast.unparse(ast.fix_missing_locations(patched_tree)))

def patch_driver_version_file() -> None:
# Playwright-Python 1.61+ reads driver_version from a DRIVER_VERSION file
# instead of a setup.py string constant. Pin it to the patched driver version
# so ensure_driver_bundle() resolves to our published release asset.
driver_version_file = "playwright-python/DRIVER_VERSION"
if os.path.exists(driver_version_file):
with open(driver_version_file, "w") as f:
f.write(f"{patchright_driver_version}\n")

def patch_ensure_driver_bundle(node: ast.FunctionDef) -> None:
# Playwright-Python 1.61+ assembles the bundle from the vanilla playwright-core
# npm package via ensure_driver_bundle(). Replace its body so it downloads our
# patched driver ZIP from the fork's releases instead. (Pre-1.61 used a
# cdn.playwright.dev url constant, handled by the url rewrite below.)
if node.name != "ensure_driver_bundle":
return
node.body = ast.parse(f'''\
destination_path = f"driver/playwright-{{driver_version}}-{{zip_name}}.zip"
if os.path.exists(destination_path):
return
os.makedirs("driver", exist_ok=True)
url = f"https://github.com/{DRIVER_REPO}/releases/download/v{{driver_version}}/playwright-{{driver_version}}-{{zip_name}}.zip"
subprocess.check_call(["curl", "-L", "--fail", "-o", destination_path, url])
if not os.path.exists(destination_path):
raise RuntimeError(f"Driver bundle {{destination_path}} was not downloaded.")
''').body

# Adding _repo_version.py (Might not be intended but fixes the build)
with open("playwright-python/playwright/_repo_version.py", "w") as f:
f.write(f"version = '{patchright_version}'")
Expand Down Expand Up @@ -37,24 +72,30 @@ def patch_file(file_path: str, patched_tree: ast.AST) -> None:
with open("playwright-python/pyproject.toml", "w") as f:
toml.dump(pyproject_source, f)

# Pin DRIVER_VERSION (Playwright-Python 1.61+ file-based driver version)
patch_driver_version_file()

# Patching setup.py
with open("playwright-python/setup.py") as f:
setup_source = f.read()
setup_tree = ast.parse(setup_source)

for node in ast.walk(setup_tree):
# Modify driver_version
# Redirect the 1.61+ driver bundle download to our patched release asset
if isinstance(node, ast.FunctionDef):
patch_ensure_driver_bundle(node)

# Modify driver_version (pre-1.61 string-constant form; no-op on 1.61+)
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant) and isinstance(node.targets[0], ast.Name):
if node.targets[0].id == "driver_version" and node.value.value.startswith("1."):
# node.value.value = node.value.value.split("-")[0]
node.value.value = os.environ.get('patchright_driver_version') or patchright_version
node.value.value = patchright_driver_version

# Modify url
# Modify url (pre-1.61 cdn form; no-op on 1.61+)
if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant) and isinstance(node.targets[0], ast.Name):
if node.targets[0].id == "url" and node.value.value == "https://cdn.playwright.dev/builds/driver/":
node.value = ast.JoinedStr(
values=[
ast.Constant(value='https://github.com/bugbasesecurity/patchright/releases/download/v'),
ast.Constant(value=f'https://github.com/{DRIVER_REPO}/releases/download/v'),
ast.FormattedValue(value=ast.Name(id='driver_version', ctx=ast.Load()), conversion=-1),
ast.Constant(value='/')
]
Expand Down
Loading