Skip to content

Commit c0247ed

Browse files
committed
Add mobile device testing to SeleniumBase
1 parent 39783ed commit c0247ed

File tree

5 files changed

+132
-13
lines changed

5 files changed

+132
-13
lines changed

examples/raw_parameter_script.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
sb.data = None
3636
sb.environment = "test"
3737
sb.user_agent = None
38+
sb.mobile_emulator = False
39+
sb.device_metrics = None
3840
sb.extension_zip = None
3941
sb.extension_dir = None
4042
sb.database_env = "test"

seleniumbase/core/browser_launcher.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ def _add_chrome_disable_csp_extension(chrome_options):
125125
def _set_chrome_options(
126126
downloads_path, headless, proxy_string, proxy_auth,
127127
proxy_user, proxy_pass, user_agent, disable_csp, enable_sync,
128-
user_data_dir, extension_zip, extension_dir):
128+
user_data_dir, extension_zip, extension_dir, mobile_emulator,
129+
device_width, device_height, device_pixel_ratio):
129130
chrome_options = webdriver.ChromeOptions()
130131
prefs = {
131132
"download.default_directory": downloads_path,
@@ -140,6 +141,23 @@ def _set_chrome_options(
140141
chrome_options.add_experimental_option(
141142
"excludeSwitches", ["enable-automation"])
142143
chrome_options.add_experimental_option("useAutomationExtension", False)
144+
if mobile_emulator:
145+
emulator_settings = {}
146+
device_metrics = {}
147+
if type(device_width) is int and type(device_height) is int and (
148+
type(device_pixel_ratio) is int):
149+
device_metrics["width"] = device_width
150+
device_metrics["height"] = device_height
151+
device_metrics["pixelRatio"] = device_pixel_ratio
152+
else:
153+
device_metrics["width"] = 411
154+
device_metrics["height"] = 731
155+
device_metrics["pixelRatio"] = 3
156+
emulator_settings["deviceMetrics"] = device_metrics
157+
if user_agent:
158+
emulator_settings["userAgent"] = user_agent
159+
chrome_options.add_experimental_option(
160+
"mobileEmulation", emulator_settings)
143161
if enable_sync:
144162
chrome_options.add_experimental_option(
145163
"excludeSwitches", ["disable-sync"])
@@ -302,7 +320,8 @@ def get_driver(browser_name, headless=False, use_grid=False,
302320
servername='localhost', port=4444, proxy_string=None,
303321
user_agent=None, cap_file=None, disable_csp=None,
304322
enable_sync=None, user_data_dir=None,
305-
extension_zip=None, extension_dir=None):
323+
extension_zip=None, extension_dir=None, mobile_emulator=False,
324+
device_width=None, device_height=None, device_pixel_ratio=None):
306325
proxy_auth = False
307326
proxy_user = None
308327
proxy_pass = None
@@ -336,19 +355,22 @@ def get_driver(browser_name, headless=False, use_grid=False,
336355
browser_name, headless, servername, port,
337356
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
338357
cap_file, disable_csp, enable_sync, user_data_dir,
339-
extension_zip, extension_dir)
358+
extension_zip, extension_dir, mobile_emulator,
359+
device_width, device_height, device_pixel_ratio)
340360
else:
341361
return get_local_driver(
342362
browser_name, headless,
343363
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
344364
disable_csp, enable_sync, user_data_dir,
345-
extension_zip, extension_dir)
365+
extension_zip, extension_dir, mobile_emulator,
366+
device_width, device_height, device_pixel_ratio)
346367

347368

