Add mobile device testing to SeleniumBase

This commit is contained in:
Michael Mintz 2019-12-09 04:03:45 -05:00
parent 39783ed3d9
commit c0247ed909
5 changed files with 132 additions and 13 deletions

View File

@ -35,6 +35,8 @@ except (ImportError, ValueError):
sb.data = None sb.data = None
sb.environment = "test" sb.environment = "test"
sb.user_agent = None sb.user_agent = None
sb.mobile_emulator = False
sb.device_metrics = None
sb.extension_zip = None sb.extension_zip = None
sb.extension_dir = None sb.extension_dir = None
sb.database_env = "test" sb.database_env = "test"

View File

@ -125,7 +125,8 @@ def _add_chrome_disable_csp_extension(chrome_options):
def _set_chrome_options( def _set_chrome_options(
downloads_path, headless, proxy_string, proxy_auth, downloads_path, headless, proxy_string, proxy_auth,
proxy_user, proxy_pass, user_agent, disable_csp, enable_sync, proxy_user, proxy_pass, user_agent, disable_csp, enable_sync,
user_data_dir, extension_zip, extension_dir): user_data_dir, extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio):
chrome_options = webdriver.ChromeOptions() chrome_options = webdriver.ChromeOptions()
prefs = { prefs = {
"download.default_directory": downloads_path, "download.default_directory": downloads_path,
@ -140,6 +141,23 @@ def _set_chrome_options(
chrome_options.add_experimental_option( chrome_options.add_experimental_option(
"excludeSwitches", ["enable-automation"]) "excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False) chrome_options.add_experimental_option("useAutomationExtension", False)
if mobile_emulator:
emulator_settings = {}
device_metrics = {}
if type(device_width) is int and type(device_height) is int and (
type(device_pixel_ratio) is int):
device_metrics["width"] = device_width
device_metrics["height"] = device_height
device_metrics["pixelRatio"] = device_pixel_ratio
else:
device_metrics["width"] = 411
device_metrics["height"] = 731
device_metrics["pixelRatio"] = 3
emulator_settings["deviceMetrics"] = device_metrics
if user_agent:
emulator_settings["userAgent"] = user_agent
chrome_options.add_experimental_option(
"mobileEmulation", emulator_settings)
if enable_sync: if enable_sync:
chrome_options.add_experimental_option( chrome_options.add_experimental_option(
"excludeSwitches", ["disable-sync"]) "excludeSwitches", ["disable-sync"])
@ -302,7 +320,8 @@ def get_driver(browser_name, headless=False, use_grid=False,
servername='localhost', port=4444, proxy_string=None, servername='localhost', port=4444, proxy_string=None,
user_agent=None, cap_file=None, disable_csp=None, user_agent=None, cap_file=None, disable_csp=None,
enable_sync=None, user_data_dir=None, enable_sync=None, user_data_dir=None,
extension_zip=None, extension_dir=None): extension_zip=None, extension_dir=None, mobile_emulator=False,
device_width=None, device_height=None, device_pixel_ratio=None):
proxy_auth = False proxy_auth = False
proxy_user = None proxy_user = None
proxy_pass = None proxy_pass = None
@ -336,19 +355,22 @@ def get_driver(browser_name, headless=False, use_grid=False,
browser_name, headless, servername, port, browser_name, headless, servername, port,
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent, proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
cap_file, disable_csp, enable_sync, user_data_dir, cap_file, disable_csp, enable_sync, user_data_dir,
extension_zip, extension_dir) extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio)
else: else:
return get_local_driver( return get_local_driver(
browser_name, headless, browser_name, headless,
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent, proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
disable_csp, enable_sync, user_data_dir, disable_csp, enable_sync, user_data_dir,
extension_zip, extension_dir) extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio)
def get_remote_driver( def get_remote_driver(
browser_name, headless, servername, port, proxy_string, proxy_auth, browser_name, headless, servername, port, proxy_string, proxy_auth,
proxy_user, proxy_pass, user_agent, cap_file, disable_csp, proxy_user, proxy_pass, user_agent, cap_file, disable_csp,
enable_sync, user_data_dir, extension_zip, extension_dir): enable_sync, user_data_dir, extension_zip, extension_dir,
mobile_emulator, device_width, device_height, device_pixel_ratio):
downloads_path = download_helper.get_downloads_folder() downloads_path = download_helper.get_downloads_folder()
download_helper.reset_downloads_folder() download_helper.reset_downloads_folder()
address = "http://%s:%s/wd/hub" % (servername, port) address = "http://%s:%s/wd/hub" % (servername, port)
@ -359,7 +381,8 @@ def get_remote_driver(
chrome_options = _set_chrome_options( chrome_options = _set_chrome_options(
downloads_path, headless, proxy_string, proxy_auth, downloads_path, headless, proxy_string, proxy_auth,
proxy_user, proxy_pass, user_agent, disable_csp, enable_sync, proxy_user, proxy_pass, user_agent, disable_csp, enable_sync,
user_data_dir, extension_zip, extension_dir) user_data_dir, extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio)
capabilities = chrome_options.to_capabilities() capabilities = chrome_options.to_capabilities()
for key in desired_caps.keys(): for key in desired_caps.keys():
capabilities[key] = desired_caps[key] capabilities[key] = desired_caps[key]
@ -469,7 +492,8 @@ def get_local_driver(
browser_name, headless, browser_name, headless,
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent, proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
disable_csp, enable_sync, user_data_dir, disable_csp, enable_sync, user_data_dir,
extension_zip, extension_dir): extension_zip, extension_dir,
mobile_emulator, device_width, device_height, device_pixel_ratio):
''' '''
Spins up a new web browser and returns the driver. Spins up a new web browser and returns the driver.
Can also be used to spin up additional browsers for the same test. Can also be used to spin up additional browsers for the same test.
@ -539,7 +563,8 @@ def get_local_driver(
downloads_path, headless, downloads_path, headless,
proxy_string, proxy_auth, proxy_user, proxy_pass, proxy_string, proxy_auth, proxy_user, proxy_pass,
user_agent, disable_csp, enable_sync, user_data_dir, user_agent, disable_csp, enable_sync, user_data_dir,
extension_zip, extension_dir) extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio)
return webdriver.Chrome(executable_path=LOCAL_EDGEDRIVER, return webdriver.Chrome(executable_path=LOCAL_EDGEDRIVER,
options=chrome_options) options=chrome_options)
else: else:
@ -563,7 +588,8 @@ def get_local_driver(
downloads_path, headless, downloads_path, headless,
proxy_string, proxy_auth, proxy_user, proxy_pass, proxy_string, proxy_auth, proxy_user, proxy_pass,
user_agent, disable_csp, enable_sync, user_data_dir, user_agent, disable_csp, enable_sync, user_data_dir,
extension_zip, extension_dir) extension_zip, extension_dir, mobile_emulator,
device_width, device_height, device_pixel_ratio)
if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER): if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER):
make_driver_executable_if_not(LOCAL_CHROMEDRIVER) make_driver_executable_if_not(LOCAL_CHROMEDRIVER)
elif not is_chromedriver_on_path(): elif not is_chromedriver_on_path():

