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

Python **does** support the features in Remote driver sessions #1621

Open
3 of 5 tasks
j3soon opened this issue Dec 18, 2022 · 2 comments
Open
3 of 5 tasks

Python **does** support the features in Remote driver sessions #1621

j3soon opened this issue Dec 18, 2022 · 2 comments
Labels
documentation Improvements or additions to documentation

Comments

@j3soon
Copy link

j3soon commented Dec 18, 2022

📃 Documentation Issue

Have you read the Contributing Guidelines on issues?

  • Yes

Description

The doc mentioned that Selenium Python does not support 4 features when using remote driver sessions:

Python does not support this feature in Remote driver sessions.

In fact, Selenium Python does support the features by a simple workaround. Take Install and Uninstall Add-ons for Firefox as an example:

addon_id = webdriver.Firefox.install_addon(driver, "resources/ninja_saucebot-1.0-an+fx.xpi")
webdriver.Firefox.uninstall_addon(driver, addon_id)

Doc URL:

Suggested Fix

Documentation Checklist

  • Typos
  • Grammar
  • Incorrect information
  • Missing information
@j3soon j3soon added the documentation Improvements or additions to documentation label Dec 18, 2022
@titusfortner
Copy link
Contributor

Nice! I didn't realize you could do this. Doesn't look like this is working for everything, but I kind of think it might be a Selenium problem.

I was able to get the Firefox Context change working, but...

setting Chrome's network conditions gives:

E KeyError: 'setNetworkConditions'

and Firefox's full page screenshot does:

E AttributeError: 'WebDriver' object has no attribute 'get_full_page_screenshot_as_png'

@j3soon would you be interested in creating a PR in the docs with working examples?
And/or helping me & the other Selenium devs figure out why these aren't working with Python Selenium?

@j3soon
Copy link
Author

j3soon commented Dec 19, 2022

@titusfortner, The errors you encountered require more complicated workarounds. I have provided these workarounds below.

Currently, I don't plan to create a PR with working examples on the saucelabs-training/demo-python repository, as I'm not familiar with saucelabs.

Nevertheless, I'm familiar with Selenium and have noticed that the workarounds can be greatly simplified with some small modifications in the Selenium codebase. I believe that exposing these functions in a similar manner to the Augmenter in Java could be helpful. If any Selenium devs are interested in implementing this feature, I would be happy to assist.

(Python) Network Conditions

The entries for network conditions are set during the initialization of ChromiumRemoteConnection, which aren't set for remote webdrivers. Therfore, we need to set the three entries manually.

  • set_network_conditions uses _commands["setNetworkConditions"],
  • get_network_conditions uses _commands["getNetworkConditions"], and
  • delete_network_conditions uses _commands["deleteNetworkConditions"].
  • See the source code for further info.
from selenium import webdriver

driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    options=webdriver.ChromeOptions(),
)

driver.command_executor._commands["setNetworkConditions"] = ("POST", "/session/$sessionId/chromium/network_conditions")
driver.command_executor._commands["getNetworkConditions"] = ("GET", "/session/$sessionId/chromium/network_conditions")
driver.command_executor._commands["deleteNetworkConditions"] = ("DELETE", "/session/$sessionId/chromium/network_conditions")

webdriver.Chrome.set_network_conditions(
    driver,
    offline=False,
    latency=5,  # additional latency (ms)
    download_throughput=500 * 1024,  # maximal throughput
    upload_throughput=500 * 1024,  # maximal throughput
)
ret = webdriver.Chrome.get_network_conditions(driver)
print(ret)
webdriver.Chrome.delete_network_conditions(driver)

(Python) Full-Page Screenshots

The simple workaround works for:

  • get_full_page_screenshot_as_base64, since it simply calls self.execute("FULL_PAGE_SCREENSHOT").
  • See the source code for further info.

But we cannot call:

  • get_full_page_screenshot_as_file,
  • save_full_page_screenshot, and
  • get_full_page_screenshot_as_png

directly, due to their use of self.get_full_page_screenshot....

However, we can simply copy & re-write the functions above by modifying the calls to self.get_full_page_screenshot....

import base64
import warnings
from selenium import webdriver