348369
def get_remote_driver(
349370
browser_name, headless, servername, port, proxy_string, proxy_auth,
350371
proxy_user, proxy_pass, user_agent, cap_file, disable_csp,
351-
enable_sync, user_data_dir, extension_zip, extension_dir):
372+
enable_sync, user_data_dir, extension_zip, extension_dir,
373+
mobile_emulator, device_width, device_height, device_pixel_ratio):
352374
downloads_path = download_helper.get_downloads_folder()
353375
download_helper.reset_downloads_folder()
354376
address = "http://%s:%s/wd/hub" % (servername, port)
@@ -359,7 +381,8 @@ def get_remote_driver(
359381
chrome_options = _set_chrome_options(
360382
downloads_path, headless, proxy_string, proxy_auth,
361383
proxy_user, proxy_pass, user_agent, disable_csp, enable_sync,
362-
user_data_dir, extension_zip, extension_dir)
384+
user_data_dir, extension_zip, extension_dir, mobile_emulator,
385+
device_width, device_height, device_pixel_ratio)
363386
capabilities = chrome_options.to_capabilities()
364387
for key in desired_caps.keys():
365388
capabilities[key] = desired_caps[key]
@@ -469,7 +492,8 @@ def get_local_driver(
469492
browser_name, headless,
470493
proxy_string, proxy_auth, proxy_user, proxy_pass, user_agent,
471494
disable_csp, enable_sync, user_data_dir,
472-
extension_zip, extension_dir):
495+
extension_zip, extension_dir,
496+
mobile_emulator, device_width, device_height, device_pixel_ratio):
473497
'''
474498
Spins up a new web browser and returns the driver.
475499
Can also be used to spin up additional browsers for the same test.
@@ -539,7 +563,8 @@ def get_local_driver(
539563
downloads_path, headless,
540564
proxy_string, proxy_auth, proxy_user, proxy_pass,
541565
user_agent, disable_csp, enable_sync, user_data_dir,
542-
extension_zip, extension_dir)
566+
extension_zip, extension_dir, mobile_emulator,
567+
device_width, device_height, device_pixel_ratio)
543568
return webdriver.Chrome(executable_path=LOCAL_EDGEDRIVER,
544569
options=chrome_options)
545570
else:
@@ -563,7 +588,8 @@ def get_local_driver(
563588
downloads_path, headless,
564589
proxy_string, proxy_auth, proxy_user, proxy_pass,
565590
user_agent, disable_csp, enable_sync, user_data_dir,
566-
extension_zip, extension_dir)
591+
extension_zip, extension_dir, mobile_emulator,
592+
device_width, device_height, device_pixel_ratio)
567593
if LOCAL_CHROMEDRIVER and os.path.exists(LOCAL_CHROMEDRIVER):
568594
make_driver_executable_if_not(LOCAL_CHROMEDRIVER)
569595
elif not is_chromedriver_on_path():

seleniumbase/fixtures/base_case.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ def __init__(self, *args, **kwargs):
7878
self.__last_page_screenshot_png = None
7979
self.__delayed_assert_count = 0
8080
self.__delayed_assert_failures = []
81+
self.__device_width = None
82+
self.__device_height = None
83+
self.__device_pixel_ratio = None
8184
# Requires self._* instead of self.__* for external class use
8285
self._html_report_extra = [] # (Used by pytest_plugin.py)
8386
self._default_driver = None
@@ -1339,7 +1342,8 @@ def get_new_driver(self, browser=None, headless=None,
13391342
servername=None, port=None, proxy=None, agent=None,
13401343
switch_to=True, cap_file=None, disable_csp=None,
13411344
enable_sync=None, user_data_dir=None,
1342-
extension_zip=None, extension_dir=None):
1345+
extension_zip=None, extension_dir=None, is_mobile=False,
1346+
d_width=None, d_height=None, d_p_r=None):
13431347
""" This method spins up an extra browser for tests that require
13441348
more than one. The first browser is already provided by tests
13451349
that import base_case.BaseCase from seleniumbase. If parameters
@@ -1357,6 +1361,10 @@ def get_new_driver(self, browser=None, headless=None,
13571361
user_data_dir - Chrome's User Data Directory to use (Chrome-only)
13581362
extension_zip - A Chrome Extension ZIP file to use (Chrome-only)
13591363
extension_dir - A Chrome Extension folder to use (Chrome-only)
1364+
is_mobile - the option to use the mobile emulator (Chrome-only)
1365+
d_width - the device width of the mobile emulator (Chrome-only)
1366+
d_height - the device height of the mobile emulator (Chrome-only)
1367+
d_p_r - the device pixel ratio of the mobile emulator (Chrome-only)
13601368
"""
13611369
if self.browser == "remote" and self.servername == "localhost":
13621370
raise Exception('Cannot use "remote" browser driver on localhost!'
@@ -1413,6 +1421,14 @@ def get_new_driver(self, browser=None, headless=None,
14131421
# disable_csp = True
14141422
if cap_file is None:
14151423
cap_file = self.cap_file
1424+
if is_mobile is None:
1425+
is_mobile = False
1426+
if d_width is None:
1427+
d_width = self.__device_width
1428+
if d_height is None:
1429+
d_height = self.__device_height
1430+
if d_p_r is None:
1431+
d_p_r = self.__device_pixel_ratio
14161432
valid_browsers = constants.ValidBrowsers.valid_browsers
14171433
if browser_name not in valid_browsers:
14181434
raise Exception("Browser: {%s} is not a valid browser option. "
@@ -1431,7 +1447,11 @@ def get_new_driver(self, browser=None, headless=None,
14311447
enable_sync=enable_sync,
14321448
user_data_dir=user_data_dir,
14331449
extension_zip=extension_zip,
1434-
extension_dir=extension_dir)
1450+
extension_dir=extension_dir,
1451+
mobile_emulator=is_mobile,
1452+
device_width=d_width,
1453+
device_height=d_height,
1454+
device_pixel_ratio=d_p_r)
14351455
self._drivers_list.append(new_driver)
14361456
if switch_to:
14371457
self.driver = new_driver
@@ -4112,6 +4132,8 @@ def setUp(self, masterqa_mode=False):
41124132
self.port = sb_config.port
41134133
self.proxy_string = sb_config.proxy_string
41144134
self.user_agent = sb_config.user_agent
4135+
self.mobile_emulator = sb_config.mobile_emulator
4136+
self.device_metrics = sb_config.device_metrics
41154137
self.cap_file = sb_config.cap_file
41164138
self.settings_file = sb_config.settings_file
41174139
self.database_env = sb_config.database_env
@@ -4195,6 +4217,32 @@ def setUp(self, masterqa_mode=False):
41954217
""" >>> "python setup.py develop" <<< """)
41964218
if self.settings_file:
41974219
settings_parser.set_settings(self.settings_file)
4220+
# Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio
4221+
if self.device_metrics:
4222+
metrics_string = self.device_metrics
4223+
metrics_string = metrics_string.replace(' ', '')
4224+
metrics_list = metrics_string.split(',')
4225+
exception_string = (
4226+
'Invalid input for Mobile Emulator device metrics!\n'
4227+
'Expecting a comma-separated string with three\n'
4228+
'integer values for Width, Height, and Pixel-Ratio.\n'
4229+
'Example: --metrics="411,731,3" ')
4230+
if len(metrics_list) != 3:
4231+
raise Exception(exception_string)
4232+
try:
4233+
self.__device_width = int(metrics_list[0])
4234+
self.__device_height = int(metrics_list[1])
4235+
self.__device_pixel_ratio = int(metrics_list[2])
4236+
self.mobile_emulator = True
4237+
except Exception:
4238+
raise Exception(exception_string)
4239+
if self.mobile_emulator:
4240+
if not self.user_agent:
4241+
# Use the Pixel 3 user agent by default if not specified
4242+
self.user_agent = (
4243+
"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) "
4244+
"AppleWebKit/537.36 (KHTML, like Gecko) "
4245+
"Chrome/76.0.3809.132 Mobile Safari/537.36")
41984246

41994247
has_url = False
42004248
if self._reuse_session:
@@ -4210,7 +4258,6 @@ def setUp(self, masterqa_mode=False):
42104258
has_url = True
42114259
except Exception:
42124260
pass
4213-
42144261
if self._reuse_session and sb_config.shared_driver and has_url:
42154262
if self.start_page and len(self.start_page) >= 4:
42164263
if page_utils.is_valid_url(self.start_page):
@@ -4236,7 +4283,11 @@ def setUp(self, masterqa_mode=False):
42364283
enable_sync=self.enable_sync,
42374284
user_data_dir=self.user_data_dir,
42384285
extension_zip=self.extension_zip,
4239-
extension_dir=self.extension_dir)
4286+
extension_dir=self.extension_dir,
4287+
is_mobile=self.mobile_emulator,
4288+
d_width=self.__device_width,
4289+
d_height=self.__device_height,
4290+
d_p_r=self.__device_pixel_ratio)
42404291
self._default_driver = self.driver
42414292
if self._reuse_session:
42424293
sb_config.shared_driver = self.driver

seleniumbase/plugins/pytest_plugin.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def pytest_addoption(parser):
2222
--port=PORT (The port that's used by the test server.)
2323
--proxy=SERVER:PORT (This is the proxy server:port combo used by tests.)
2424
--agent=STRING (This designates the web browser's User Agent to use.)
25+
--mobile (The option to use the mobile emulator while running tests.)
26+
--metrics=STRING ("CSSWidth,Height,PixelRatio" for mobile emulator tests.)
2527
--extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.)
2628
--extension-dir=DIR (Load a Chrome Extension directory, comma-separated.)
2729
--headless (The option to run tests headlessly. The default on Linux OS.)
@@ -185,6 +187,21 @@ def pytest_addoption(parser):
185187
help="""Designates the User-Agent for the browser to use.
186188
Format: A string.
187189
Default: None.""")
190+
parser.addoption('--mobile', '--mobile-emulator', '--mobile_emulator',
191+
action="store_true",
192+
dest='mobile_emulator',
193+
default=False,
194+
help="""If this option is enabled, the mobile emulator
195+
will be used while running tests.""")
196+
parser.addoption('--metrics', '--device-metrics', '--device_metrics',
197+
action='store',
198+
dest='device_metrics',
199+
default=None,
200+
help="""Designates the three device metrics of the mobile
201+
emulator: CSS Width, CSS Height, and Pixel-Ratio.
202+
Format: A comma-separated string with the 3 values.
203+
Example: "375,734,3"
204+
Default: None. (Will use default values if None)""")
188205
parser.addoption('--extension_zip', '--extension-zip',
189206
action='store',
190207
dest='extension_zip',
@@ -337,6 +354,8 @@ def pytest_configure(config):
337354
sb_config.environment = config.getoption('environment')
338355
sb_config.with_selenium = config.getoption('with_selenium')
339356
sb_config.user_agent = config.getoption('user_agent')
357+
sb_config.mobile_emulator = config.getoption('mobile_emulator')
358+
sb_config.device_metrics = config.getoption('device_metrics')
340359
sb_config.headless = config.getoption('headless')
341360
sb_config.headed = config.getoption('headed')
342361
sb_config.start_page = config.getoption('start_page')

seleniumbase/plugins/selenium_plugin.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class SeleniumBrowser(Plugin):
1717
--port=PORT (The port that's used by the test server.)
1818
--proxy=SERVER:PORT (This is the proxy server:port combo used by tests.)
1919
--agent=STRING (This designates the web browser's User Agent to use.)
20+
--mobile (The option to use the mobile emulator while running tests.)
21+
--metrics=STRING ("CSSWidth,Height,PixelRatio" for mobile emulator tests.)
2022
--extension-zip=ZIP (Load a Chrome Extension .zip file, comma-separated.)
2123
--extension-dir=DIR (Load a Chrome Extension directory, comma-separated.)
2224
--headless (The option to run tests headlessly. The default on Linux OS.)
@@ -104,6 +106,23 @@ def options(self, parser, env):
104106
help="""Designates the User-Agent for the browser to use.
105107
Format: A string.
106108
Default: None.""")
109+
parser.add_option(
110+
'--mobile', '--mobile-emulator', '--mobile_emulator',
111+
action="store_true",
112+
dest='mobile_emulator',
113+
default=False,
114+
help="""If this option is enabled, the mobile emulator
115+
will be used while running tests.""")
116+
parser.add_option(
117+
'--metrics', '--device-metrics', '--device_metrics',
118+
action='store',
119+
dest='device_metrics',
120+
default=None,
121+
help="""Designates the three device metrics of the mobile
122+
emulator: CSS Width, CSS Height, and Pixel-Ratio.
123+
Format: A comma-separated string with the 3 values.
124+
Example: "375,734,3"
125+
Default: None. (Will use default values if None)""")
107126
parser.add_option(
108127
'--extension_zip', '--extension-zip',
109128
action='store',
@@ -272,6 +291,8 @@ def beforeTest(self, test):
272291
test.test.extension_dir = self.options.extension_dir
273292
test.test.proxy_string = self.options.proxy_string
274293
test.test.user_agent = self.options.user_agent
294+
test.test.mobile_emulator = self.options.mobile_emulator
295+
test.test.device_metrics = self.options.device_metrics
275296
test.test.slow_mode = self.options.slow_mode
276297
test.test.demo_mode = self.options.demo_mode
277298
test.test.demo_sleep = self.options.demo_sleep

0 commit comments

Comments
 (0)