View File

@ -78,6 +78,9 @@ class BaseCase(unittest.TestCase):
self.__last_page_screenshot_png = None self.__last_page_screenshot_png = None
self.__delayed_assert_count = 0 self.__delayed_assert_count = 0
self.__delayed_assert_failures = [] self.__delayed_assert_failures = []
self.__device_width = None
self.__device_height = None
self.__device_pixel_ratio = None
# Requires self._* instead of self.__* for external class use # Requires self._* instead of self.__* for external class use
self._html_report_extra = [] # (Used by pytest_plugin.py) self._html_report_extra = [] # (Used by pytest_plugin.py)
self._default_driver = None self._default_driver = None
@ -1339,7 +1342,8 @@ class BaseCase(unittest.TestCase):
servername=None, port=None, proxy=None, agent=None, servername=None, port=None, proxy=None, agent=None,
switch_to=True, cap_file=None, disable_csp=None, switch_to=True, cap_file=None, disable_csp=None,
enable_sync=None, user_data_dir=None, enable_sync=None, user_data_dir=None,
extension_zip=None, extension_dir=None): extension_zip=None, extension_dir=None, is_mobile=False,
d_width=None, d_height=None, d_p_r=None):
""" This method spins up an extra browser for tests that require """ This method spins up an extra browser for tests that require
more than one. The first browser is already provided by tests more than one. The first browser is already provided by tests
that import base_case.BaseCase from seleniumbase. If parameters that import base_case.BaseCase from seleniumbase. If parameters
@ -1357,6 +1361,10 @@ class BaseCase(unittest.TestCase):
user_data_dir - Chrome's User Data Directory to use (Chrome-only) user_data_dir - Chrome's User Data Directory to use (Chrome-only)
extension_zip - A Chrome Extension ZIP file to use (Chrome-only) extension_zip - A Chrome Extension ZIP file to use (Chrome-only)
extension_dir - A Chrome Extension folder to use (Chrome-only) extension_dir - A Chrome Extension folder to use (Chrome-only)
is_mobile - the option to use the mobile emulator (Chrome-only)
d_width - the device width of the mobile emulator (Chrome-only)
d_height - the device height of the mobile emulator (Chrome-only)
d_p_r - the device pixel ratio of the mobile emulator (Chrome-only)
""" """
if self.browser == "remote" and self.servername == "localhost": if self.browser == "remote" and self.servername == "localhost":
raise Exception('Cannot use "remote" browser driver on localhost!' raise Exception('Cannot use "remote" browser driver on localhost!'
@ -1413,6 +1421,14 @@ class BaseCase(unittest.TestCase):
# disable_csp = True # disable_csp = True
if cap_file is None: if cap_file is None:
cap_file = self.cap_file cap_file = self.cap_file
if is_mobile is None:
is_mobile = False
if d_width is None:
d_width = self.__device_width
if d_height is None:
d_height = self.__device_height
if d_p_r is None:
d_p_r = self.__device_pixel_ratio
valid_browsers = constants.ValidBrowsers.valid_browsers valid_browsers = constants.ValidBrowsers.valid_browsers
if browser_name not in valid_browsers: if browser_name not in valid_browsers:
raise Exception("Browser: {%s} is not a valid browser option. " raise Exception("Browser: {%s} is not a valid browser option. "
@ -1431,7 +1447,11 @@ class BaseCase(unittest.TestCase):
enable_sync=enable_sync, enable_sync=enable_sync,
user_data_dir=user_data_dir, user_data_dir=user_data_dir,
extension_zip=extension_zip, extension_zip=extension_zip,
extension_dir=extension_dir) extension_dir=extension_dir,
mobile_emulator=is_mobile,
device_width=d_width,
device_height=d_height,
device_pixel_ratio=d_p_r)
self._drivers_list.append(new_driver) self._drivers_list.append(new_driver)
if switch_to: if switch_to:
self.driver = new_driver self.driver = new_driver
@ -4112,6 +4132,8 @@ class BaseCase(unittest.TestCase):
self.port = sb_config.port self.port = sb_config.port
self.proxy_string = sb_config.proxy_string self.proxy_string = sb_config.proxy_string
self.user_agent = sb_config.user_agent self.user_agent = sb_config.user_agent
self.mobile_emulator = sb_config.mobile_emulator
self.device_metrics = sb_config.device_metrics
self.cap_file = sb_config.cap_file self.cap_file = sb_config.cap_file
self.settings_file = sb_config.settings_file self.settings_file = sb_config.settings_file
self.database_env = sb_config.database_env self.database_env = sb_config.database_env
@ -4195,6 +4217,32 @@ class BaseCase(unittest.TestCase):
""" >>> "python setup.py develop" <<< """) """ >>> "python setup.py develop" <<< """)
if self.settings_file: if self.settings_file:
settings_parser.set_settings(self.settings_file) settings_parser.set_settings(self.settings_file)
# Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio
if self.device_metrics:
metrics_string = self.device_metrics
metrics_string = metrics_string.replace(' ', '')
metrics_list = metrics_string.split(',')
exception_string = (
'Invalid input for Mobile Emulator device metrics!\n'
'Expecting a comma-separated string with three\n'
'integer values for Width, Height, and Pixel-Ratio.\n'
'Example: --metrics="411,731,3" ')
if len(metrics_list) != 3:
raise Exception(exception_string)
try:
self.__device_width = int(metrics_list[0])
self.__device_height = int(metrics_list[1])
self.__device_pixel_ratio = int(metrics_list[2])
self.mobile_emulator = True
except Exception:
raise Exception(exception_string)
if self.mobile_emulator:
if not self.user_agent:
# Use the Pixel 3 user agent by default if not specified
self.user_agent = (
"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/76.0.3809.132 Mobile Safari/537.36")
has_url = False has_url = False
if self._reuse_session: if self._reuse_session:
@ -4210,7 +4258,6 @@ class BaseCase(unittest.TestCase):
has_url = True has_url = True
except Exception: except Exception:
pass pass
if self._reuse_session and sb_config.shared_driver and has_url: if self._reuse_session and sb_config.shared_driver and has_url:
if self.start_page and len(self.start_page) >= 4: if self.start_page and len(self.start_page) >= 4:
if page_utils.is_valid_url(self.start_page): if page_utils.is_valid_url(self.start_page):
@ -4236,7 +4283,11 @@ class BaseCase(unittest.TestCase):
enable_sync=self.enable_sync, enable_sync=self.enable_sync,
user_data_dir=self.user_data_dir, user_data_dir=self.user_data_dir,
extension_zip=self.extension_zip, extension_zip=self.extension_zip,
extension_dir=self.extension_dir) extension_dir=self.extension_dir,
is_mobile=self.mobile_emulator,
d_width=self.__device_width,
d_height=self.__device_height,
d_p_r=self.__device_pixel_ratio)
self._default_driver = self.driver self._default_driver = self.driver
if self._reuse_session: if self._reuse_session:
sb_config.shared_driver = self.driver sb_config.shared_driver = self.driver

