playwright-python icon indicating copy to clipboard operation
playwright-python copied to clipboard

Wrong ProxySettings type hints

Open povilasb opened this issue 2 years ago • 1 comments

Context:

  • Playwright Version: 1.21.0
  • Operating System: Mac
  • Python version: 3.10.1
  • Browser: All

Code Snippet

import asyncio

from playwright.async_api import async_playwright, ProxySettings


async def main():
    pw = await async_playwright().start()
    browser = await pw.firefox.launch()
    proxy_settings = ProxySettings(
        server="http://localhost:8080", username=None, password=None
    )
    ctx = await browser.new_context(proxy=proxy_settings, ignore_https_errors=True)

    page = await ctx.new_page()
    resp = await page.goto("https://httpbin.org/ip")
    print(resp)


asyncio.run(main())

Describe the bug

  1. run mitmproxy, or any other HTTP/HTTPS proxy.
  2. Run the sample.
  3. playwright crashes with
Traceback (most recent call last):
  File "/private/tmp/main.py", line 19, in <module>
    asyncio.run(main())
  File "/Users/povilas/.pyenv/versions/3.10.1/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/povilas/.pyenv/versions/3.10.1/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
    return future.result()
  File "/private/tmp/main.py", line 12, in main
    ctx = await browser.new_context(proxy=proxy_settings, ignore_https_errors=True)
  File "/private/tmp/pyenv/lib/python3.10/site-packages/playwright/async_api/_generated.py", line 11317, in new_context
    await self._async(
  File "/private/tmp/pyenv/lib/python3.10/site-packages/playwright/_impl/_browser.py", line 117, in new_context
    channel = await self._channel.send("newContext", params)
  File "/private/tmp/pyenv/lib/python3.10/site-packages/playwright/_impl/_connection.py", line 39, in send
    return await self.inner_send(method, params, False)
  File "/private/tmp/pyenv/lib/python3.10/site-packages/playwright/_impl/_connection.py", line 63, in inner_send
    result = next(iter(done)).result()
playwright._impl._api_types.Error: proxy.username: expected string, got object
  1. ProxySettings declaration is
class ProxySettings(TypedDict, total=False):
    server: str
    bypass: Optional[str]
    username: Optional[str]
    password: Optional[str]
  1. But clearly None is not allowed for username and password. This, however, works:
    proxy_settings = ProxySettings(
        server="http://localhost:8080", username="", password=""
    )

povilasb avatar Apr 22 '22 12:04 povilasb

I reproduced the error as expected when declaring proxy_settings as:

    proxy_settings = ProxySettings(
        server="http://localhost:8080", username=None, password=None
    )
    # results in a dict with these items:
    # dict_items([('server', 'http://localhost:8080'), ('username', None), ('password', None)])

Note, that another workaround is:

    proxy_settings = ProxySettings(
        server="http://localhost:8080"
    )
    # results in a dict with these items:
    # dict_items([('server', 'http://localhost:8080')])

So, username and password are implicitly optional, but if you explicitly declare them as None things break (which is an issue).

A clean fix to the problem is updating locals_to_params as:

def locals_to_params(args: Dict) -> Dict:
    copy = {}
    for key in args:
        if key == "self":
            continue
        if args[key] is not None:
            copy[key] = args[key] if not isinstance(args[key], Dict) else locals_to_params(args[key])
    return copy

danphenderson avatar Feb 15 '24 00:02 danphenderson