Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ install:
- "sudo rm -f /etc/boto.cfg"
before_script:
- "flake8 seleniumbase/*.py && flake8 seleniumbase/*/*.py && flake8 seleniumbase/*/*/*.py && flake8 seleniumbase/*/*/*/*.py"
- "export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start"
- "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"
- "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"
# - "export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start"
- "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"
- "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"
# - "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"
script:
- "pytest examples/my_first_test.py --browser=chrome -s"
- "nosetests examples/boilerplates/boilerplate_test.py"
- "pytest examples/my_first_test.py --browser=firefox -s"
- "pytest examples/my_first_test.py --browser=chrome -s --headless"
- "nosetests examples/boilerplates/boilerplate_test.py --headless"
- "pytest examples/my_first_test.py --browser=firefox -s --headless"
- "pytest examples/github_test.py --browser=chrome -s --headless"
notifications:
email: false
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ SeleniumBase automatically handles common WebDriver actions such as spinning up
**Simple Python syntax makes coding easy:**<br />
(<i>By default, [CSS Selectors](https://www.w3schools.com/cssref/css_selectors.asp) are used for finding page elements.</i>)

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

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

Expand Down
15 changes: 15 additions & 0 deletions examples/github_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from seleniumbase import BaseCase


class GitHubTests(BaseCase):

def test_github(self):
self.open("https://github.com/")
self.update_text("input.header-search-input", "SeleniumBase\n")
self.click('a[href="/seleniumbase/SeleniumBase"]')
self.assert_element("div.repository-content")
self.assert_text("SeleniumBase", "h1")
self.click('a[title="seleniumbase"]')
self.click('a[title="fixtures"]')
self.click('a[title="base_case.py"]')
self.assert_text("Code", "nav a.selected")
16 changes: 12 additions & 4 deletions help_docs/method_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ self.is_text_visible(text, selector, by=By.CSS_SELECTOR)

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

self.is_element_in_frame(selector, by=By.CSS_SELECTOR)
self.is_element_in_an_iframe(selector, by=By.CSS_SELECTOR)

self.enter_frame_of_element(selector, by=By.CSS_SELECTOR)
self.switch_to_frame_of_element(selector, by=By.CSS_SELECTOR)

self.execute_script(script)

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

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

self.switch_to_default_content()

self.open_new_window(switch_to=True)

self.switch_to_window(window, timeout=settings.SMALL_TIMEOUT)

self.switch_to_default_content()
self.switch_to_default_window()

self.save_screenshot(name, folder=None)

self.get_new_driver(browser=None, headless=None, servername=None, port=None,
proxy_string=None)
proxy=None, switch_to=True)

self.switch_to_driver(driver)

self.switch_to_default_driver()

########

Expand Down
153 changes: 96 additions & 57 deletions seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
"""
BaseCase gathers SeleniumBase libraries into a single file for easy calling.
The BaseCase class is the main gateway for using The SeleniumBase Framework.
It inherits Python's unittest.TestCase class, and runs with Pytest or Nose.
All tests using BaseCase automatically launch WebDriver browsers for tests.

Usage:

from seleniumbase import BaseCase
class MyTestClass(BaseCase):
test_anything(self):
def test_anything(self):
# Write your code here. Example:
self.open("https://github.com/")
self.update_text("input.header-search-input", "SeleniumBase\n")
self.click('a[href="/seleniumbase/SeleniumBase"]')
self.assert_element("div.repository-content")
....

The methods here expand and improve existing WebDriver commands.
Improvements include making WebDriver more robust and more reliable.
Page elements are given enough time to load before taking action on them.
SeleniumBase methods expand and improve on existing WebDriver commands.
Improvements include making WebDriver more robust, reliable, and flexible.
Page elements are given enough time to load before WebDriver acts on them.
Code becomes greatly simplified and easier to maintain.
"""

Expand Down Expand Up @@ -74,7 +77,8 @@ def __init__(self, *args, **kwargs):
self._page_check_count = 0
self._page_check_failures = []
self._html_report_extra = []
self._extra_drivers = []
self._default_driver = None
self._drivers_list = []

def open(self, url):
self.driver.get(url)
Expand Down Expand Up @@ -612,8 +616,8 @@ def find_visible_elements(self, selector, by=By.CSS_SELECTOR):
by = By.LINK_TEXT
return page_actions.find_visible_elements(self.driver, selector, by)

def is_element_in_frame(self, selector, by=By.CSS_SELECTOR):
""" Returns True if the selector's element is located in an iFrame.
def is_element_in_an_iframe(self, selector, by=By.CSS_SELECTOR):
""" Returns True if the selector's element is located in an iframe.
Otherwise returns False. """
selector, by = self._recalculate_selector(selector, by)
if self.is_element_present(selector, by=by):
Expand All @@ -636,10 +640,11 @@ def is_element_in_frame(self, selector, by=By.CSS_SELECTOR):
self.switch_to_default_content()
return False

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

def switch_to_frame(self, frame, timeout=settings.SMALL_TIMEOUT):
""" Sets driver control to the specified browser frame. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self._get_new_timeout(timeout)
page_actions.switch_to_frame(self.driver, frame, timeout)

def switch_to_default_content(self):
""" Brings driver control outside the current iframe.
(If driver control is inside an iframe, the driver control
will be set to one level above the current frame. If the driver
control is not currenly in an iframe, nothing will happen.) """
self.driver.switch_to.default_content()

def open_new_window(self, switch_to=True):
""" Opens a new browser tab/window and switches to it by default. """
self.driver.execute_script("window.open('');")
time.sleep(0.01)
if switch_to:
self.switch_to_window(len(self.driver.window_handles) - 1)

def switch_to_window(self, window, timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self._get_new_timeout(timeout)
page_actions.switch_to_window(self.driver, window, timeout)

def switch_to_default_content(self):
self.driver.switch_to.default_content()
def switch_to_default_window(self):
self.switch_to_window(0)

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

def get_new_driver(self, browser=None, headless=None,
servername=None, port=None,
proxy_string=None):
servername=None, port=None, proxy=None, switch_to=True):
""" This method spins up an extra browser for tests that require
more than one. The first browser is already provided by tests
that import base_case.BaseCase from seleniumbase. """
that import base_case.BaseCase from seleniumbase. If parameters
aren't specified, the method uses the same as the default driver.
@Params
browser - the browser to use. (Ex: "chrome", "firefox")
headless - the option to run webdriver in headless mode
servername - if using a Selenium Grid, set the host address here
port - if using a Selenium Grid, set the host port here
proxy - if using a proxy server, specify the "host:port" combo here
switch_to - the option to switch to the new driver (default = True)
"""
if browser is None:
browser = self.browser
browser_name = browser
if headless is None:
headless = self.headless
if servername is None:
servername = self.servername
if port is None:
port = self.port
if proxy_string is None:
proxy_string = self.proxy_string
use_grid = False
if servername != "localhost":
# Use Selenium Grid (Use --server=127.0.0.1 for localhost Grid)
# Use Selenium Grid (Use "127.0.0.1" for localhost Grid)
use_grid = True
proxy_string = proxy
if proxy_string is None:
proxy_string = self.proxy_string
valid_browsers = constants.ValidBrowsers.valid_browsers
if browser not in valid_browsers:
if browser_name not in valid_browsers:
raise Exception("Browser: {%s} is not a valid browser option. "
"Valid options = {%s}" % (browser, valid_browsers))
new_driver = browser_launcher.get_driver(browser,
headless,
use_grid,
servername,
port,
proxy_string)
self._extra_drivers.append(new_driver)
new_driver = browser_launcher.get_driver(browser_name=browser_name,
headless=headless,
use_grid=use_grid,
servername=servername,
port=port,
proxy_string=proxy_string)
self._drivers_list.append(new_driver)
if switch_to:
self.driver = new_driver
return new_driver

def switch_to_driver(self, driver):
""" Sets self.driver to the specified driver.
You may need this if using self.get_new_driver() in your code. """
self.driver = driver

def switch_to_default_driver(self):
""" Sets self.driver to the default/original driver. """
self.driver = self._default_driver

############

def _get_new_timeout(self, timeout):
Expand Down Expand Up @@ -1846,6 +1887,16 @@ def setUp(self):
self.servername,
self.port,
self.proxy_string)
self._default_driver = self.driver
self._drivers_list.append(self.driver)
if self.headless:
# Make sure the invisible browser window is big enough
try:
self.set_window_size(1920, 1200)
except Exception:
# This shouldn't fail, but in case it does, get safely through
# setUp() so that WebDrivers can get closed during tearDown().
pass

def __insert_test_result(self, state, err):
data_payload = TestcaseDataPayload()
Expand Down Expand Up @@ -1880,6 +1931,19 @@ def _add_pytest_html_extra(self):
except Exception:
pass

def _quit_all_drivers(self):
# Close all open browser windows
self._drivers_list.reverse() # Last In, First Out
for driver in self._drivers_list:
try:
driver.quit()
except AttributeError:
pass
except Exception:
pass
self.driver = None
self._drivers_list = []

def tearDown(self):
"""
Be careful if a subclass of BaseCase overrides setUp()
Expand Down Expand Up @@ -1939,20 +2003,8 @@ def tearDown(self):
if self.with_page_source:
log_helper.log_page_source(
test_logpath, self.driver)
try:
# Finally close the browser
if self._extra_drivers:
for extra_driver in self._extra_drivers:
try:
extra_driver.quit()
except Exception:
pass # Extra driver was already quit
self.driver.quit()
except AttributeError:
pass
except Exception:
pass
self.driver = None
# (Pytest) Finally close all open browser windows
self._quit_all_drivers()
if self.headless:
if self.headless_active:
self.display.stop()
Expand Down Expand Up @@ -1990,18 +2042,5 @@ def tearDown(self):
data_payload.logURL = index_file
self.testcase_manager.update_testcase_log_url(data_payload)
else:
# Using Nosetests
try:
# Finally close the browser
if self._extra_drivers:
for extra_driver in self._extra_drivers:
try:
extra_driver.quit()
except Exception:
pass # Extra driver was already quit
self.driver.quit()
except AttributeError:
pass
except Exception:
pass
self.driver = None
# (Nosetests) Finally close all open browser windows
self._quit_all_drivers()
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='seleniumbase',
version='1.8.0',
version='1.8.1',
description='Web Automation & Testing Framework - http://seleniumbase.com',
long_description='Web Automation and Testing Framework - seleniumbase.com',
platforms='Mac * Windows * Linux * Docker',
Expand Down