View File

@ -22,6 +22,8 @@ def pytest_addoption(parser):
--port=PORT (The port that's used by the test server.) --port=PORT (The port that's used by the test server.)
--proxy=SERVER:PORT (This is the proxy server:port combo used by tests.) --proxy=SERVER:PORT (This is the proxy server:port combo used by tests.)
--agent=STRING (This designates the web browser's User Agent to use.) --agent=STRING (This designates the web browser's User Agent to use.)
--mobile (The option to use the mobile emulator while running tests.)
--metrics=STRING ("CSSWidth,Height,PixelRatio" for mobile emulator tests.)
--extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.) --extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.)
--extension-dir=DIR (Load a Chrome Extension directory, comma-separated.) --extension-dir=DIR (Load a Chrome Extension directory, comma-separated.)
--headless (The option to run tests headlessly. The default on Linux OS.) --headless (The option to run tests headlessly. The default on Linux OS.)
@ -185,6 +187,21 @@ def pytest_addoption(parser):
help="""Designates the User-Agent for the browser to use. help="""Designates the User-Agent for the browser to use.
Format: A string. Format: A string.
Default: None.""") Default: None.""")
parser.addoption('--mobile', '--mobile-emulator', '--mobile_emulator',
action="store_true",
dest='mobile_emulator',
default=False,
help="""If this option is enabled, the mobile emulator
will be used while running tests.""")
parser.addoption('--metrics', '--device-metrics', '--device_metrics',
action='store',
dest='device_metrics',
default=None,
help="""Designates the three device metrics of the mobile
emulator: CSS Width, CSS Height, and Pixel-Ratio.
Format: A comma-separated string with the 3 values.
Example: "375,734,3"
Default: None. (Will use default values if None)""")
parser.addoption('--extension_zip', '--extension-zip', parser.addoption('--extension_zip', '--extension-zip',
action='store', action='store',
dest='extension_zip', dest='extension_zip',
@ -337,6 +354,8 @@ def pytest_configure(config):
sb_config.environment = config.getoption('environment') sb_config.environment = config.getoption('environment')
sb_config.with_selenium = config.getoption('with_selenium') sb_config.with_selenium = config.getoption('with_selenium')
sb_config.user_agent = config.getoption('user_agent') sb_config.user_agent = config.getoption('user_agent')
sb_config.mobile_emulator = config.getoption('mobile_emulator')
sb_config.device_metrics = config.getoption('device_metrics')
sb_config.headless = config.getoption('headless') sb_config.headless = config.getoption('headless')
sb_config.headed = config.getoption('headed') sb_config.headed = config.getoption('headed')
sb_config.start_page = config.getoption('start_page') sb_config.start_page = config.getoption('start_page')

View File

@ -17,6 +17,8 @@ class SeleniumBrowser(Plugin):
--port=PORT (The port that's used by the test server.) --port=PORT (The port that's used by the test server.)
--proxy=SERVER:PORT (This is the proxy server:port combo used by tests.) --proxy=SERVER:PORT (This is the proxy server:port combo used by tests.)
--agent=STRING (This designates the web browser's User Agent to use.) --agent=STRING (This designates the web browser's User Agent to use.)
--mobile (The option to use the mobile emulator while running tests.)
--metrics=STRING ("CSSWidth,Height,PixelRatio" for mobile emulator tests.)
--extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.) --extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.)
--extension-dir=DIR (Load a Chrome Extension directory, comma-separated.) --extension-dir=DIR (Load a Chrome Extension directory, comma-separated.)
--headless (The option to run tests headlessly. The default on Linux OS.) --headless (The option to run tests headlessly. The default on Linux OS.)
@ -104,6 +106,23 @@ class SeleniumBrowser(Plugin):
help="""Designates the User-Agent for the browser to use. help="""Designates the User-Agent for the browser to use.
Format: A string. Format: A string.
Default: None.""") Default: None.""")
parser.add_option(
'--mobile', '--mobile-emulator', '--mobile_emulator',
action="store_true",
dest='mobile_emulator',
default=False,
help="""If this option is enabled, the mobile emulator
will be used while running tests.""")
parser.add_option(
'--metrics', '--device-metrics', '--device_metrics',
action='store',
dest='device_metrics',
default=None,
help="""Designates the three device metrics of the mobile
emulator: CSS Width, CSS Height, and Pixel-Ratio.
Format: A comma-separated string with the 3 values.
Example: "375,734,3"
Default: None. (Will use default values if None)""")
parser.add_option( parser.add_option(
'--extension_zip', '--extension-zip', '--extension_zip', '--extension-zip',
action='store', action='store',
@ -272,6 +291,8 @@ class SeleniumBrowser(Plugin):
test.test.extension_dir = self.options.extension_dir test.test.extension_dir = self.options.extension_dir
test.test.proxy_string = self.options.proxy_string test.test.proxy_string = self.options.proxy_string
test.test.user_agent = self.options.user_agent test.test.user_agent = self.options.user_agent
test.test.mobile_emulator = self.options.mobile_emulator
test.test.device_metrics = self.options.device_metrics
test.test.slow_mode = self.options.slow_mode test.test.slow_mode = self.options.slow_mode
test.test.demo_mode = self.options.demo_mode test.test.demo_mode = self.options.demo_mode
test.test.demo_sleep = self.options.demo_sleep test.test.demo_sleep = self.options.demo_sleep