SeleniumBase/seleniumbase/fixtures/base_case.py

3527 lines
157 KiB
Python
Executable File

"""
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):
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")
....
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.
"""
import logging
import math
import os
import pytest
import re
import requests
import sys
import time
import unittest
import uuid
from bs4 import BeautifulSoup
from seleniumbase.common import decorators
from seleniumbase.config import settings
from seleniumbase.core.application_manager import ApplicationManager
from seleniumbase.core.testcase_manager import ExecutionQueryPayload
from seleniumbase.core.testcase_manager import TestcaseDataPayload
from seleniumbase.core.testcase_manager import TestcaseManager
from seleniumbase.core import download_helper
from seleniumbase.core import log_helper
from seleniumbase.core import style_sheet
from seleniumbase.fixtures import constants
from seleniumbase.fixtures import page_actions
from seleniumbase.fixtures import page_utils
from seleniumbase.fixtures import xpath_to_css
from selenium.common.exceptions import (StaleElementReferenceException,
MoveTargetOutOfBoundsException,
WebDriverException)
from selenium.common import exceptions as selenium_exceptions
try:
# Selenium 3 (ElementNotInteractableException does not exist in selenium 2)
ENI_Exception = selenium_exceptions.ElementNotInteractableException
except Exception:
# Selenium 2 (Keep compatibility with seleneium 2.53.6 if still being used)
ENI_Exception = selenium_exceptions.ElementNotSelectableException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.webdriver import ActionChains
class BaseCase(unittest.TestCase):
'''
A base test case that wraps methods for enhanced usage.
You can also add your own methods here.
'''
def __init__(self, *args, **kwargs):
super(BaseCase, self).__init__(*args, **kwargs)
self.driver = None
self.environment = None
self.env = None # Add a shortened version of self.environment
self.__last_url_of_delayed_assert = "data:,"
self.__last_page_load_url = "data:,"
self.__page_check_count = 0
self.__page_check_failures = []
# Requires self._* instead of self.__* for external class use
self._html_report_extra = [] # (Used by pytest_plugin.py)
self._default_driver = None
self._drivers_list = []
self._tour_steps = {}
def open(self, url):
self.__last_page_load_url = None
self.driver.get(url)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
self.__demo_mode_pause_if_active()
def open_url(self, url):
""" In case people are mixing up self.open() with open(),
use this alternative. """
self.open(url)
def click(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
if not self.is_link_text_visible(selector):
# Handle a special case of links hidden in dropdowns
self.click_link_text(selector, timeout=timeout)
return
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout=timeout)
self.__demo_mode_highlight_if_active(selector, by)
if not self.demo_mode:
self.__scroll_to_element(element)
pre_action_url = self.driver.current_url
try:
if self.browser == 'ie' and by == By.LINK_TEXT:
# An issue with clicking Link Text on IE means using jquery
self.__jquery_click(selector, by=by)
else:
# Normal click
element.click()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout=timeout)
element.click()
except (WebDriverException, MoveTargetOutOfBoundsException):
self.wait_for_ready_state_complete()
try:
self.__js_click(selector, by=by)
except Exception:
try:
self.__jquery_click(selector, by=by)
except Exception:
# One more attempt to click on the element
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout=timeout)
element.click()
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def double_click(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout=timeout)
self.__demo_mode_highlight_if_active(selector, by)
if not self.demo_mode:
self.__scroll_to_element(element)
pre_action_url = self.driver.current_url
try:
actions = ActionChains(self.driver)
actions.move_to_element(element)
actions.double_click(element)
actions.perform()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout=timeout)
actions = ActionChains(self.driver)
actions.move_to_element(element)
actions.double_click(element)
actions.perform()
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def click_chain(self, selectors_list, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT, spacing=0):
""" This method clicks on a list of elements in succession.
'spacing' is the amount of time to wait between clicks. (sec) """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
for selector in selectors_list:
self.click(selector, by=by, timeout=timeout)
if spacing > 0:
time.sleep(spacing)
def is_link_text_present(self, link_text):
""" Returns True if the link text appears in the HTML of the page.
The element doesn't need to be visible,
such as elements hidden inside a dropdown selection. """
self.wait_for_ready_state_complete()
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
html_links = soup.find_all('a')
for html_link in html_links:
if html_link.text.strip() == link_text.strip():
return True
return False
def get_link_attribute(self, link_text, attribute, hard_fail=True):
""" Finds a link by link text and then returns the attribute's value.
If the link text or attribute cannot be found, an exception will
get raised if hard_fail is True (otherwise None is returned). """
self.wait_for_ready_state_complete()
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
html_links = soup.find_all('a')
for html_link in html_links:
if html_link.text.strip() == link_text.strip():
if html_link.has_attr(attribute):
attribute_value = html_link.get(attribute)
return attribute_value
if hard_fail:
raise Exception(
'Unable to find attribute {%s} from link text {%s}!'
% (attribute, link_text))
else:
return None
if hard_fail:
raise Exception("Link text {%s} was not found!" % link_text)
else:
return None
def wait_for_link_text_present(self, link_text,
timeout=settings.SMALL_TIMEOUT):
start_ms = time.time() * 1000.0
stop_ms = start_ms + (timeout * 1000.0)
for x in range(int(timeout * 5)):
try:
if not self.is_link_text_present(link_text):
raise Exception(
"Link text {%s} was not found!" % link_text)
return
except Exception:
now_ms = time.time() * 1000.0
if now_ms >= stop_ms:
break
time.sleep(0.2)
raise Exception(
"Link text {%s} was not present after %s seconds!" % (
link_text, timeout))
def click_link_text(self, link_text, timeout=settings.SMALL_TIMEOUT):
""" This method clicks link text on a page """
# If using phantomjs, might need to extract and open the link directly
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if self.browser == 'phantomjs':
if self.is_link_text_visible(link_text):
element = self.wait_for_link_text_visible(link_text)
element.click()
return
self.open(self.__get_href_from_link_text(link_text))
return
if not self.is_link_text_present(link_text):
self.wait_for_link_text_present(link_text)
pre_action_url = self.get_current_url()
try:
element = self.wait_for_link_text_visible(
link_text, timeout=0.2)
self.__demo_mode_highlight_if_active(link_text, by=By.LINK_TEXT)
try:
element.click()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = self.wait_for_link_text_visible(
link_text, timeout=timeout)
element.click()
except Exception:
found_css = False
text_id = self.get_link_attribute(link_text, "id", False)
if text_id:
link_css = '[id="%s"]' % link_text
found_css = True
if not found_css:
href = self.__get_href_from_link_text(link_text, False)
if href:
if href.startswith('/') or page_utils.is_valid_url(href):
link_css = '[href="%s"]' % href
found_css = True
if not found_css:
ngclick = self.get_link_attribute(link_text, "ng-click", False)
if ngclick:
link_css = '[ng-click="%s"]' % ngclick
found_css = True
if not found_css:
onclick = self.get_link_attribute(link_text, "onclick", False)
if onclick:
link_css = '[onclick="%s"]' % onclick
found_css = True
success = False
if found_css:
if self.is_element_visible(link_css):
self.click(link_css)
success = True
else:
# The link text might be hidden under a dropdown menu
success = self.__click_dropdown_link_text(
link_text, link_css)
if not success:
element = self.wait_for_link_text_visible(
link_text, timeout=settings.MINI_TIMEOUT)
element.click()
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def click_link(self, link_text, timeout=settings.SMALL_TIMEOUT):
""" Same as self.click_link_text() """
self.click_link_text(link_text, timeout=timeout)
def click_partial_link_text(self, partial_link_text,
timeout=settings.SMALL_TIMEOUT):
""" This method clicks the partial link text on a page. """
# If using phantomjs, might need to extract and open the link directly
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if self.browser == 'phantomjs':
if self.is_partial_link_text_visible(partial_link_text):
element = self.wait_for_partial_link_text(partial_link_text)
element.click()
return
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
html_links = soup.fetch('a')
for html_link in html_links:
if partial_link_text in html_link.text:
for html_attribute in html_link.attrs:
if html_attribute[0] == 'href':
href = html_attribute[1]
if href.startswith('//'):
link = "http:" + href
elif href.startswith('/'):
url = self.driver.current_url
domain_url = self.get_domain_url(url)
link = domain_url + href
else:
link = href
self.open(link)
return
raise Exception(
'Could not parse link from partial link_text '
'{%s}' % partial_link_text)
raise Exception(
"Partial link text {%s} was not found!" % partial_link_text)
# Not using phantomjs
element = self.wait_for_partial_link_text(
partial_link_text, timeout=timeout)
self.__demo_mode_highlight_if_active(
partial_link_text, by=By.PARTIAL_LINK_TEXT)
pre_action_url = self.driver.current_url
try:
element.click()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = self.wait_for_partial_link_text(
partial_link_text, timeout=timeout)
element.click()
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def get_text(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
self.wait_for_ready_state_complete()
time.sleep(0.01)
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout)
try:
element_text = element.text
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.06)
element = page_actions.wait_for_element_visible(
self.driver, selector, by, timeout)
element_text = element.text
return element_text
def get_attribute(self, selector, attribute, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
self.wait_for_ready_state_complete()
time.sleep(0.01)
element = page_actions.wait_for_element_present(
self.driver, selector, by, timeout)
try:
attribute_value = element.get_attribute(attribute)
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.06)
element = page_actions.wait_for_element_present(
self.driver, selector, by, timeout)
attribute_value = element.get_attribute(attribute)
if attribute_value is not None:
return attribute_value
else:
raise Exception("Element {%s} has no attribute {%s}!" % (
selector, attribute))
def refresh_page(self):
self.__last_page_load_url = None
self.driver.refresh()
self.wait_for_ready_state_complete()
def refresh(self):
""" The shorter version of self.refresh_page() """
self.refresh_page()
def get_current_url(self):
return self.driver.current_url
def get_page_source(self):
return self.driver.page_source
def get_page_title(self):
return self.driver.title
def get_title(self):
""" The shorter version of self.get_page_title() """
return self.driver.title
def go_back(self):
self.__last_page_load_url = None
self.driver.back()
self.wait_for_ready_state_complete()
def go_forward(self):
self.__last_page_load_url = None
self.driver.forward()
self.wait_for_ready_state_complete()
def get_image_url(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Extracts the URL from an image element on the page. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.get_attribute(selector,
attribute='src', by=by, timeout=timeout)
def add_text(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" The more-reliable version of driver.send_keys()
Similar to update_text(), but won't clear the text field first. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
self.__demo_mode_highlight_if_active(selector, by)
if not self.demo_mode:
self.__scroll_to_element(element)
pre_action_url = self.driver.current_url
try:
if not new_value.endswith('\n'):
element.send_keys(new_value)
else:
new_value = new_value[:-1]
element.send_keys(new_value)
element.send_keys(Keys.RETURN)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.06)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
if not new_value.endswith('\n'):
element.send_keys(new_value)
else:
new_value = new_value[:-1]
element.send_keys(new_value)
element.send_keys(Keys.RETURN)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
except Exception:
exc_message = self.__get_improved_exception_message()
raise Exception(exc_message)
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def send_keys(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Same as add_text() -> more reliable, but less name confusion. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
self.add_text(selector, new_value, by=by, timeout=timeout)
def update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT, retry=False):
""" This method updates an element's text value with a new value.
@Params
selector - the selector with the value to update
new_value - the new value for setting the text field
by - the type of selector to search by (Default: CSS)
timeout - how long to wait for the selector to be visible
retry - if True, use JS if the selenium text update fails
"""
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
self.__demo_mode_highlight_if_active(selector, by)
if not self.demo_mode:
self.__scroll_to_element(element)
try:
element.clear()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.06)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
element.clear()
except Exception:
pass # Clearing the text field first isn't critical
self.__demo_mode_pause_if_active(tiny=True)
pre_action_url = self.driver.current_url
try:
if not new_value.endswith('\n'):
element.send_keys(new_value)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
else:
new_value = new_value[:-1]
element.send_keys(new_value)
element.send_keys(Keys.RETURN)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.06)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
element.clear()
if not new_value.endswith('\n'):
element.send_keys(new_value)
else:
new_value = new_value[:-1]
element.send_keys(new_value)
element.send_keys(Keys.RETURN)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
except Exception:
exc_message = self.__get_improved_exception_message()
raise Exception(exc_message)
if (retry and element.get_attribute('value') != new_value and (
not new_value.endswith('\n'))):
logging.debug('update_text() is falling back to JavaScript!')
self.set_value(selector, new_value, by=by)
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def update_text(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT, retry=False):
""" The shorter version of update_text_value(), which
clears existing text and adds new text into the text field.
We want to keep the old version for backward compatibility. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
self.update_text_value(selector, new_value, by=by,
timeout=timeout, retry=retry)
def is_element_present(self, selector, by=By.CSS_SELECTOR):
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.is_element_present(self.driver, selector, by)
def is_element_visible(self, selector, by=By.CSS_SELECTOR):
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.is_element_visible(self.driver, selector, by)
def is_link_text_visible(self, link_text):
self.wait_for_ready_state_complete()
time.sleep(0.01)
return page_actions.is_element_visible(self.driver, link_text,
by=By.LINK_TEXT)
def is_partial_link_text_visible(self, partial_link_text):
self.wait_for_ready_state_complete()
time.sleep(0.01)
return page_actions.is_element_visible(self.driver, partial_link_text,
by=By.PARTIAL_LINK_TEXT)
def is_text_visible(self, text, selector, by=By.CSS_SELECTOR):
self.wait_for_ready_state_complete()
time.sleep(0.01)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.is_text_visible(self.driver, text, selector, by)
def find_visible_elements(self, selector, by=By.CSS_SELECTOR):
""" Returns a list of matching WebElements that are visible. """
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.find_visible_elements(self.driver, selector, by)
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):
return False
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
iframe_list = soup.select('iframe')
for iframe in iframe_list:
iframe_identifier = None
if iframe.has_attr('name') and len(iframe['name']) > 0:
iframe_identifier = iframe['name']
elif iframe.has_attr('id') and len(iframe['id']) > 0:
iframe_identifier = iframe['id']
else:
continue
self.switch_to_frame(iframe_identifier)
if self.is_element_present(selector, by=by):
self.switch_to_default_content()
return True
self.switch_to_default_content()
return False
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
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
iframe_list = soup.select('iframe')
for iframe in iframe_list:
iframe_identifier = None
if iframe.has_attr('name') and len(iframe['name']) > 0:
iframe_identifier = iframe['name']
elif iframe.has_attr('id') and len(iframe['id']) > 0:
iframe_identifier = iframe['id']
else:
continue
self.switch_to_frame(iframe_identifier)
if self.is_element_present(selector, by=by):
return iframe_identifier
self.switch_to_default_content()
return None
def execute_script(self, script):
return self.driver.execute_script(script)
def execute_async_script(self, script, timeout=settings.EXTREME_TIMEOUT):
if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.driver.set_script_timeout(timeout)
return self.driver.execute_async_script(script)
def set_window_size(self, width, height):
self.driver.set_window_size(width, height)
self.__demo_mode_pause_if_active()
def maximize_window(self):
self.driver.maximize_window()
self.__demo_mode_pause_if_active()
def add_css_link(self, css_link):
script_to_add_css = (
"""function injectCSS() {
var head = document.getElementsByTagName("head")[0];
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "%s";
link.crossorigin = "anonymous";
head.appendChild(link);
}
injectCSS();""")
self.execute_script(script_to_add_css % css_link)
def add_js_link(self, js_link):
script_to_add_js = (
"""function injectJS() {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = "%s";
script.defer;
script.crossorigin = "anonymous";
script.onload = function() { null };
head.appendChild(script);
}
injectJS();""")
self.execute_script(script_to_add_js % js_link)
def add_css_style(self, css_style):
add_css_style_script = (
'''var h = document.getElementsByTagName('head').item(0);'''
'''var s = document.createElement("style");'''
'''s.type = "text/css";'''
'''s.appendChild(document.createTextNode("%s"));'''
'''h.appendChild(s);''')
css_style = re.escape(css_style)
css_style = self.__escape_quotes_if_needed(css_style)
self.execute_script(add_css_style_script % css_style)
def add_js_code_from_link(self, js_link):
if js_link.startswith("//"):
js_link = "http:" + js_link
js_code = requests.get(js_link).text
add_js_code_script = (
'''var h = document.getElementsByTagName('head').item(0);'''
'''var s = document.createElement("script");'''
'''s.type = "text/javascript";'''
'''s.onload = function() { null };'''
'''s.appendChild(document.createTextNode("%s"));'''
'''h.appendChild(s);''')
js_code = re.escape(js_code)
js_code = self.__escape_quotes_if_needed(js_code)
self.execute_script(add_js_code_script % js_code)
def add_meta_tag(self, http_equiv=None, content=None):
if http_equiv is None:
http_equiv = "Content-Security-Policy"
if content is None:
content = ("default-src *; style-src 'self' 'unsafe-inline'; "
"script-src: 'self' 'unsafe-inline' 'unsafe-eval'")
script_to_add_meta = (
"""function injectMeta() {
var meta = document.createElement('meta');
meta.httpEquiv = "%s";
meta.content = "%s";
document.getElementsByTagName('head')[0].appendChild(meta);
}
injectMeta();""" % (http_equiv, content))
self.execute_script(script_to_add_meta)
def activate_jquery(self):
""" If "jQuery is not defined", use this method to activate it for use.
This happens because jQuery is not always defined on web sites. """
try:
# Let's first find out if jQuery is already defined.
self.execute_script("jQuery('html')")
# Since that command worked, jQuery is defined. Let's return.
return
except Exception:
# jQuery is not currently defined. Let's proceed by defining it.
pass
jquery_js = constants.JQuery.MIN_JS
self.add_js_link(jquery_js)
for x in range(int(settings.MINI_TIMEOUT * 10.0)):
# jQuery needs a small amount of time to activate.
try:
self.execute_script("jQuery('html')")
return
except Exception:
time.sleep(0.1)
# Since jQuery still isn't activating, give up and raise an exception
raise Exception(
'''Unable to load jQuery on "%s" due to a possible violation '''
'''of the website's Content Security Policy '''
'''directive. ''' % self.driver.current_url)
def __are_quotes_escaped(self, string):
return page_utils.are_quotes_escaped(string)
def __escape_quotes_if_needed(self, string):
return page_utils.escape_quotes_if_needed(string)
def __activate_bootstrap(self):
""" Allows you to use Bootstrap Tours with SeleniumBase
http://bootstraptour.com/
"""
bootstrap_tour_css = constants.BootstrapTour.MIN_CSS
bootstrap_tour_js = constants.BootstrapTour.MIN_JS
verify_script = ("""// Instance the tour
var tour2 = new Tour({
});""")
for x in range(4):
self.activate_jquery()
self.add_css_link(bootstrap_tour_css)
self.add_js_link(bootstrap_tour_js)
time.sleep(0.1)
for x in range(int(settings.MINI_TIMEOUT * 2.0)):
# Bootstrap needs a small amount of time to load & activate.
try:
self.execute_script(verify_script)
time.sleep(0.05)
return
except Exception:
time.sleep(0.15)
raise Exception(
'''Unable to load jQuery on "%s" due to a possible violation '''
'''of the website's Content Security Policy '''
'''directive. ''' % self.driver.current_url)
def __is_bootstrap_activated(self):
verify_script = ("""// Instance the tour
var tour2 = new Tour({
});""")
try:
self.execute_script(verify_script)
return True
except Exception:
return False
def __activate_introjs(self):
""" Allows you to use IntroJS Tours with SeleniumBase
https://introjs.com/
"""
intro_css = constants.IntroJS.MIN_CSS
intro_js = constants.IntroJS.MIN_JS
verify_script = ("""// Verify IntroJS activated
var intro2 = introJs();
""")
self.__activate_bootstrap()
self.wait_for_ready_state_complete()
for x in range(4):
self.activate_jquery()
self.add_css_link(intro_css)
self.add_js_link(intro_js)
time.sleep(0.1)
for x in range(int(settings.MINI_TIMEOUT * 2.0)):
# IntroJS needs a small amount of time to load & activate.
try:
self.execute_script(verify_script)
self.wait_for_ready_state_complete()
time.sleep(0.05)
return
except Exception:
time.sleep(0.15)
raise Exception(
'''Unable to load jQuery on "%s" due to a possible violation '''
'''of the website's Content Security Policy '''
'''directive. ''' % self.driver.current_url)
def __is_introjs_activated(self):
verify_script = ("""// Verify IntroJS activated
var intro2 = introJs();
""")
try:
self.execute_script(verify_script)
return True
except Exception:
return False
def __activate_shepherd(self):
""" Allows you to use Shepherd Tours with SeleniumBase
http://github.hubspot.com/shepherd/docs/welcome/
"""
shepherd_js = constants.Shepherd.MIN_JS
sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS
sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS
sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS
sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS
sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS
sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS
tether_js = constants.Tether.MIN_JS
underscore_js = constants.Underscore.MIN_JS
backbone_js = constants.Backbone.MIN_JS
spinner_css = constants.Messenger.SPINNER_CSS
sh_style = style_sheet.sh_style_test
backdrop_style = style_sheet.sh_backdrop_style
self.__activate_bootstrap()
self.wait_for_ready_state_complete()
self.add_css_style(backdrop_style)
self.wait_for_ready_state_complete()
for x in range(4):
# self.activate_jquery() # Included with __activate_bootstrap()
self.add_css_link(spinner_css)
self.add_css_link(sh_theme_arrows_css)
self.add_css_link(sh_theme_arrows_fix_css)
self.add_css_link(sh_theme_default_css)
self.add_css_link(sh_theme_dark_css)
self.add_css_link(sh_theme_sq_css)
self.add_css_link(sh_theme_sq_dark_css)
self.add_js_link(tether_js)
self.add_js_link(underscore_js)
self.add_js_link(backbone_js)
self.add_js_link(shepherd_js)
time.sleep(0.1)
for x in range(int(settings.MINI_TIMEOUT * 2.0)):
# Shepherd needs a small amount of time to load & activate.
try:
self.execute_script(sh_style) # Verify Shepherd has loaded
self.wait_for_ready_state_complete()
self.execute_script(sh_style) # Need it twice for ordering
self.wait_for_ready_state_complete()
time.sleep(0.05)
return
except Exception:
time.sleep(0.15)
raise Exception(
'''Unable to load jQuery on "%s" due to a possible violation '''
'''of the website's Content Security Policy '''
'''directive. ''' % self.driver.current_url)
def __is_shepherd_activated(self):
sh_style = style_sheet.sh_style_test
try:
self.execute_script(sh_style) # Verify Shepherd has loaded
return True
except Exception:
return False
def create_tour(self, name=None, theme=None):
""" Creates a tour for a website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
theme - Sets the default theme for the tour.
Choose from "light"/"arrows", "dark", "default", "square",
and "square-dark". ("arrows" is used if None is selected.)
"""
if not name:
name = "default"
shepherd_theme = "shepherd-theme-arrows"
if theme:
if theme.lower() == "bootstrap":
self.create_bootstrap_tour(name)
return
elif theme == "default":
shepherd_theme = "shepherd-theme-default"
elif theme == "dark":
shepherd_theme = "shepherd-theme-dark"
elif theme == "light":
shepherd_theme = "shepherd-theme-arrows"
elif theme == "arrows":
shepherd_theme = "shepherd-theme-arrows"
elif theme == "square":
shepherd_theme = "shepherd-theme-square"
elif theme == "square-dark":
shepherd_theme = "shepherd-theme-square-dark"
new_tour = ("""
// Shepherd Tour
let tour = new Shepherd.Tour({
defaults: {
classes: '%s',
scrollTo: true
}
});
""" % shepherd_theme)
self._tour_steps[name] = []
self._tour_steps[name].append(new_tour)
def create_bootstrap_tour(self, name=None):
""" Creates a Bootstrap tour for a website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
"""
if not name:
name = "default"
new_tour = (
"""
// Bootstrap Tour
var tour = new Tour({
});
tour.addSteps([
""")
self._tour_steps[name] = []
self._tour_steps[name].append(new_tour)
def create_introjs_tour(self, name=None):
""" Creates an IntroJS tour for a website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
"""
if not name:
name = "default"
new_tour = (
"""
// IntroJS Tour
function startIntro(){
var intro = introJs();
intro.setOptions({
steps: [
""")
self._tour_steps[name] = []
self._tour_steps[name].append(new_tour)
def add_tour_step(self, message, selector=None, name=None,
title=None, theme=None, alignment=None, duration=None):
""" Allows the user to add tour steps for a website.
@Params
message - The message to display.
selector - The CSS Selector of the Element to attach to.
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
title - Additional header text that appears above the message.
theme - (NON-Bootstrap Tours ONLY) The styling of the tour step.
Choose from "light"/"arrows", "dark", "default", "square",
and "square-dark". ("arrows" is used if None is selected.)
alignment - Choose from "top", "bottom", "left", and "right".
("top" is the default alignment).
duration - (Bootstrap Tours ONLY) The amount of time, in seconds,
before automatically advancing to the next tour step.
"""
if not selector:
selector = "html"
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
if not name:
name = "default"
if name not in self._tour_steps:
# By default, will create a Bootstrap tour if no tours exist
self.create_tour(name=name, theme="bootstrap")
if not title:
title = ""
title = re.escape(title)
title = self.__escape_quotes_if_needed(title)
if message:
message = re.escape(message)
message = self.__escape_quotes_if_needed(message)
else:
message = ""
if not alignment or (
alignment not in ["top", "bottom", "left", "right"]):
alignment = "top"
if "Bootstrap" in self._tour_steps[name][0]:
self.__add_bootstrap_tour_step(
message, selector=selector, name=name, title=title,
alignment=alignment, duration=duration)
elif "IntroJS" in self._tour_steps[name][0]:
self.__add_introjs_tour_step(
message, selector=selector, name=name, title=title,
alignment=alignment)
else:
self.__add_shepherd_tour_step(
message, selector=selector, name=name, title=title,
theme=theme, alignment=alignment)
def __add_shepherd_tour_step(self, message, selector=None, name=None,
title=None, theme=None, alignment=None):
""" Allows the user to add tour steps for a website.
@Params
message - The message to display.
selector - The CSS Selector of the Element to attach to.
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
title - Additional header text that appears above the message.
theme - (NON-Bootstrap Tours ONLY) The styling of the tour step.
Choose from "light"/"arrows", "dark", "default", "square",
and "square-dark". ("arrows" is used if None is selected.)
alignment - Choose from "top", "bottom", "left", and "right".
("top" is the default alignment).
"""
if theme == "default":
shepherd_theme = "shepherd-theme-default"
elif theme == "dark":
shepherd_theme = "shepherd-theme-dark"
elif theme == "light":
shepherd_theme = "shepherd-theme-arrows"
elif theme == "arrows":
shepherd_theme = "shepherd-theme-arrows"
elif theme == "square":
shepherd_theme = "shepherd-theme-square"
elif theme == "square-dark":
shepherd_theme = "shepherd-theme-square-dark"
else:
shepherd_base_theme = re.search(
r"[\S\s]+classes: '([\S\s]+)',[\S\s]+",
self._tour_steps[name][0]).group(1)
shepherd_theme = shepherd_base_theme
shepherd_classes = shepherd_theme
if selector == "html":
shepherd_classes += " shepherd-orphan"
step = ("""
tour.addStep('%s', {
title: '%s',
classes: '%s',
text: '%s',
attachTo: {element: '%s', on: '%s'},
advanceOn: '.docs-link click'
});""" % (
name, title, shepherd_classes, message, selector, alignment))
self._tour_steps[name].append(step)
def __add_bootstrap_tour_step(self, message, selector=None, name=None,
title=None, alignment=None, duration=None):
""" Allows the user to add tour steps for a website.
@Params
message - The message to display.
selector - The CSS Selector of the Element to attach to.
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
title - Additional header text that appears above the message.
alignment - Choose from "top", "bottom", "left", and "right".
("top" is the default alignment).
duration - (Bootstrap Tours ONLY) The amount of time, in seconds,
before automatically advancing to the next tour step.
"""
if selector != "html":
element_row = "element: '%s'," % selector
else:
element_row = ""
if not duration:
duration = "0"
else:
duration = str(float(duration) * 1000.0)
step = ("""{
%s
title: '%s',
content: '%s',
orphan: true,
placement: 'auto %s',
smartPlacement: true,
duration: %s,
},""" % (element_row, title, message, alignment, duration))
self._tour_steps[name].append(step)
def __add_introjs_tour_step(self, message, selector=None, name=None,
title=None, alignment=None):
""" Allows the user to add tour steps for a website.
@Params
message - The message to display.
selector - The CSS Selector of the Element to attach to.
name - If creating multiple tours, use this to select the
tour you wish to add steps to.
alignment - Choose from "top", "bottom", "left", and "right".
("top" is the default alignment).
"""
if selector != "html":
element_row = "element: '%s'," % selector
else:
element_row = ""
if title:
message = "<center><b>" + title + "</b></center><hr>" + message
message = '<font size=\"3\" color=\"#33475B\">' + message + '</font>'
step = ("""{%s
intro: '%s',
position: '%s'},
""" % (element_row, message, alignment))
self._tour_steps[name].append(step)
def play_tour(self, name=None, interval=0):
""" Plays a tour on the current website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to play.
interval - The delay time between autoplaying tour steps.
If set to 0 (default), the tour is fully manual control.
"""
if self.headless:
return # Tours should not run in headless mode.
if not name:
name = "default"
if name not in self._tour_steps:
raise Exception("Tour {%s} does not exist!" % name)
if "Bootstrap" in self._tour_steps[name][0]:
self.__play_bootstrap_tour(name=name, interval=interval)
elif "IntroJS" in self._tour_steps[name][0]:
self.__play_introjs_tour(name=name, interval=interval)
else:
self.__play_shepherd_tour(name=name, interval=interval)
def __play_shepherd_tour(self, name=None, interval=0):
""" Plays a tour on the current website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to play.
interval - The delay time between autoplaying tour steps.
If set to 0 (default), the tour is fully manual control.
"""
instructions = ""
for tour_step in self._tour_steps[name]:
instructions += tour_step
instructions += "tour.start();"
autoplay = False
if interval and interval > 0:
autoplay = True
interval = float(interval)
if interval < 0.5:
interval = 0.5
if not self.__is_shepherd_activated():
self.__activate_shepherd()
if len(self._tour_steps[name]) > 1:
try:
selector = re.search(
r"[\S\s]+{element: '([\S\s]+)', on: [\S\s]+",
self._tour_steps[name][1]).group(1)
selector = selector.replace('\\', '')
self.wait_for_element_present(
selector, timeout=(settings.SMALL_TIMEOUT))
except Exception:
self.__post_messenger_error_message(
"Tour Error: {'%s'} was not found!"
"" % selector,
duration=settings.SMALL_TIMEOUT)
raise Exception(
"Tour Error: {'%s'} was not found! "
"Exiting due to failure on first tour step!"
"" % selector)
self.execute_script(instructions)
tour_on = True
if autoplay:
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
latest_element = None
latest_text = None
while tour_on:
try:
time.sleep(0.01)
result = self.execute_script(
"return Shepherd.activeTour.currentStep.isOpen()")
except Exception:
tour_on = False
result = None
if result:
tour_on = True
if autoplay:
try:
element = self.execute_script(
"Shepherd.activeTour.currentStep"
".options.attachTo.element")
shep_text = self.execute_script(
"Shepherd.activeTour.currentStep.options.text")
except Exception:
continue
now_ms = time.time() * 1000.0
if now_ms >= stop_ms:
if ((element == latest_element) and
(shep_text == latest_text)):
self.execute_script("Shepherd.activeTour.next()")
try:
latest_element = self.execute_script(
"Shepherd.activeTour.currentStep"
".options.attachTo.element")
latest_text = self.execute_script(
"Shepherd.activeTour.currentStep"
".options.text")
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
except Exception:
pass
continue
else:
try:
time.sleep(0.01)
selector = self.execute_script(
"return Shepherd.activeTour"
".currentStep.options.attachTo.element")
try:
self.__wait_for_css_query_selector(
selector, timeout=(settings.MINI_TIMEOUT))
except Exception:
self.remove_elements("div.shepherd-content")
self.__post_messenger_error_message(
"Tour Error: {'%s'} was not found!"
"" % selector,
duration=settings.SMALL_TIMEOUT)
time.sleep(0.1)
self.execute_script("Shepherd.activeTour.next()")
if autoplay:
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
tour_on = True
except Exception:
tour_on = False
time.sleep(0.1)
def __play_bootstrap_tour(self, name=None, interval=0):
""" Plays a tour on the current website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to play.
interval - The delay time between autoplaying tour steps.
If set to 0 (default), the tour is fully manual control.
"""
instructions = ""
for tour_step in self._tour_steps[name]:
instructions += tour_step
instructions += (
"""]});
// Initialize the tour
tour.init();
// Start the tour
tour.start();
$tour = tour;
$tour.restart();""")
if interval and interval > 0:
if interval < 1:
interval = 1
interval = str(float(interval) * 1000.0)
instructions = instructions.replace(
'duration: 0,', 'duration: %s,' % interval)
if not self.__is_bootstrap_activated():
self.__activate_bootstrap()
if len(self._tour_steps[name]) > 1:
try:
if "element: " in self._tour_steps[name][1]:
selector = re.search(
r"[\S\s]+element: '([\S\s]+)',[\S\s]+title: '",
self._tour_steps[name][1]).group(1)
selector = selector.replace('\\', '')
self.wait_for_element_present(
selector, timeout=(settings.SMALL_TIMEOUT))
else:
selector = "html"
except Exception:
self.__post_messenger_error_message(
"Tour Error: {'%s'} was not found!"
"" % selector,
duration=settings.SMALL_TIMEOUT)
raise Exception(
"Tour Error: {'%s'} was not found! "
"Exiting due to failure on first tour step!"
"" % selector)
self.execute_script(instructions)
tour_on = True
while tour_on:
try:
time.sleep(0.01)
result = self.execute_script(
"return $tour.ended()")
except Exception:
tour_on = False
result = None
if result is False:
tour_on = True
else:
try:
time.sleep(0.01)
result = self.execute_script(
"return $tour.ended()")
if result is False:
time.sleep(0.1)
continue
else:
return
except Exception:
tour_on = False
time.sleep(0.1)
def __play_introjs_tour(self, name=None, interval=0):
""" Plays a tour on the current website.
@Params
name - If creating multiple tours, use this to select the
tour you wish to play.
"""
instructions = ""
for tour_step in self._tour_steps[name]:
instructions += tour_step
instructions += (
"""]
});
intro.setOption("disableInteraction", true);
intro.setOption("overlayOpacity", .29);
intro.setOption("scrollToElement", true);
intro.setOption("keyboardNavigation", true);
intro.setOption("exitOnEsc", false);
intro.setOption("exitOnOverlayClick", false);
intro.setOption("showStepNumbers", false);
intro.setOption("showProgress", false);
intro.start();
$intro = intro;
}
startIntro();
""")
autoplay = False
if interval and interval > 0:
autoplay = True
interval = float(interval)
if interval < 0.5:
interval = 0.5
if not self.__is_introjs_activated():
self.__activate_introjs()
if len(self._tour_steps[name]) > 1:
try:
if "element: " in self._tour_steps[name][1]:
selector = re.search(
r"[\S\s]+element: '([\S\s]+)',[\S\s]+intro: '",
self._tour_steps[name][1]).group(1)
selector = selector.replace('\\', '')
self.wait_for_element_present(
selector, timeout=settings.SMALL_TIMEOUT)
else:
selector = "html"
except Exception:
self.__post_messenger_error_message(
"Tour Error: {'%s'} was not found!"
"" % selector,
duration=settings.SMALL_TIMEOUT)
raise Exception(
"Tour Error: {'%s'} was not found! "
"Exiting due to failure on first tour step!"
"" % selector)
self.execute_script(instructions)
tour_on = True
if autoplay:
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
latest_step = 0
while tour_on:
try:
time.sleep(0.01)
if self.browser != "firefox":
result = self.execute_script(
"return $intro._currentStep")
else:
self.wait_for_element_present(
".introjs-tooltip", timeout=0.4)
result = True
except Exception:
tour_on = False
result = None
if result is not None:
tour_on = True
if autoplay:
try:
current_step = self.execute_script(
"return $intro._currentStep")
except Exception:
continue
if current_step != latest_step:
latest_step = current_step
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
now_ms = time.time() * 1000.0
if now_ms >= stop_ms:
if current_step == latest_step:
self.execute_script("return $intro.nextStep()")
try:
latest_step = self.execute_script(
"return $intro._currentStep")
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
except Exception:
pass
continue
else:
try:
time.sleep(0.01)
if self.browser != "firefox":
result = self.execute_script(
"return $intro._currentStep")
else:
self.wait_for_element_present(
".introjs-tooltip", timeout=0.4)
result = True
if result is not None:
time.sleep(0.1)
continue
else:
return
except Exception:
tour_on = False
time.sleep(0.1)
def __wait_for_css_query_selector(
self, selector, timeout=settings.SMALL_TIMEOUT):
element = None
start_ms = time.time() * 1000.0
stop_ms = start_ms + (timeout * 1000.0)
for x in range(int(timeout * 10)):
try:
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
element = self.execute_script(
"""return document.querySelector('%s')""" % selector)
if element:
return element
except Exception:
element = None
if not element:
now_ms = time.time() * 1000.0
if now_ms >= stop_ms:
break
time.sleep(0.1)
raise Exception(
"Element {%s} was not present after %s seconds!" % (
selector, timeout))
def activate_messenger(self):
jquery_js = constants.JQuery.MIN_JS
messenger_css = constants.Messenger.MIN_CSS
messenger_js = constants.Messenger.MIN_JS
msgr_theme_flat_js = constants.Messenger.THEME_FLAT_JS
msgr_theme_future_js = constants.Messenger.THEME_FUTURE_JS
msgr_theme_flat_css = constants.Messenger.THEME_FLAT_CSS
msgr_theme_future_css = constants.Messenger.THEME_FUTURE_CSS
msgr_theme_block_css = constants.Messenger.THEME_BLOCK_CSS
msgr_theme_air_css = constants.Messenger.THEME_AIR_CSS
msgr_theme_ice_css = constants.Messenger.THEME_ICE_CSS
spinner_css = constants.Messenger.SPINNER_CSS
underscore_js = constants.Underscore.MIN_JS
backbone_js = constants.Backbone.MIN_JS
msg_style = ("Messenger.options = {'maxMessages': 8, "
"extraClasses: 'messenger-fixed "
"messenger-on-bottom messenger-on-right', "
"theme: 'future'}")
self.add_js_link(jquery_js)
self.add_css_link(messenger_css)
self.add_css_link(msgr_theme_flat_css)
self.add_css_link(msgr_theme_future_css)
self.add_css_link(msgr_theme_block_css)
self.add_css_link(msgr_theme_air_css)
self.add_css_link(msgr_theme_ice_css)
self.add_js_link(underscore_js)
self.add_js_link(backbone_js)
self.add_css_link(spinner_css)
self.add_js_link(messenger_js)
self.add_js_link(msgr_theme_flat_js)
self.add_js_link(msgr_theme_future_js)
for x in range(int(settings.MINI_TIMEOUT * 10.0)):
# Messenger needs a small amount of time to load & activate.
try:
self.execute_script(msg_style)
self.wait_for_ready_state_complete()
return
except Exception:
time.sleep(0.1)
def set_messenger_theme(self, theme="default", location="default",
max_messages="default"):
if theme == "default":
theme = "future"
if location == "default":
location = "bottom_right"
if max_messages == "default":
max_messages = "8"
valid_themes = ['flat', 'future', 'block', 'air', 'ice']
if theme not in valid_themes:
raise Exception("Theme: %s is not in %s!" % (theme, valid_themes))
valid_locations = (['top_left', 'top_center', 'top_right'
'bottom_left', 'bottom_center', 'bottom_right'])
if location not in valid_locations:
raise Exception(
"Location: %s is not in %s!" % (location, valid_locations))
if location == 'top_left':
messenger_location = "messenger-on-top messenger-on-left"
elif location == 'top_center':
messenger_location = "messenger-on-top"
elif location == 'top_right':
messenger_location = "messenger-on-top messenger-on-right"
elif location == 'bottom_left':
messenger_location = "messenger-on-bottom messenger-on-left"
elif location == 'bottom_center':
messenger_location = "messenger-on-bottom"
elif location == 'bottom_right':
messenger_location = "messenger-on-bottom messenger-on-right"
msg_style = ("Messenger.options = {'maxMessages': %s, "
"extraClasses: 'messenger-fixed %s', theme: '%s'}"
% (max_messages, messenger_location, theme))
try:
self.execute_script(msg_style)
except Exception:
self.activate_messenger()
self.execute_script(msg_style)
time.sleep(0.1)
def post_message(self, message, style="info", duration=None):
""" Post a message on the screen with Messenger.
Arguments:
message: The message to display.
style: "info", "success", or "error".
duration: The time until the message vanishes.
You can also post messages by using =>
self.execute_script('Messenger().post("My Message")')
"""
if not duration:
if not self.message_duration:
duration = settings.DEFAULT_MESSAGE_DURATION
else:
duration = self.message_duration
message = re.escape(message)
message = self.__escape_quotes_if_needed(message)
messenger_script = ('''Messenger().post({message: "%s", type: "%s", '''
'''hideAfter: %s, hideOnNavigate: true});'''
% (message, style, duration))
try:
self.execute_script(messenger_script)
except Exception:
self.activate_messenger()
self.set_messenger_theme()
try:
self.execute_script(messenger_script)
except Exception:
time.sleep(0.2)
self.activate_messenger()
time.sleep(0.2)
self.set_messenger_theme()
time.sleep(0.5)
self.execute_script(messenger_script)
def get_property_value(self, selector, property, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Returns the property value of a page element's computed style.
Example:
opacity = self.get_property_value("html body a", "opacity")
self.assertTrue(float(opacity) > 0, "Element not visible!") """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
self.wait_for_ready_state_complete()
page_actions.wait_for_element_present(
self.driver, selector, by, timeout)
try:
selector = self.convert_to_css_selector(selector, by=by)
except Exception:
# Don't run action if can't convert to CSS_Selector for JavaScript
raise Exception(
"Exception: Could not convert {%s}(by=%s) to CSS_SELECTOR!" % (
selector, by))
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
script = ("""var $elm = document.querySelector('%s');
$val = window.getComputedStyle($elm).getPropertyValue('%s');
return $val;"""
% (selector, property))
value = self.execute_script(script)
if value is not None:
return value
else:
return "" # Return an empty string if the property doesn't exist
def bring_to_front(self, selector, by=By.CSS_SELECTOR):
""" Updates the Z-index of a page element to bring it into view.
Useful when getting a WebDriverException, such as the one below:
{ Element is not clickable at point (#, #).
Other element would receive the click: ... } """
if page_utils.is_xpath_selector(selector):
by = By.XPATH
self.find_element(selector, by=by, timeout=settings.SMALL_TIMEOUT)
try:
selector = self.convert_to_css_selector(selector, by=by)
except Exception:
# Don't run action if can't convert to CSS_Selector for JavaScript
return
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
script = ("""document.querySelector('%s').style.zIndex = '9999';"""
% selector)
self.execute_script(script)
def highlight_click(self, selector, by=By.CSS_SELECTOR,
loops=3, scroll=True):
if not self.demo_mode:
self.highlight(selector, by=by, loops=loops, scroll=scroll)
self.click(selector, by=by)
def highlight_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
loops=3, scroll=True):
if not self.demo_mode:
self.highlight(selector, by=by, loops=loops, scroll=scroll)
self.update_text(selector, new_value, by=by)
def highlight(self, selector, by=By.CSS_SELECTOR,
loops=settings.HIGHLIGHTS, scroll=True):
""" This method uses fancy JavaScript to highlight an element.
Used during demo_mode.
@Params
selector - the selector of the element to find
by - the type of selector to search by (Default: CSS)
loops - # of times to repeat the highlight animation
(Default: 4. Each loop lasts for about 0.18s)
scroll - the option to scroll to the element first (Default: True)
"""
selector, by = self.__recalculate_selector(selector, by)
element = self.find_element(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
if scroll:
self.__slow_scroll_to_element(element)
try:
selector = self.convert_to_css_selector(selector, by=by)
except Exception:
# Don't highlight if can't convert to CSS_SELECTOR
return
if self.highlights:
loops = self.highlights
if self.browser == 'ie':
loops = 1 # Override previous setting because IE is slow
loops = int(loops)
o_bs = '' # original_box_shadow
style = element.get_attribute('style')
if style:
if 'box-shadow: ' in style:
box_start = style.find('box-shadow: ')
box_end = style.find(';', box_start) + 1
original_box_shadow = style[box_start:box_end]
o_bs = original_box_shadow
if ":contains" not in selector and ":first" not in selector:
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
self.__highlight_with_js(selector, loops, o_bs)
else:
selector = self.__make_css_match_first_element_only(selector)
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
try:
self.__highlight_with_jquery(selector, loops, o_bs)
except Exception:
pass # JQuery probably couldn't load. Skip highlighting.
time.sleep(0.065)
def __highlight_with_js(self, selector, loops, o_bs):
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
% selector)
self.execute_script(script)
for n in range(loops):
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(255, 0, 0, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(0, 0, 255, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(0, 255, 0, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 128, 0, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: %s';"""
% (selector, o_bs))
self.execute_script(script)
def __highlight_with_jquery(self, selector, loops, o_bs):
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 128, 128, 0.5)');""" % selector
self.safe_execute_script(script)
for n in range(loops):
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(255, 0, 0, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 0, 128, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(0, 0, 255, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(0, 255, 0, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 128, 0, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 0, 128, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
self.execute_script(script)
def scroll_to(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
''' Fast scroll to destination '''
if self.demo_mode:
self.slow_scroll_to(selector, by=by, timeout=timeout)
return
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
try:
self.__scroll_to_element(element)
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
self.__scroll_to_element(element)
def slow_scroll_to(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
''' Slow motion scroll to destination '''
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
self.__slow_scroll_to_element(element)
def scroll_click(self, selector, by=By.CSS_SELECTOR):
# DEPRECATED - self.click() now scrolls to the element before clicking
# self.scroll_to(selector, by=by)
self.click(selector, by=by)
def click_xpath(self, xpath):
self.click(xpath, by=By.XPATH)
def js_click(self, selector, by=By.CSS_SELECTOR):
""" Clicks an element using pure JS. Does not use jQuery. """
selector, by = self.__recalculate_selector(selector, by)
if by == By.LINK_TEXT:
message = (
"Pure JavaScript doesn't support clicking by Link Text. "
"You may want to use self.jquery_click() instead, which "
"allows this with :contains(), assuming jQuery isn't blocked. "
"For now, self.js_click() will use a regular WebDriver click.")
logging.debug(message)
self.click(selector, by=by)
return
element = self.wait_for_element_present(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
if self.is_element_visible(selector, by=by):
self.__demo_mode_highlight_if_active(selector, by)
if not self.demo_mode:
self.__scroll_to_element(element)
css_selector = self.convert_to_css_selector(selector, by=by)
css_selector = re.escape(css_selector)
css_selector = self.__escape_quotes_if_needed(css_selector)
self.__js_click(selector, by=by) # The real "magic" happens here
self.__demo_mode_pause_if_active()
def jquery_click(self, selector, by=By.CSS_SELECTOR):
""" Clicks an element using jQuery. Different from using pure JS. """
selector, by = self.__recalculate_selector(selector, by)
self.wait_for_element_present(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
if self.is_element_visible(selector, by=by):
self.__demo_mode_highlight_if_active(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
click_script = """jQuery('%s')[0].click()""" % selector
self.safe_execute_script(click_script)
self.__demo_mode_pause_if_active()
def hide_element(self, selector, by=By.CSS_SELECTOR):
""" Hide the first element on the page that matches the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
hide_script = """jQuery('%s').hide()""" % selector
self.safe_execute_script(hide_script)
def hide_elements(self, selector, by=By.CSS_SELECTOR):
""" Hide all elements on the page that match the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
hide_script = """jQuery('%s').hide()""" % selector
self.safe_execute_script(hide_script)
def show_element(self, selector, by=By.CSS_SELECTOR):
""" Show the first element on the page that matches the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
show_script = """jQuery('%s').show(0)""" % selector
self.safe_execute_script(show_script)
def show_elements(self, selector, by=By.CSS_SELECTOR):
""" Show all elements on the page that match the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
show_script = """jQuery('%s').show(0)""" % selector
self.safe_execute_script(show_script)
def remove_element(self, selector, by=By.CSS_SELECTOR):
""" Remove the first element on the page that matches the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
remove_script = """jQuery('%s').remove()""" % selector
self.safe_execute_script(remove_script)
def remove_elements(self, selector, by=By.CSS_SELECTOR):
""" Remove all elements on the page that match the selector. """
selector, by = self.__recalculate_selector(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
remove_script = """jQuery('%s').remove()""" % selector
self.safe_execute_script(remove_script)
def ad_block(self):
from seleniumbase.config import ad_block_list
for css_selector in ad_block_list.AD_BLOCK_LIST:
css_selector = re.escape(css_selector)
css_selector = self.__escape_quotes_if_needed(css_selector)
script = ("""var $elements = document.querySelectorAll('%s');
var index = 0, length = $elements.length;
for(; index < length; index++){
$elements[index].remove();}"""
% css_selector)
try:
self.execute_script(script)
except Exception:
pass # Don't fail test if ad_blocking fails
def jq_format(self, code):
# DEPRECATED - Use re.escape() instead, which does the action you want.
return page_utils._jq_format(code)
def get_domain_url(self, url):
return page_utils.get_domain_url(url)
def safe_execute_script(self, script):
""" When executing a script that contains a jQuery command,
it's important that the jQuery library has been loaded first.
This method will load jQuery if it wasn't already loaded. """
try:
self.execute_script(script)
except Exception:
# The likely reason this fails is because: "jQuery is not defined"
self.activate_jquery() # It's a good thing we can define it here
self.execute_script(script)
def download_file(self, file_url, destination_folder=None):
""" Downloads the file from the url to the destination folder.
If no destination folder is specified, the default one is used.
(The default downloads folder = "./downloaded_files") """
if not destination_folder:
destination_folder = constants.Files.DOWNLOADS_FOLDER
page_utils._download_file_to(file_url, destination_folder)
def save_file_as(self, file_url, new_file_name, destination_folder=None):
""" Similar to self.download_file(), except that you get to rename the
file being downloaded to whatever you want. """
if not destination_folder:
destination_folder = constants.Files.DOWNLOADS_FOLDER
page_utils._download_file_to(
file_url, destination_folder, new_file_name)
def save_data_as(self, data, file_name, destination_folder=None):
""" Saves the data specified to a file of the name specified.
If no destination folder is specified, the default one is used.
(The default downloads folder = "./downloaded_files") """
if not destination_folder:
destination_folder = constants.Files.DOWNLOADS_FOLDER
page_utils._save_data_as(data, destination_folder, file_name)
def get_downloads_folder(self):
""" Returns the OS path of the Downloads Folder.
(Works with Chrome and Firefox only, for now.) """
return download_helper.get_downloads_folder()
def get_path_of_downloaded_file(self, file):
""" Returns the OS path of the downloaded file. """
return os.path.join(self.get_downloads_folder(), file)
def is_downloaded_file_present(self, file):
""" Checks if the file exists in the Downloads Folder. """
return os.path.exists(self.get_path_of_downloaded_file(file))
def assert_downloaded_file(self, file):
""" Asserts that the file exists in the Downloads Folder. """
assert os.path.exists(self.get_path_of_downloaded_file(file))
def get_google_auth_password(self, totp_key=None):
""" Returns a time-based one-time password based on the
Google Authenticator password algorithm. Works with Authy.
If "totp_key" is not specified, defaults to using the one
provided in seleniumbase/config/settings.py
Google Auth passwords expire and change at 30-second intervals.
If the fetched password expires in the next 3 seconds, waits
for a fresh one before returning it (may take up to 3 seconds).
See https://pyotp.readthedocs.io/en/latest/ for details. """
import pyotp
if not totp_key:
totp_key = settings.TOTP_KEY
epoch_interval = time.time() / 30.0
cycle_lifespan = float(epoch_interval) - int(epoch_interval)
if float(cycle_lifespan) > 0.90:
# Password expires in less than 3 seconds. Wait for a fresh one.
for i in range(60):
time.sleep(0.05)
epoch_interval = time.time() / 30.0
cycle_lifespan = float(epoch_interval) - int(epoch_interval)
if not float(cycle_lifespan) > 0.90:
# The new password cycle has begun
break
totp = pyotp.TOTP(totp_key)
return str(totp.now())
def convert_xpath_to_css(self, xpath):
return xpath_to_css.convert_xpath_to_css(xpath)
def convert_to_css_selector(self, selector, by):
""" This method converts a selector to a CSS_SELECTOR.
jQuery commands require a CSS_SELECTOR for finding elements.
This method should only be used for jQuery/JavaScript actions.
Pure JavaScript doesn't support using a:contains("LINK_TEXT"). """
if by == By.CSS_SELECTOR:
return selector
elif by == By.ID:
return '#%s' % selector
elif by == By.CLASS_NAME:
return '.%s' % selector
elif by == By.NAME:
return '[name="%s"]' % selector
elif by == By.TAG_NAME:
return selector
elif by == By.XPATH:
return self.convert_xpath_to_css(selector)
elif by == By.LINK_TEXT:
return 'a:contains("%s")' % selector
elif by == By.PARTIAL_LINK_TEXT:
return 'a:contains("%s")' % selector
else:
raise Exception(
"Exception: Could not convert {%s}(by=%s) to CSS_SELECTOR!" % (
selector, by))
def set_value(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" This method uses JavaScript to update a text field. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
orginal_selector = selector
css_selector = self.convert_to_css_selector(selector, by=by)
self.__demo_mode_highlight_if_active(orginal_selector, by)
if not self.demo_mode:
self.scroll_to(orginal_selector, by=by, timeout=timeout)
value = re.escape(new_value)
value = self.__escape_quotes_if_needed(value)
css_selector = re.escape(css_selector)
css_selector = self.__escape_quotes_if_needed(css_selector)
script = ("""document.querySelector('%s').value='%s';"""
% (css_selector, value))
self.execute_script(script)
if new_value.endswith('\n'):
element = self.wait_for_element_present(
orginal_selector, by=by, timeout=timeout)
element.send_keys(Keys.RETURN)
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
self.wait_for_ready_state_complete()
self.__demo_mode_pause_if_active()
def js_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Same as self.set_value() """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.set_value(
selector, new_value, by=by, timeout=timeout)
def jquery_update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" This method uses jQuery to update a text field.
If the new_value string ends with the newline character,
WebDriver will finish the call, which simulates pressing
{Enter/Return} after the text is entered. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
element = self.wait_for_element_visible(
selector, by=by, timeout=timeout)
self.__demo_mode_highlight_if_active(selector, by)
self.scroll_to(selector, by=by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
selector = self.__escape_quotes_if_needed(selector)
new_value = re.escape(new_value)
new_value = self.__escape_quotes_if_needed(new_value)
update_text_script = """jQuery('%s').val('%s')""" % (
selector, new_value)
self.safe_execute_script(update_text_script)
if new_value.endswith('\n'):
element.send_keys('\n')
self.__demo_mode_pause_if_active()
def jquery_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" The shorter version of self.jquery_update_text_value()
(The longer version remains for backwards compatibility.) """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.jquery_update_text_value(
selector, new_value, by=by, timeout=timeout)
def hover_on_element(self, selector, by=By.CSS_SELECTOR):
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
self.wait_for_element_visible(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
self.__demo_mode_highlight_if_active(selector, by)
self.scroll_to(selector, by=by)
time.sleep(0.05) # Settle down from scrolling before hovering
return page_actions.hover_on_element(self.driver, selector)
def hover_and_click(self, hover_selector, click_selector,
hover_by=By.CSS_SELECTOR, click_by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(hover_selector):
hover_by = By.XPATH
if page_utils.is_xpath_selector(click_selector):
click_by = By.XPATH
if page_utils.is_link_text_selector(hover_selector):
hover_selector = page_utils.get_link_text_from_selector(
hover_selector)
hover_by = By.LINK_TEXT
if page_utils.is_link_text_selector(click_selector):
click_selector = page_utils.get_link_text_from_selector(
click_selector)
click_by = By.LINK_TEXT
self.wait_for_element_visible(
hover_selector, by=hover_by, timeout=timeout)
self.__demo_mode_highlight_if_active(hover_selector, hover_by)
self.scroll_to(hover_selector, by=hover_by)
pre_action_url = self.driver.current_url
element = page_actions.hover_and_click(
self.driver, hover_selector, click_selector,
hover_by, click_by, timeout)
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
return element
def pick_select_option_by_text(self, dropdown_selector, option,
dropdown_by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Picks an HTML <select> option by option text. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.__pick_select_option(dropdown_selector, option,
dropdown_by=dropdown_by, option_by="text",
timeout=timeout)
def pick_select_option_by_index(self, dropdown_selector, option,
dropdown_by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Picks an HTML <select> option by option index. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.__pick_select_option(dropdown_selector, option,
dropdown_by=dropdown_by, option_by="index",
timeout=timeout)
def pick_select_option_by_value(self, dropdown_selector, option,
dropdown_by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Picks an HTML <select> option by option value. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.__pick_select_option(dropdown_selector, option,
dropdown_by=dropdown_by, option_by="value",
timeout=timeout)
############
def generate_referral(self, start_page, destination_page):
""" This method opens the start_page, creates a referral link there,
and clicks on that link, which goes to the destination_page.
(This generates real traffic for testing analytics software.) """
if not page_utils.is_valid_url(destination_page):
raise Exception(
"Exception: destination_page {%s} is not a valid URL!"
% destination_page)
if start_page:
if not page_utils.is_valid_url(start_page):
raise Exception(
"Exception: start_page {%s} is not a valid URL! "
"(Use an empty string or None to start from current page.)"
% start_page)
self.open(start_page)
time.sleep(0.08)
referral_link = ('''<a class='analytics referral test' href='%s' '''
'''style='font-family: Arial,sans-serif; '''
'''font-size: 30px; color: #18a2cd'>'''
'''Magic Link Button</a>''' % destination_page)
self.execute_script(
'''document.body.innerHTML = \"%s\"''' % referral_link)
time.sleep(0.1)
self.click("a.analytics.referral.test") # Clicks the generated button
time.sleep(0.15)
try:
self.click("html")
time.sleep(0.08)
except Exception:
pass
def generate_traffic(self, start_page, destination_page, loops=1):
""" Similar to generate_referral(), but can do multiple loops. """
for loop in range(loops):
self.generate_referral(start_page, destination_page)
time.sleep(0.05)
def generate_referral_chain(self, pages):
""" Use this method to chain the action of creating button links on
one website page that will take you to the next page.
(When you want to create a referral to a website for traffic
generation without increasing the bounce rate, you'll want to visit
at least one additional page on that site with a button click.) """
if not type(pages) is tuple and not type(pages) is list:
raise Exception(
"Exception: Expecting a list of website pages for chaining!")
if len(pages) < 2:
raise Exception(
"Exception: At least two website pages required for chaining!")
for page in pages:
# Find out if any of the web pages are invalid before continuing
if not page_utils.is_valid_url(page):
raise Exception(
"Exception: Website page {%s} is not a valid URL!" % page)
for page in pages:
self.generate_referral(None, page)
def generate_traffic_chain(self, pages, loops=1):
""" Similar to generate_referral_chain(), but for multiple loops. """
for loop in range(loops):
self.generate_referral_chain(pages)
time.sleep(0.05)
############
def wait_for_element_present(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Waits for an element to appear in the HTML of a page.
The element does not need be visible (it may be hidden). """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.wait_for_element_present(
self.driver, selector, by, timeout)
def assert_element_present(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_element_present(), but returns nothing.
Waits for an element to appear in the HTML of a page.
The element does not need be visible (it may be hidden).
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_element_present(selector, by=by, timeout=timeout)
return True
# For backwards compatibility, earlier method names of the next
# four methods have remained even though they do the same thing,
# with the exception of assert_*, which won't return the element,
# but like the others, will raise an exception if the call fails.
def wait_for_element_visible(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Waits for an element to appear in the HTML of a page.
The element must be visible (it cannot be hidden). """
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.wait_for_element_visible(
self.driver, selector, by, timeout)
def wait_for_element(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" The shorter version of wait_for_element_visible() """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_element_visible(selector, by=by, timeout=timeout)
def find_element(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Same as wait_for_element_visible() - returns the element """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_element_visible(selector, by=by, timeout=timeout)
def assert_element(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_element_visible(), but returns nothing.
As above, will raise an exception if nothing can be found.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_element_visible(selector, by=by, timeout=timeout)
if self.demo_mode:
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
messenger_post = "ASSERT %s: %s" % (by, selector)
self.__highlight_with_assert_success(messenger_post, selector, by)
return True
# For backwards compatibility, earlier method names of the next
# four methods have remained even though they do the same thing,
# with the exception of assert_*, which won't return the element,
# but like the others, will raise an exception if the call fails.
def wait_for_text_visible(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.wait_for_text_visible(
self.driver, text, selector, by, timeout)
def wait_for_text(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" The shorter version of wait_for_text_visible() """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_text_visible(
text, selector, by=by, timeout=timeout)
def find_text(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Same as wait_for_text_visible() - returns the element """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_text_visible(
text, selector, by=by, timeout=timeout)
def assert_text_visible(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Same as assert_text() """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.assert_text(text, selector, by=by, timeout=timeout)
def assert_text(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_text_visible()
Raises an exception if the element or the text is not found.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_text_visible(text, selector, by=by, timeout=timeout)
if self.demo_mode:
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
messenger_post = ("ASSERT TEXT {%s} in %s: %s"
% (text, by, selector))
self.__highlight_with_assert_success(messenger_post, selector, by)
return True
# For backwards compatibility, earlier method names of the next
# four methods have remained even though they do the same thing,
# with the exception of assert_*, which won't return the element,
# but like the others, will raise an exception if the call fails.
def wait_for_link_text_visible(self, link_text,
timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_element_visible(
link_text, by=By.LINK_TEXT, timeout=timeout)
def wait_for_link_text(self, link_text, timeout=settings.LARGE_TIMEOUT):
""" The shorter version of wait_for_link_text_visible() """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_link_text_visible(link_text, timeout=timeout)
def find_link_text(self, link_text, timeout=settings.LARGE_TIMEOUT):
""" Same as wait_for_link_text_visible() - returns the element """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_link_text_visible(link_text, timeout=timeout)
def assert_link_text(self, link_text, timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_link_text_visible(), but returns nothing.
As above, will raise an exception if nothing can be found.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_link_text_visible(link_text, timeout=timeout)
if self.demo_mode:
messenger_post = ("ASSERT LINK TEXT {%s}." % link_text)
self.__highlight_with_assert_success(
messenger_post, link_text, by=By.LINK_TEXT)
return True
# For backwards compatibility, earlier method names of the next
# four methods have remained even though they do the same thing,
# with the exception of assert_*, which won't return the element,
# but like the others, will raise an exception if the call fails.
def wait_for_partial_link_text(self, partial_link_text,
timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_element_visible(
partial_link_text, by=By.PARTIAL_LINK_TEXT, timeout=timeout)
def find_partial_link_text(self, partial_link_text,
timeout=settings.LARGE_TIMEOUT):
""" Same as wait_for_partial_link_text() - returns the element """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return self.wait_for_partial_link_text(
partial_link_text, timeout=timeout)
def assert_partial_link_text(self, partial_link_text,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_partial_link_text(), but returns nothing.
As above, will raise an exception if nothing can be found.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_partial_link_text(partial_link_text, timeout=timeout)
return True
############
def wait_for_element_absent(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Waits for an element to no longer appear in the HTML of a page.
A hidden element still counts as appearing in the page HTML.
If an element with "hidden" status is acceptable,
use wait_for_element_not_visible() instead. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
return page_actions.wait_for_element_absent(
self.driver, selector, by, timeout)
def assert_element_absent(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_element_absent() - returns nothing.
As above, will raise an exception if the element stays present.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_element_absent(selector, by=by, timeout=timeout)
return True
############
def wait_for_element_not_visible(self, selector, by=By.CSS_SELECTOR,
timeout=settings.LARGE_TIMEOUT):
""" Waits for an element to no longer be visible on a page.
The element can be non-existant in the HTML or hidden on the page
to qualify as not visible. """
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return page_actions.wait_for_element_not_visible(
self.driver, selector, by, timeout)
def assert_element_not_visible(self, selector, by=By.CSS_SELECTOR,
timeout=settings.SMALL_TIMEOUT):
""" Similar to wait_for_element_not_visible() - returns nothing.
As above, will raise an exception if the element stays visible.
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_element_not_visible(selector, by=by, timeout=timeout)
return True
############
def wait_for_ready_state_complete(self, timeout=settings.EXTREME_TIMEOUT):
if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
is_ready = page_actions.wait_for_ready_state_complete(self.driver,
timeout)
self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)
if self.ad_block_on:
# If the ad_block feature is enabled, then block ads for new URLs
current_url = self.get_current_url()
if not current_url == self.__last_page_load_url:
time.sleep(0.02)
self.ad_block()
time.sleep(0.01)
if self.is_element_present("iframe"):
time.sleep(0.07) # iframe ads take slightly longer to load
self.ad_block() # Do ad_block on slower-loading iframes
self.__last_page_load_url = current_url
return is_ready
def wait_for_angularjs(self, timeout=settings.LARGE_TIMEOUT, **kwargs):
if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
if not settings.WAIT_FOR_ANGULARJS:
return
NG_WRAPPER = '%(prefix)s' \
'var $elm=document.querySelector(' \
'\'[data-ng-app],[ng-app],.ng-scope\')||document;' \
'if(window.angular && angular.getTestability){' \
'angular.getTestability($elm).whenStable(%(handler)s)' \
'}else{' \
'var $inj;try{$inj=angular.element($elm).injector()||' \
'angular.injector([\'ng\'])}catch(ex){' \
'$inj=angular.injector([\'ng\'])};$inj.get=$inj.get||' \
'$inj;$inj.get(\'$browser\').' \
'notifyWhenNoOutstandingRequests(%(handler)s)}' \
'%(suffix)s'
def_pre = 'var cb=arguments[arguments.length-1];if(window.angular){'
prefix = kwargs.pop('prefix', def_pre)
handler = kwargs.pop('handler', 'function(){cb(true)}')
suffix = kwargs.pop('suffix', '}else{cb(false)}')
script = NG_WRAPPER % {'prefix': prefix,
'handler': handler,
'suffix': suffix}
try:
self.execute_async_script(script, timeout=timeout)
except Exception:
time.sleep(0.05)
def wait_for_and_accept_alert(self, timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return page_actions.wait_for_and_accept_alert(self.driver, timeout)
def wait_for_and_dismiss_alert(self, timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)
def wait_for_and_switch_to_alert(self, timeout=settings.LARGE_TIMEOUT):
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
timeout = self.__get_new_timeout(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_window(self):
self.switch_to_window(0)
def save_screenshot(self, name, folder=None):
""" The screenshot will be in PNG format. """
return page_actions.save_screenshot(self.driver, name, folder)
def get_new_driver(self, browser=None, headless=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. 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
use_grid = False
if servername != "localhost":
# 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_name not in valid_browsers:
raise Exception("Browser: {%s} is not a valid browser option. "
"Valid options = {%s}" % (browser, valid_browsers))
# Launch a web browser
from seleniumbase.core import browser_launcher
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
if self.headless:
# Make sure the invisible browser window is big enough
try:
self.set_window_size(1920, 1200)
self.wait_for_ready_state_complete()
except Exception:
# This shouldn't fail, but in case it does,
# get safely through setUp() so that
# WebDrivers can get closed during tearDown().
pass
else:
if self.browser == 'chrome':
try:
if settings.START_CHROME_IN_FULL_SCREEN_MODE:
self.driver.maximize_window()
else:
self.driver.set_window_size(1250, 800)
self.wait_for_ready_state_complete()
except Exception:
pass # Keep existing browser resolution
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):
""" When using --timeout_multiplier=#.# """
try:
timeout_multiplier = float(self.timeout_multiplier)
if timeout_multiplier <= 0.5:
timeout_multiplier = 0.5
timeout = int(math.ceil(timeout_multiplier * timeout))
return timeout
except Exception:
# Wrong data type for timeout_multiplier (expecting int or float)
return timeout
############
def __get_exception_message(self):
""" This method extracts the message from an exception if there
was an exception that occurred during the test, assuming
that the exception was in a try/except block and not thrown. """
exception_info = sys.exc_info()[1]
if hasattr(exception_info, 'msg'):
exc_message = exception_info.msg
elif hasattr(exception_info, 'message'):
exc_message = exception_info.message
else:
exc_message = '(Unknown Exception)'
return exc_message
def __get_improved_exception_message(self):
"""
If Chromedriver is out-of-date, make it clear!
Given the high popularity of the following StackOverflow article:
https://stackoverflow.com/questions/49162667/unknown-error-
call-function-result-missing-value-for-selenium-send-keys-even
... the original error message was not helpful. Tell people directly.
(Only expected when using driver.send_keys() with an old Chromedriver.)
"""
exc_message = self.__get_exception_message()
maybe_using_old_chromedriver = False
if "unknown error: call function result missing" in exc_message:
maybe_using_old_chromedriver = True
if self.browser == 'chrome' and maybe_using_old_chromedriver:
update = ("Your version of ChromeDriver may be out-of-date! "
"Please go to "
"https://sites.google.com/a/chromium.org/chromedriver/ "
"and download the latest version to your system PATH! "
"Or use: ``seleniumbase install chromedriver`` . "
"Original Exception Message: %s" % exc_message)
exc_message = update
return exc_message
def __add_delayed_assert_failure(self):
""" Add a delayed_assert failure into a list for future processing. """
current_url = self.driver.current_url
message = self.__get_exception_message()
self.__page_check_failures.append(
"CHECK #%s: (%s)\n %s" % (
self.__page_check_count, current_url, message))
def delayed_assert_element(self, selector, by=By.CSS_SELECTOR,
timeout=settings.MINI_TIMEOUT):
""" A non-terminating assertion for an element on a page.
Failures will be saved until the process_delayed_asserts()
method is called from inside a test, likely at the end of it. """
self.__page_check_count += 1
try:
url = self.get_current_url()
if url == self.__last_url_of_delayed_assert:
timeout = 1
else:
self.__last_url_of_delayed_assert = url
except Exception:
pass
try:
self.wait_for_element_visible(selector, by=by, timeout=timeout)
return True
except Exception:
self.__add_delayed_assert_failure()
return False
@decorators.deprecated("Use self.delayed_assert_element() instead!")
def check_assert_element(self, selector, by=By.CSS_SELECTOR,
timeout=settings.MINI_TIMEOUT):
""" DEPRECATED - Use self.delayed_assert_element() instead. """
return self.delayed_assert_element(selector, by=by, timeout=timeout)
def delayed_assert_text(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.MINI_TIMEOUT):
""" A non-terminating assertion for text from an element on a page.
Failures will be saved until the process_delayed_asserts()
method is called from inside a test, likely at the end of it. """
self.__page_check_count += 1
try:
url = self.get_current_url()
if url == self.__last_url_of_delayed_assert:
timeout = 1
else:
self.__last_url_of_delayed_assert = url
except Exception:
pass
try:
self.wait_for_text_visible(text, selector, by=by, timeout=timeout)
return True
except Exception:
self.__add_delayed_assert_failure()
return False
@decorators.deprecated("Use self.delayed_assert_text() instead!")
def check_assert_text(self, text, selector, by=By.CSS_SELECTOR,
timeout=settings.MINI_TIMEOUT):
""" DEPRECATED - Use self.delayed_assert_text() instead. """
return self.delayed_assert_text(text, selector, by=by, timeout=timeout)
def process_delayed_asserts(self, print_only=False):
""" To be used with any test that uses delayed_asserts, which are
non-terminating verifications that only raise exceptions
after this method is called.
This is useful for pages with multiple elements to be checked when
you want to find as many bugs as possible in a single test run
before having all the exceptions get raised simultaneously.
Might be more useful if this method is called after processing all
the delayed asserts on a single html page so that the failure
screenshot matches the location of the delayed asserts.
If "print_only" is set to True, the exception won't get raised. """
if self.__page_check_failures:
exception_output = ''
exception_output += "\n*** DELAYED ASSERTION FAILURES FOR: "
exception_output += "%s\n" % self.id()
all_failing_checks = self.__page_check_failures
self.__page_check_failures = []
for tb in all_failing_checks:
exception_output += "%s\n" % tb
if print_only:
print(exception_output)
else:
raise Exception(exception_output)
@decorators.deprecated("Use self.process_delayed_asserts() instead!")
def process_checks(self, print_only=False):
""" DEPRECATED - Use self.process_delayed_asserts() instead. """
self.process_delayed_asserts(print_only=print_only)
############
def __js_click(self, selector, by=By.CSS_SELECTOR):
""" Clicks an element using pure JS. Does not use jQuery. """
selector, by = self.__recalculate_selector(selector, by)
css_selector = self.convert_to_css_selector(selector, by=by)
css_selector = re.escape(css_selector)
css_selector = self.__escape_quotes_if_needed(css_selector)
script = ("""var simulateClick = function (elem) {
var evt = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
var canceled = !elem.dispatchEvent(evt);
};
var someLink = document.querySelector('%s');
simulateClick(someLink);"""
% css_selector)
self.execute_script(script)
def __jquery_click(self, selector, by=By.CSS_SELECTOR):
""" Clicks an element using jQuery. Different from using pure JS. """
selector, by = self.__recalculate_selector(selector, by)
self.wait_for_element_present(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
if self.is_element_visible(selector, by=by):
self.__demo_mode_highlight_if_active(selector, by)
selector = self.convert_to_css_selector(selector, by=by)
selector = self.__make_css_match_first_element_only(selector)
click_script = """jQuery('%s')[0].click()""" % selector
self.safe_execute_script(click_script)
def __get_href_from_link_text(self, link_text, hard_fail=True):
href = self.get_link_attribute(link_text, "href", hard_fail)
if not href:
return None
if href.startswith('//'):
link = "http:" + href
elif href.startswith('/'):
url = self.driver.current_url
domain_url = self.get_domain_url(url)
link = domain_url + href
else:
link = href
return link
def __click_dropdown_link_text(self, link_text, link_css):
""" When a link may be hidden under a dropdown menu, use this. """
source = self.get_page_source()
soup = BeautifulSoup(source, "html.parser")
drop_down_list = soup.select('[class*=dropdown]')
csstype = link_css.split('[')[1].split('=')[0]
for item in drop_down_list:
if link_text in item.text.split('\n') and csstype in item.decode():
dropdown_css = ""
for css_class in item['class']:
dropdown_css += '.'
dropdown_css += css_class
dropdown_css = item.name + dropdown_css
matching_dropdowns = self.find_visible_elements(dropdown_css)
for dropdown in matching_dropdowns:
# The same class names might be used for multiple dropdowns
try:
page_actions.hover_element_and_click(
self.driver, dropdown, link_text,
click_by=By.LINK_TEXT, timeout=0.1)
return True
except Exception:
pass
return False
def __pick_select_option(self, dropdown_selector, option,
dropdown_by=By.CSS_SELECTOR, option_by="text",
timeout=settings.SMALL_TIMEOUT):
""" Picks an HTML <select> option by specification.
Option specifications are by "text", "index", or "value".
Defaults to "text" if option_by is unspecified or unknown. """
element = self.find_element(
dropdown_selector, by=dropdown_by, timeout=timeout)
self.__demo_mode_highlight_if_active(dropdown_selector, dropdown_by)
pre_action_url = self.driver.current_url
try:
if option_by == "index":
Select(element).select_by_index(option)
elif option_by == "value":
Select(element).select_by_value(option)
else:
Select(element).select_by_visible_text(option)
except (StaleElementReferenceException, ENI_Exception):
self.wait_for_ready_state_complete()
time.sleep(0.05)
element = self.find_element(
dropdown_selector, by=dropdown_by, timeout=timeout)
if option_by == "index":
Select(element).select_by_index(option)
elif option_by == "value":
Select(element).select_by_value(option)
else:
Select(element).select_by_visible_text(option)
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
if self.driver.current_url != pre_action_url:
self.__demo_mode_pause_if_active()
else:
self.__demo_mode_pause_if_active(tiny=True)
def __recalculate_selector(self, selector, by):
# Try to determine the type of selector automatically
if page_utils.is_xpath_selector(selector):
by = By.XPATH
if page_utils.is_link_text_selector(selector):
selector = page_utils.get_link_text_from_selector(selector)
by = By.LINK_TEXT
return (selector, by)
def __make_css_match_first_element_only(self, selector):
# Only get the first match
last_syllable = selector.split(' ')[-1]
if ':' not in last_syllable and ':contains' not in selector:
selector += ':first'
return selector
def __demo_mode_pause_if_active(self, tiny=False):
if self.demo_mode:
if self.demo_sleep:
wait_time = float(self.demo_sleep)
else:
wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT
if not tiny:
time.sleep(wait_time)
else:
time.sleep(wait_time / 3.4)
def __demo_mode_scroll_if_active(self, selector, by):
if self.demo_mode:
self.slow_scroll_to(selector, by=by)
def __demo_mode_highlight_if_active(self, selector, by):
if self.demo_mode:
# Includes self.slow_scroll_to(selector, by=by) by default
self.highlight(selector, by=by)
def __scroll_to_element(self, element):
element_location = element.location['y']
element_location = element_location - 130
if element_location < 0:
element_location = 0
scroll_script = "window.scrollTo(0, %s);" % element_location
# The old jQuery scroll_script required by=By.CSS_SELECTOR
# scroll_script = "jQuery('%s')[0].scrollIntoView()" % selector
try:
self.execute_script(scroll_script)
except WebDriverException:
pass # Older versions of Firefox experienced issues here
self.__demo_mode_pause_if_active(tiny=True)
def __slow_scroll_to_element(self, element):
if self.browser == 'ie':
# IE breaks on slow-scrolling. Do a fast scroll instead.
self.__scroll_to_element(element)
return
scroll_position = self.execute_script("return window.scrollY;")
element_location = element.location['y']
element_location = element_location - 130
if element_location < 0:
element_location = 0
distance = element_location - scroll_position
if distance != 0:
total_steps = int(abs(distance) / 50.0) + 2.0
step_value = float(distance) / total_steps
new_position = scroll_position
for y in range(int(total_steps)):
time.sleep(0.0114)
new_position += step_value
scroll_script = "window.scrollTo(0, %s);" % new_position
self.execute_script(scroll_script)
time.sleep(0.01)
scroll_script = "window.scrollTo(0, %s);" % element_location
self.execute_script(scroll_script)
time.sleep(0.01)
if distance > 430 or distance < -300:
# Add small recovery time for long-distance slow-scrolling
time.sleep(0.162)
def __post_messenger_success_message(self, message, duration=None):
if not duration:
if not self.message_duration:
duration = settings.DEFAULT_MESSAGE_DURATION
else:
duration = self.message_duration
try:
self.set_messenger_theme(theme="future", location="bottom_right")
self.post_message(message, style="success", duration=duration)
time.sleep(duration)
except Exception:
pass
def __post_messenger_error_message(self, message, duration=None):
if not duration:
if not self.message_duration:
duration = settings.DEFAULT_MESSAGE_DURATION
else:
duration = self.message_duration
try:
self.set_messenger_theme(theme="block", location="top_center")
self.post_message(message, style="error", duration=duration)
time.sleep(duration)
except Exception:
pass
def __highlight_with_assert_success(
self, message, selector, by=By.CSS_SELECTOR):
selector, by = self.__recalculate_selector(selector, by)
element = self.find_element(
selector, by=by, timeout=settings.SMALL_TIMEOUT)
try:
selector = self.convert_to_css_selector(selector, by=by)
except Exception:
# Don't highlight if can't convert to CSS_SELECTOR
return
o_bs = '' # original_box_shadow
style = element.get_attribute('style')
if style:
if 'box-shadow: ' in style:
box_start = style.find('box-shadow: ')
box_end = style.find(';', box_start) + 1
original_box_shadow = style[box_start:box_end]
o_bs = original_box_shadow
if ":contains" not in selector and ":first" not in selector:
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
self.__highlight_with_js_2(message, selector, o_bs)
else:
selector = self.__make_css_match_first_element_only(selector)
selector = re.escape(selector)
selector = self.__escape_quotes_if_needed(selector)
try:
self.__highlight_with_jquery_2(message, selector, o_bs)
except Exception:
pass # JQuery probably couldn't load. Skip highlighting.
time.sleep(0.065)
def __highlight_with_js_2(self, message, selector, o_bs):
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 128, 128, 0.5)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(205, 30, 0, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(128, 0, 128, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(50, 50, 128, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
script = ("""document.querySelector('%s').style =
'box-shadow: 0px 0px 6px 6px rgba(50, 205, 50, 1)';"""
% selector)
self.execute_script(script)
time.sleep(0.0181)
self.__post_messenger_success_message(message)
script = ("""document.querySelector('%s').style =
'box-shadow: %s';"""
% (selector, o_bs))
self.execute_script(script)
def __highlight_with_jquery_2(self, message, selector, o_bs):
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 128, 128, 0.5)');""" % selector
self.safe_execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(205, 30, 0, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(128, 0, 128, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(50, 50, 200, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
script = """jQuery('%s').css('box-shadow',
'0px 0px 6px 6px rgba(50, 205, 50, 1)');""" % selector
self.execute_script(script)
time.sleep(0.0181)
self.__post_messenger_success_message(message)
script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
self.execute_script(script)
############
def setUp(self):
"""
Be careful if a subclass of BaseCase overrides setUp()
You'll need to add the following line to the subclass setUp() method:
super(SubClassOfBaseCase, self).setUp()
"""
self.is_pytest = None
try:
# This raises an exception if the test is not coming from pytest
self.is_pytest = pytest.config.option.is_pytest
except Exception:
# Not using pytest (probably nosetests)
self.is_pytest = False
if self.is_pytest:
# pytest-specific code
test_id = "%s.%s.%s" % (self.__class__.__module__,
self.__class__.__name__,
self._testMethodName)
self.environment = pytest.config.option.environment
self.env = self.environment # Add a shortened version
self.with_selenium = pytest.config.option.with_selenium
self.headless = pytest.config.option.headless
self.headless_active = False
self.with_testing_base = pytest.config.option.with_testing_base
self.with_db_reporting = pytest.config.option.with_db_reporting
self.with_s3_logging = pytest.config.option.with_s3_logging
self.with_screen_shots = pytest.config.option.with_screen_shots
self.with_basic_test_info = (
pytest.config.option.with_basic_test_info)
self.with_page_source = pytest.config.option.with_page_source
self.servername = pytest.config.option.servername
self.port = pytest.config.option.port
self.proxy_string = pytest.config.option.proxy_string
self.database_env = pytest.config.option.database_env
self.log_path = pytest.config.option.log_path
self.browser = pytest.config.option.browser
self.data = pytest.config.option.data
self.demo_mode = pytest.config.option.demo_mode
self.demo_sleep = pytest.config.option.demo_sleep
self.highlights = pytest.config.option.highlights
self.message_duration = pytest.config.option.message_duration
self.ad_block_on = pytest.config.option.ad_block_on
self.verify_delay = pytest.config.option.verify_delay
self.timeout_multiplier = pytest.config.option.timeout_multiplier
self.use_grid = False
if self.servername != "localhost":
# Use Selenium Grid (Use --server=127.0.0.1 for localhost Grid)
self.use_grid = True
if self.with_db_reporting:
import getpass
self.execution_guid = str(uuid.uuid4())
self.testcase_guid = None
self.execution_start_time = 0
self.case_start_time = 0
self.application = None
self.testcase_manager = None
self.error_handled = False
self.testcase_manager = TestcaseManager(self.database_env)
#
exec_payload = ExecutionQueryPayload()
exec_payload.execution_start_time = int(time.time() * 1000)
self.execution_start_time = exec_payload.execution_start_time
exec_payload.guid = self.execution_guid
exec_payload.username = getpass.getuser()
self.testcase_manager.insert_execution_data(exec_payload)
#
data_payload = TestcaseDataPayload()
self.testcase_guid = str(uuid.uuid4())
data_payload.guid = self.testcase_guid
data_payload.execution_guid = self.execution_guid
if self.with_selenium:
data_payload.browser = self.browser
else:
data_payload.browser = "N/A"
data_payload.test_address = test_id
application = ApplicationManager.generate_application_string(
self._testMethodName)
data_payload.env = application.split('.')[0]
data_payload.start_time = application.split('.')[1]
data_payload.state = constants.State.NOTRUN
self.testcase_manager.insert_testcase_data(data_payload)
self.case_start_time = int(time.time() * 1000)
if self.headless:
try:
from pyvirtualdisplay import Display
self.display = Display(visible=0, size=(1920, 1200))
self.display.start()
self.headless_active = True
except Exception:
# pyvirtualdisplay might not be necessary anymore because
# Chrome and Firefox now have built-in headless displays
pass
# Launch WebDriver for both Pytest and Nosetests
if not hasattr(self, "browser"):
raise Exception("""SeleniumBase plugins did not load! """
"""Please reinstall using:\n"""
""" >>> "python setup.py develop" <<< """)
self.driver = self.get_new_driver(browser=self.browser,
headless=self.headless,
servername=self.servername,
port=self.port,
proxy=self.proxy_string,
switch_to=True)
self._default_driver = self.driver
def __insert_test_result(self, state, err):
data_payload = TestcaseDataPayload()
data_payload.runtime = int(time.time() * 1000) - self.case_start_time
data_payload.guid = self.testcase_guid
data_payload.execution_guid = self.execution_guid
data_payload.state = state
if err:
import traceback
tb_string = traceback.format_exc()
if "Message: " in tb_string:
data_payload.message = "Message: " + tb_string.split(
"Message: ")[-1]
elif "Exception: " in tb_string:
data_payload.message = tb_string.split("Exception: ")[-1]
elif "Error: " in tb_string:
data_payload.message = tb_string.split("Error: ")[-1]
else:
data_payload.message = "Unknown Error: See Stacktrace"
self.testcase_manager.update_testcase_data(data_payload)
def __add_pytest_html_extra(self):
try:
pytest_html = pytest.config.pluginmanager.getplugin('html')
if self.with_selenium and pytest_html:
driver = self.driver
extra_url = pytest_html.extras.url(driver.current_url)
screenshot = driver.get_screenshot_as_base64()
extra_image = pytest_html.extras.image(screenshot,
name='Screenshot')
self._html_report_extra.append(extra_url)
self._html_report_extra.append(extra_image)
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()
You'll need to add the following line to the subclass's tearDown():
super(SubClassOfBaseCase, self).tearDown()
"""
has_exception = False
if sys.version.startswith('3') and hasattr(self, '_outcome'):
if self._outcome.errors:
has_exception = True
else:
has_exception = sys.exc_info()[1] is not None
if self.__page_check_failures:
print(
"\nWhen using self.delayed_assert_*() methods in your tests, "
"remember to call self.process_delayed_asserts() afterwards. "
"Now calling in tearDown()...\nFailures Detected:")
if not has_exception:
self.process_delayed_asserts()
else:
self.process_delayed_asserts(print_only=True)
self.is_pytest = None
try:
# This raises an exception if the test is not coming from pytest
self.is_pytest = pytest.config.option.is_pytest
except Exception:
# Not using pytest (probably nosetests)
self.is_pytest = False
if self.is_pytest:
# pytest-specific code
test_id = "%s.%s.%s" % (self.__class__.__module__,
self.__class__.__name__,
self._testMethodName)
try:
with_selenium = self.with_selenium
except Exception:
sub_class_name = str(
self.__class__.__bases__[0]).split('.')[-1].split("'")[0]
sub_file_name = str(self.__class__.__bases__[0]).split('.')[-2]
sub_file_name = sub_file_name + ".py"
class_name = str(self.__class__).split('.')[-1].split("'")[0]
file_name = str(self.__class__).split('.')[-2] + ".py"
class_name_used = sub_class_name
file_name_used = sub_file_name
if sub_class_name == "BaseCase":
class_name_used = class_name
file_name_used = file_name
fix_setup = "super(%s, self).setUp()" % class_name_used
fix_teardown = "super(%s, self).tearDown()" % class_name_used
message = ("You're overriding SeleniumBase's BaseCase setUp() "
"method with your own setUp() method, which breaks "
"SeleniumBase. You can fix this by going to your "
"%s class located in your %s file and adding the "
"following line of code AT THE BEGINNING of your "
"setUp() method:\n%s\n\nAlso make sure "
"you have added the following line of code AT THE "
"END of your tearDown() method:\n%s\n"
% (class_name_used, file_name_used,
fix_setup, fix_teardown))
raise Exception(message)
if with_selenium:
# Save a screenshot if logging is on when an exception occurs
if has_exception:
self.__add_pytest_html_extra()
if self.with_testing_base and has_exception:
test_logpath = self.log_path + "/" + test_id
if not os.path.exists(test_logpath):
os.makedirs(test_logpath)
if ((not self.with_screen_shots) and
(not self.with_basic_test_info) and
(not self.with_page_source)):
# Log everything if nothing specified (if testing_base)
log_helper.log_screenshot(test_logpath, self.driver)
log_helper.log_test_failure_data(
self, test_logpath, self.driver, self.browser)
log_helper.log_page_source(test_logpath, self.driver)
else:
if self.with_screen_shots:
log_helper.log_screenshot(
test_logpath, self.driver)
if self.with_basic_test_info:
log_helper.log_test_failure_data(
self, test_logpath, self.driver, self.browser)
if self.with_page_source:
log_helper.log_page_source(
test_logpath, self.driver)
# (Pytest) Finally close all open browser windows
self.__quit_all_drivers()
if self.headless:
if self.headless_active:
self.display.stop()
self.display = None
if self.with_db_reporting:
if has_exception:
self.__insert_test_result(constants.State.ERROR, True)
else:
self.__insert_test_result(constants.State.PASS, False)
runtime = int(time.time() * 1000) - self.execution_start_time
self.testcase_manager.update_execution_data(
self.execution_guid, runtime)
if self.with_s3_logging and has_exception:
""" If enabled, upload logs to S3 during test exceptions. """
from seleniumbase.core.s3_manager import S3LoggingBucket
s3_bucket = S3LoggingBucket()
guid = str(uuid.uuid4().hex)
path = "%s/%s" % (self.log_path, test_id)
uploaded_files = []
for logfile in os.listdir(path):
logfile_name = "%s/%s/%s" % (guid,
test_id,
logfile.split(path)[-1])
s3_bucket.upload_file(logfile_name,
"%s/%s" % (path, logfile))
uploaded_files.append(logfile_name)
s3_bucket.save_uploaded_file_names(uploaded_files)
index_file = s3_bucket.upload_index_file(test_id, guid)
print("\n\n*** Log files uploaded: ***\n%s\n" % index_file)
logging.error(
"\n\n*** Log files uploaded: ***\n%s\n" % index_file)
if self.with_db_reporting:
self.testcase_manager = TestcaseManager(self.database_env)
data_payload = TestcaseDataPayload()
data_payload.guid = self.testcase_guid
data_payload.logURL = index_file
self.testcase_manager.update_testcase_log_url(data_payload)
else:
# (Nosetests)
if has_exception:
test_id = "%s.%s.%s" % (self.__class__.__module__,
self.__class__.__name__,
self._testMethodName)
test_logpath = "latest_logs/" + test_id
if not os.path.exists(test_logpath):
os.makedirs(test_logpath)
log_helper.log_test_failure_data(
self, test_logpath, self.driver, self.browser)
if len(self._drivers_list) > 0:
log_helper.log_screenshot(test_logpath, self.driver)
log_helper.log_page_source(test_logpath, self.driver)
# Finally close all open browser windows
self.__quit_all_drivers()