def get_full_page_screenshot_as_file(driver, filename) -> bool:
    """
    Saves a full document screenshot of the current window to a PNG image file. Returns
        False if there is any IOError, else returns True. Use full paths in
        your filename.

    :Args:
        - filename: The full path you wish to save your screenshot to. This
        should end with a `.png` extension.

    :Usage:
        ::

            get_full_page_screenshot_as_file(driver, '/Screenshots/foo.png')
    """
    if not filename.lower().endswith(".png"):
        warnings.warn(
            "name used for saved screenshot does not match file " "type. It should end with a `.png` extension",
            UserWarning,
        )
    png = get_full_page_screenshot_as_png(driver)
    try:
        with open(filename, "wb") as f:
            f.write(png)
    except OSError:
        return False
    finally:
        del png
    return True

def save_full_page_screenshot(driver, filename) -> bool:
    """
    Saves a full document screenshot of the current window to a PNG image file. Returns
        False if there is any IOError, else returns True. Use full paths in
        your filename.

    :Args:
        - filename: The full path you wish to save your screenshot to. This
        should end with a `.png` extension.

    :Usage:
        ::

            save_full_page_screenshot(driver, '/Screenshots/foo.png')
    """
    return get_full_page_screenshot_as_file(driver, filename)

def get_full_page_screenshot_as_png(driver) -> bytes:
    """
    Gets the full document screenshot of the current window as a binary data.

    :Usage:
        ::

            get_full_page_screenshot_as_png(driver)
    """
    return base64.b64decode(webdriver.Firefox.get_full_page_screenshot_as_base64(driver).encode("ascii"))

driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    options=webdriver.FirefoxOptions(),
)

b64 = webdriver.Firefox.get_full_page_screenshot_as_base64(driver)
print('get_full_page_screenshot_as_base64', b64)
png = get_full_page_screenshot_as_png(driver)
print('get_full_page_screenshot_as_png', png)
ret = get_full_page_screenshot_as_file(driver, 'screenshot1.png')
print('get_full_page_screenshot_as_file', ret)
ret = save_full_page_screenshot(driver, 'screenshot2.png')
print('save_full_page_screenshot', ret)

(Python) Install and Uninstall Add-ons

The simple workaround works since:

  • install_addon base64 encodes the zipped add-on and calls self.execute("INSTALL_ADDON", ...), while
  • uninstall_addon simply calls self.execute("UNINSTALL_ADDON", ...).
  • See the source code for further info.
from selenium import webdriver

driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    options=webdriver.FirefoxOptions(),
)

addon_id = webdriver.Firefox.install_addon(driver, "resources/ninja_saucebot-1.0-an+fx.xpi")
webdriver.Firefox.uninstall_addon(driver, addon_id)

(Python) Change Preferences During Session

The simple workaround works for:

  • set_context since it simply calls self.execute("SET_CONTEXT", ...),

but requires some copy & re-write for:

  • context, due to the use of self.set_context.
  • See the source code for further info.
from contextlib import contextmanager
from selenium import webdriver

@contextmanager
def context(driver, context):
    """Sets the context that Selenium commands are running in using
    a `with` statement. The state of the context on the server is
    saved before entering the block, and restored upon exiting it.

    :param context: Context, may be one of the class properties
        `CONTEXT_CHROME` or `CONTEXT_CONTENT`.

    Usage example::

        with selenium.context(selenium.CONTEXT_CHROME):
            # chrome scope
            ... do stuff ...
    """
    initial_context = driver.execute("GET_CONTEXT").pop("value")
    webdriver.Firefox.set_context(driver, context)
    try:
        yield
    finally:
        webdriver.Firefox.set_context(driver, initial_context)

driver = webdriver.Remote(
    command_executor='http://localhost:4444/wd/hub',
    options=webdriver.FirefoxOptions(),
)
            
webdriver.Firefox.set_context(driver, webdriver.Firefox.CONTEXT_CHROME)
# chrome scope
webdriver.Firefox.set_context(driver, webdriver.Firefox.CONTEXT_CONTENT)

with context(driver, webdriver.Firefox.CONTEXT_CHROME):
    # chrome scope
    pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants