Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🚀 Feature]: Support custom headers for remote WebDriver connection (python) #10892

Open
scook12 opened this issue Jul 20, 2022 · 11 comments
Open
Assignees

Comments

@scook12
Copy link

scook12 commented Jul 20, 2022

Feature and motivation

In RemoteConnection, the get_remote_connection_headers method has a set of logical defaults it builds, but there's no way for a user to make an addition to these headers.

Adding a hook or property that the consuming code can set to add to these headers would be a big help.

Usage example

I need to add a host header for requests to be successful to my selenium grid running behind traefik reverse proxy. Without this feature, I can't use selenium python out of the box to create sessions (initializing WebDriver throws 404 errors because traefik doesn't know where to route without the host header).

Passing a parameter from WebDriver to RemoteConnection ought to do the trick:

driver = WebDriver(command_executor, additional_headers={"Host": "some-value"})
@github-actions
Copy link

@scook12, thank you for creating this issue. We will troubleshoot it as soon as we can.


Info for maintainers

Triage this issue by using labels.

If information is missing, add a helpful comment and then I-issue-template label.

If the issue is a question, add the I-question label.

If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

After troubleshooting the issue, please add the R-awaiting answer label.

Thank you!

@scook12 scook12 changed the title [🚀 Feature]: Support custom headers for remote WebDriver connection [🚀 Feature]: Support custom headers for remote WebDriver connection (python) Jul 20, 2022
@symonk
Copy link
Member

symonk commented Jul 23, 2022

I think as a workaround you an pass your own RemoteConnection object as a command executor at the moment tho thats a bit heavy handed for the use case maybe, will take a look when i'm home

@jamesmortensen
Copy link
Member

jamesmortensen commented Jul 28, 2022

Just to clarify, @scook12 are you wanting to set a custom header to allow your test code to access a Selenium Grid on a secured remote server?

For instance, WebdriverIO lets me do something like this in my configuration file:

    port: 443,
    protocol: 'https',
    hostname: 'myremoteseleniumserver.example.com',
    headers: {
        'authorization': 'Bearer some_token_which_the_server_uses_to_keep_out_bad_actors'
    },
    strictSSL: true,
    path: '/wd/hub'

Is something like this what you're looking for, or are you looking to control the headers that the browser sends to the app under test?

@scook12
Copy link
Author

scook12 commented Aug 11, 2022

@jamesmortensen yes, I need to control headers sent to the grid!

My grid is deployed behind Traefik and using the Host directive requires this header, so my test code needs to pass it to for reverse proxy to route appropriately.

For more context, the intent was to use subdomains to organize my hubs. We test on web and mobile (via appium) deployed on docker swarm. I wanted mobile.domain.com and web.domain.com to route to the appropriate hub service, depending on which platform our test code is being run against.

To do that, I just need to include the Host header in requests sent via WebDriver to the grid. I tested this in a debug session, and it worked, but monkeypatching these headers is really convoluted and creating our own RemoteConnection object is, as symonk mentioned, a pretty heavy solution for a one-line change to the current behavior.

Hopefully that helps clarify!

@jamesmortensen
Copy link
Member

jamesmortensen commented Aug 13, 2022

UPDATE: Unpublished and renamed to "selenium-proxy" as it's incorrect to say it's a "reverse proxy". Sorry for any inconvenience.


@scook12 I have had some success with this Selenium Proxy Server which I just published. I wanted to be able to set the authorization header, but not every test automation library makes that easy to do. With the proxy server, we can modify the headers for any framework we want to use just by pointing the testing library to the proxy server.

So what you can do is configure your testing library to go through this proxy, and then configure the proxy to communicate securely to your Selenium grid. The proxy handles rewriting the headers with the token so the testing library doesn't need to. It's written in Node.js, but that shouldn't be a problem since Selenium WebDriver is a standard. If you try it out, let me know if you run into any trouble.

@titusfortner
Copy link
Member

@symonk we could add an additional_headers argument to constructor or as a class property in RemoteConnection to update the default headers

In the meantime the user *could subclass the browser specific subclass, but that is clumsy indeed.

@kaliiiiiiiiii
Copy link

Actually, I found a quite sneaky way without using a Proxy or Extension:

You can change the Headers in continue_request() at my_headers = {"sec-ch-ua-platform": "Android"} .

# resource: https://stackoverflow.com/questions/66227508/selenium-4-0-0-beta-1-how-add-event-listeners-in-cdp

class cdp_listener:
    from typing import Dict

    def __init__(self):
        self.listeners = {}
    async def async_helper(self):
        async with driver.bidi_connection() as connection:
            session, devtools = connection.session, connection.devtools

            for listener in self.listeners.items():
                my_listener = await listener[1]["listener"](connection=connection)

                async for event in my_listener:
                    try:
                        await session.execute(listener[1]["at_event"](event=event, connection=connection))
                    except Exception as e:
                        print(e)

    def trio_helper(self):
        import trio
        trio.run(self.async_helper)

    def start_threaded(self, listeners: Dict[str,Dict[callable, callable]] = {}):
        if listeners:
            self.listeners = listeners

        import threading
        thread = threading.Thread(target=self.trio_helper)
        thread.start()
        return thread

    def add_listeners(self, listeners: Dict[str,Dict[callable, callable]]):
        self.listeners = listeners

    def remove_listener(self, listener:str):
        del self.listeners[listener]

async def all_requests(connection):
    session, devtools = connection.session, connection.devtools
    pattern = map(devtools.fetch.RequestPattern.from_json,[{"urlPattern":"*"}])
    pattern = list(pattern)
    await session.execute(devtools.fetch.enable(patterns=pattern))

    return session.listen(devtools.fetch.RequestPaused)

def continue_request(event, connection):
    print({"type":event.resource_type.to_json(),"frame_id": event.frame_id, "url": event.request.url})
    session, devtools = connection.session, connection.devtools

    headers = event.request.headers.to_json()

    my_headers = {"sec-ch-ua-platform": "Android"}
    headers.update(my_headers)
    my_headers = []
    for item in headers.items():
        my_headers.append(devtools.fetch.HeaderEntry.from_json({"name": item[0], "value": item[1]}))

    return devtools.fetch.continue_request(request_id=event.request_id, headers=my_headers)


cdp_listener = cdp_listener()
thread = cdp_listener.start_threaded(listeners= {"continue":{"listener":all_requests,"at_event":continue_request}})

driver.get('https://modheader.com/headers?product=ModHeader')

@github-actions
Copy link

This issue is stale because it has been open 280 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions bot added the I-stale Applied to issues that become stale, and eventually closed. label Oct 17, 2023
@kaliiiiiiiiii
Copy link

pretty sure this would have to be added to the webdriver spec or (if it isn't already) to Webdriver BiDi

@titusfortner
Copy link
Member

titusfortner commented Oct 17, 2023

There's 2 pieces.

  • The Remote Connection class uses an http client to send requests and receive responses between the code and the driver/server.
  • All assets in the browser are loaded via responses to requests.

BiDi/network interception manages the second one. This issue relates to the first one.

@github-actions github-actions bot removed the I-stale Applied to issues that become stale, and eventually closed. label Oct 17, 2023
@titusfortner
Copy link
Member

Hmm, this might be a part of this larger issue — #12368

@titusfortner titusfortner self-assigned this Dec 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants