Skip to content

Commit 5c293a9

Browse files
authored
Merge pull request #153 from seleniumbase/more-methods-for-handling-drivers
More methods for handling webdriver actions
2 parents 9303f3f + e00e7db commit 5c293a9

File tree

6 files changed

+132
-69
lines changed

6 files changed

+132
-69
lines changed

.travis.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ install:
1212
- "sudo rm -f /etc/boto.cfg"
1313
before_script:
1414
- "flake8 seleniumbase/*.py && flake8 seleniumbase/*/*.py && flake8 seleniumbase/*/*/*.py && flake8 seleniumbase/*/*/*/*.py"
15-
- "export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start"
16-
- "wget https://chromedriver.storage.googleapis.com/2.35/chromedriver_linux64.zip && unzip chromedriver_linux64.zip && sudo cp chromedriver /usr/local/bin/ && sudo chmod +x /usr/local/bin/chromedriver"
17-
- "wget https://github.com/mozilla/geckodriver/releases/download/v0.19.0/geckodriver-v0.19.0-linux64.tar.gz -O /tmp/geckodriver.tar.gz && tar -C /opt -xzf /tmp/geckodriver.tar.gz && sudo chmod 755 /opt/geckodriver && sudo ln -fs /opt/geckodriver /usr/bin/geckodriver && sudo ln -fs /opt/geckodriver /usr/local/bin/geckodriver"
15+
# - "export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start"
16+
- "wget https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip && unzip chromedriver_linux64.zip && sudo cp chromedriver /usr/local/bin/ && sudo chmod +x /usr/local/bin/chromedriver"
17+
- "wget https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz -O /tmp/geckodriver.tar.gz && tar -C /opt -xzf /tmp/geckodriver.tar.gz && sudo chmod 755 /opt/geckodriver && sudo ln -fs /opt/geckodriver /usr/bin/geckodriver && sudo ln -fs /opt/geckodriver /usr/local/bin/geckodriver"
1818
# - "wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 && tar -xvf ./phantomjs-2.1.1-linux-x86_64.tar.bz2 && export PATH=$PWD/phantomjs-2.1.1-linux-x86_64/bin:$PATH"
1919
script:
20-
- "pytest examples/my_first_test.py --browser=chrome -s"
21-
- "nosetests examples/boilerplates/boilerplate_test.py"
22-
- "pytest examples/my_first_test.py --browser=firefox -s"
20+
- "pytest examples/my_first_test.py --browser=chrome -s --headless"
21+
- "nosetests examples/boilerplates/boilerplate_test.py --headless"
22+
- "pytest examples/my_first_test.py --browser=firefox -s --headless"
23+
- "pytest examples/github_test.py --browser=chrome -s --headless"
2324
notifications:
2425
email: false

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ SeleniumBase automatically handles common WebDriver actions such as spinning up
1717
**Simple Python syntax makes coding easy:**<br />
1818
(<i>By default, [CSS Selectors](https://www.w3schools.com/cssref/css_selectors.asp) are used for finding page elements.</i>)
1919

20-
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_py_2.png" title="SeleniumBase Python Code" height="280">
20+
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_image.png" title="SeleniumBase Python Code" height="280">
2121

2222
**Run tests with Pytest or Nose in any browser:**
2323

examples/github_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from seleniumbase import BaseCase
2+
3+
4+
class GitHubTests(BaseCase):
5+
6+
def test_github(self):
7+
self.open("https://github.com/")
8+
self.update_text("input.header-search-input", "SeleniumBase\n")
9+
self.click('a[href="/seleniumbase/SeleniumBase"]')
10+
self.assert_element("div.repository-content")
11+
self.assert_text("SeleniumBase", "h1")
12+
self.click('a[title="seleniumbase"]')
13+
self.click('a[title="fixtures"]')
14+
self.click('a[title="base_case.py"]')
15+
self.assert_text("Code", "nav a.selected")

help_docs/method_summary.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ self.is_text_visible(text, selector, by=By.CSS_SELECTOR)
7373

7474
self.find_visible_elements(selector, by=By.CSS_SELECTOR)
7575

76-
self.is_element_in_frame(selector, by=By.CSS_SELECTOR)
76+
self.is_element_in_an_iframe(selector, by=By.CSS_SELECTOR)
7777

78-
self.enter_frame_of_element(selector, by=By.CSS_SELECTOR)
78+
self.switch_to_frame_of_element(selector, by=By.CSS_SELECTOR)
7979

8080
self.execute_script(script)
8181

@@ -248,14 +248,22 @@ self.wait_for_and_switch_to_alert(timeout=settings.LARGE_TIMEOUT)
248248

249249
self.switch_to_frame(frame, timeout=settings.SMALL_TIMEOUT)
250250

251+
self.switch_to_default_content()
252+
253+
self.open_new_window(switch_to=True)
254+
251255
self.switch_to_window(window, timeout=settings.SMALL_TIMEOUT)
252256

253-
self.switch_to_default_content()
257+
self.switch_to_default_window()
254258

255259
self.save_screenshot(name, folder=None)
256260

257261
self.get_new_driver(browser=None, headless=None, servername=None, port=None,
258-
proxy_string=None)
262+
proxy=None, switch_to=True)
263+
264+
self.switch_to_driver(driver)
265+
266+
self.switch_to_default_driver()
259267

260268
########
261269

seleniumbase/fixtures/base_case.py

Lines changed: 96 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
"""
2-
BaseCase gathers SeleniumBase libraries into a single file for easy calling.
2+
The BaseCase class is the main gateway for using The SeleniumBase Framework.
3+
It inherits Python's unittest.TestCase class, and runs with Pytest or Nose.
4+
All tests using BaseCase automatically launch WebDriver browsers for tests.
5+
36
Usage:
47
58
from seleniumbase import BaseCase
69
class MyTestClass(BaseCase):
7-
test_anything(self):
10+
def test_anything(self):
811
# Write your code here. Example:
912
self.open("https://github.com/")
1013
self.update_text("input.header-search-input", "SeleniumBase\n")
1114
self.click('a[href="/seleniumbase/SeleniumBase"]')
1215
self.assert_element("div.repository-content")
1316
....
1417
15-
The methods here expand and improve existing WebDriver commands.
16-
Improvements include making WebDriver more robust and more reliable.
17-
Page elements are given enough time to load before taking action on them.
18+
SeleniumBase methods expand and improve on existing WebDriver commands.
19+
Improvements include making WebDriver more robust, reliable, and flexible.
20+
Page elements are given enough time to load before WebDriver acts on them.
1821
Code becomes greatly simplified and easier to maintain.
1922
"""
2023

@@ -74,7 +77,8 @@ def __init__(self, *args, **kwargs):
7477
self._page_check_count = 0
7578
self._page_check_failures = []
7679
self._html_report_extra = []
77-
self._extra_drivers = []
80+
self._default_driver = None
81+
self._drivers_list = []
7882

7983
def open(self, url):
8084
self.driver.get(url)
@@ -612,8 +616,8 @@ def find_visible_elements(self, selector, by=By.CSS_SELECTOR):
612616
by = By.LINK_TEXT
613617
return page_actions.find_visible_elements(self.driver, selector, by)
614618

615-
def is_element_in_frame(self, selector, by=By.CSS_SELECTOR):
616-
""" Returns True if the selector's element is located in an iFrame.
619+
def is_element_in_an_iframe(self, selector, by=By.CSS_SELECTOR):
620+
""" Returns True if the selector's element is located in an iframe.
617621
Otherwise returns False. """
618622
selector, by = self._recalculate_selector(selector, by)
619623
if self.is_element_present(selector, by=by):
@@ -636,10 +640,11 @@ def is_element_in_frame(self, selector, by=By.CSS_SELECTOR):
636640
self.switch_to_default_content()
637641
return False
638642

639-
def enter_frame_of_element(self, selector, by=By.CSS_SELECTOR):
640-
""" Returns the frame name of the selector's element if in an iFrame.
641-
Also enters the iFrame if the element was inside an iFrame.
642-
If the element is not in an iFrame, returns None. """
643+
def switch_to_frame_of_element(self, selector, by=By.CSS_SELECTOR):
644+
""" Set driver control to the iframe of the element (assuming the
645+
element is in a single-nested iframe) and returns the iframe name.
646+
If element is not in an iframe, returns None, and nothing happens.
647+
May not work if multiple iframes are nested within each other. """
643648
selector, by = self._recalculate_selector(selector, by)
644649
if self.is_element_present(selector, by=by):
645650
return None
@@ -1420,54 +1425,90 @@ def wait_for_and_switch_to_alert(self, timeout=settings.LARGE_TIMEOUT):
14201425
return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)
14211426

14221427
def switch_to_frame(self, frame, timeout=settings.SMALL_TIMEOUT):
1428+
""" Sets driver control to the specified browser frame. """
14231429
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
14241430
timeout = self._get_new_timeout(timeout)
14251431
page_actions.switch_to_frame(self.driver, frame, timeout)
14261432

1433+
def switch_to_default_content(self):
1434+
""" Brings driver control outside the current iframe.
1435+
(If driver control is inside an iframe, the driver control
1436+
will be set to one level above the current frame. If the driver
1437+
control is not currenly in an iframe, nothing will happen.) """
1438+
self.driver.switch_to.default_content()
1439+
1440+
def open_new_window(self, switch_to=True):
1441+
""" Opens a new browser tab/window and switches to it by default. """
1442+
self.driver.execute_script("window.open('');")
1443+
time.sleep(0.01)
1444+
if switch_to:
1445+
self.switch_to_window(len(self.driver.window_handles) - 1)
1446+
14271447
def switch_to_window(self, window, timeout=settings.SMALL_TIMEOUT):
14281448
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
14291449
timeout = self._get_new_timeout(timeout)
14301450
page_actions.switch_to_window(self.driver, window, timeout)
14311451

1432-
def switch_to_default_content(self):
1433-
self.driver.switch_to.default_content()
1452+
def switch_to_default_window(self):
1453+
self.switch_to_window(0)
14341454

14351455
def save_screenshot(self, name, folder=None):
14361456
return page_actions.save_screenshot(self.driver, name, folder)
14371457

14381458
def get_new_driver(self, browser=None, headless=None,
1439-
servername=None, port=None,
1440-
proxy_string=None):
1459+
servername=None, port=None, proxy=None, switch_to=True):
14411460
""" This method spins up an extra browser for tests that require
14421461
more than one. The first browser is already provided by tests
1443-
that import base_case.BaseCase from seleniumbase. """
1462+
that import base_case.BaseCase from seleniumbase. If parameters
1463+
aren't specified, the method uses the same as the default driver.
1464+
@Params
1465+
browser - the browser to use. (Ex: "chrome", "firefox")
1466+
headless - the option to run webdriver in headless mode
1467+
servername - if using a Selenium Grid, set the host address here
1468+
port - if using a Selenium Grid, set the host port here
1469+
proxy - if using a proxy server, specify the "host:port" combo here
1470+
switch_to - the option to switch to the new driver (default = True)
1471+
"""
14441472
if browser is None:
14451473
browser = self.browser
1474+
browser_name = browser
14461475
if headless is None:
14471476
headless = self.headless
14481477
if servername is None:
14491478
servername = self.servername
14501479
if port is None:
14511480
port = self.port
1452-
if proxy_string is None:
1453-
proxy_string = self.proxy_string
14541481
use_grid = False
14551482
if servername != "localhost":
1456-
# Use Selenium Grid (Use --server=127.0.0.1 for localhost Grid)
1483+
# Use Selenium Grid (Use "127.0.0.1" for localhost Grid)
14571484
use_grid = True
1485+
proxy_string = proxy
1486+
if proxy_string is None:
1487+
proxy_string = self.proxy_string
14581488
valid_browsers = constants.ValidBrowsers.valid_browsers
1459-
if browser not in valid_browsers:
1489+
if browser_name not in valid_browsers:
14601490
raise Exception("Browser: {%s} is not a valid browser option. "
14611491
"Valid options = {%s}" % (browser, valid_browsers))
1462-
new_driver = browser_launcher.get_driver(browser,
1463-
headless,
1464-
use_grid,
1465-
servername,
1466-
port,
1467-
proxy_string)
1468-
self._extra_drivers.append(new_driver)
1492+
new_driver = browser_launcher.get_driver(browser_name=browser_name,
1493+
headless=headless,
1494+
use_grid=use_grid,
1495+
servername=servername,
1496+
port=port,
1497+
proxy_string=proxy_string)
1498+
self._drivers_list.append(new_driver)
1499+
if switch_to:
1500+
self.driver = new_driver
14691501
return new_driver
14701502

1503+
def switch_to_driver(self, driver):
1504+
""" Sets self.driver to the specified driver.
1505+
You may need this if using self.get_new_driver() in your code. """
1506+
self.driver = driver
1507+
1508+
def switch_to_default_driver(self):
1509+
""" Sets self.driver to the default/original driver. """
1510+
self.driver = self._default_driver
1511+
14711512
############
14721513

14731514
def _get_new_timeout(self, timeout):
@@ -1846,6 +1887,16 @@ def setUp(self):
18461887
self.servername,
18471888
self.port,
18481889
self.proxy_string)
1890+
self._default_driver = self.driver
1891+
self._drivers_list.append(self.driver)
1892+
if self.headless:
1893+
# Make sure the invisible browser window is big enough
1894+
try:
1895+
self.set_window_size(1920, 1200)
1896+
except Exception:
1897+
# This shouldn't fail, but in case it does, get safely through
1898+
# setUp() so that WebDrivers can get closed during tearDown().
1899+
pass
18491900

18501901
def __insert_test_result(self, state, err):
18511902
data_payload = TestcaseDataPayload()
@@ -1880,6 +1931,19 @@ def _add_pytest_html_extra(self):
18801931
except Exception:
18811932
pass
18821933

1934+
def _quit_all_drivers(self):
1935+
# Close all open browser windows
1936+
self._drivers_list.reverse() # Last In, First Out
1937+
for driver in self._drivers_list:
1938+
try:
1939+
driver.quit()
1940+
except AttributeError:
1941+
pass
1942+
except Exception:
1943+
pass
1944+
self.driver = None
1945+
self._drivers_list = []
1946+
18831947
def tearDown(self):
18841948
"""
18851949
Be careful if a subclass of BaseCase overrides setUp()
@@ -1939,20 +2003,8 @@ def tearDown(self):
19392003
if self.with_page_source:
19402004
log_helper.log_page_source(
19412005
test_logpath, self.driver)
1942-
try:
1943-
# Finally close the browser
1944-
if self._extra_drivers:
1945-
for extra_driver in self._extra_drivers:
1946-
try:
1947-
extra_driver.quit()
1948-
except Exception:
1949-
pass # Extra driver was already quit
1950-
self.driver.quit()
1951-
except AttributeError:
1952-
pass
1953-
except Exception:
1954-
pass
1955-
self.driver = None
2006+
# (Pytest) Finally close all open browser windows
2007+
self._quit_all_drivers()
19562008
if self.headless:
19572009
if self.headless_active:
19582010
self.display.stop()
@@ -1990,18 +2042,5 @@ def tearDown(self):
19902042
data_payload.logURL = index_file
19912043
self.testcase_manager.update_testcase_log_url(data_payload)
19922044
else:
1993-
# Using Nosetests
1994-
try:
1995-
# Finally close the browser
1996-
if self._extra_drivers:
1997-
for extra_driver in self._extra_drivers:
1998-
try:
1999-
extra_driver.quit()
2000-
except Exception:
2001-
pass # Extra driver was already quit
2002-
self.driver.quit()
2003-
except AttributeError:
2004-
pass
2005-
except Exception:
2006-
pass
2007-
self.driver = None
2045+
# (Nosetests) Finally close all open browser windows
2046+
self._quit_all_drivers()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup(
99
name='seleniumbase',
10-
version='1.8.0',
10+
version='1.8.1',
1111
description='Web Automation & Testing Framework - http://seleniumbase.com',
1212
long_description='Web Automation and Testing Framework - seleniumbase.com',
1313
platforms='Mac * Windows * Linux * Docker',

0 commit comments

Comments
 (0)