990 lines
45 KiB
Python
Executable File
990 lines
45 KiB
Python
Executable File
"""
|
|
These methods improve on and expand existing WebDriver commands.
|
|
Improvements include making WebDriver commands more robust and more reliable
|
|
by giving page elements enough time to load before taking action on them.
|
|
"""
|
|
|
|
import getpass
|
|
import json
|
|
import logging
|
|
import os
|
|
import pytest
|
|
import sys
|
|
import time
|
|
import traceback
|
|
import unittest
|
|
import uuid
|
|
from BeautifulSoup import BeautifulSoup
|
|
from pyvirtualdisplay import Display
|
|
from seleniumbase.config import settings
|
|
from seleniumbase.core.application_manager import ApplicationManager
|
|
from seleniumbase.core.s3_manager import S3LoggingBucket
|
|
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 browser_launcher
|
|
from seleniumbase.core import log_helper
|
|
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.webdriver.remote.webdriver import WebDriver
|
|
from selenium.webdriver.common.by import By
|
|
|
|
|
|
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)
|
|
try:
|
|
self.driver = WebDriver()
|
|
except Exception:
|
|
pass
|
|
self.environment = None
|
|
self.page_check_count = 0
|
|
self.page_check_failures = []
|
|
|
|
def open(self, url):
|
|
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 selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
element = page_actions.wait_for_element_visible(
|
|
self.driver, selector, by, timeout=timeout)
|
|
self._demo_mode_highlight_if_active(selector, by)
|
|
pre_action_url = self.driver.current_url
|
|
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_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) """
|
|
for selector in selectors_list:
|
|
self.click(selector, by=by, timeout=timeout)
|
|
if spacing > 0:
|
|
time.sleep(spacing)
|
|
|
|
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.browser == 'phantomjs':
|
|
if self.is_link_text_visible(link_text):
|
|
element = self.wait_for_link_text_visible(link_text)
|
|
element.click()
|
|
return
|
|
source = self.driver.page_source
|
|
soup = BeautifulSoup(source)
|
|
html_links = soup.fetch('a')
|
|
for html_link in html_links:
|
|
if html_link.text == 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 link_text [%s]' % link_text)
|
|
raise Exception("Link text [%s] was not found!" % link_text)
|
|
# Not using phantomjs
|
|
element = self.wait_for_link_text_visible(link_text, timeout=timeout)
|
|
self._demo_mode_highlight_if_active(link_text, by=By.LINK_TEXT)
|
|
pre_action_url = self.driver.current_url
|
|
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):
|
|
element = page_actions.wait_for_element_visible(
|
|
self.driver, selector, by, timeout)
|
|
return element.text
|
|
|
|
def get_attribute(self, selector, attribute, by=By.CSS_SELECTOR,
|
|
timeout=settings.SMALL_TIMEOUT):
|
|
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 add_text(self, selector, new_value, by=By.CSS_SELECTOR,
|
|
timeout=settings.SMALL_TIMEOUT):
|
|
""" The more-reliable version of driver.send_keys()
|
|
Similar to update_text(), but won't clear the text field first. """
|
|
element = self.wait_for_element_visible(
|
|
selector, by=by, timeout=timeout)
|
|
self._demo_mode_highlight_if_active(selector, by)
|
|
pre_action_url = self.driver.current_url
|
|
element.send_keys(new_value)
|
|
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.SMALL_TIMEOUT):
|
|
""" Same as add_text() -> more reliable, but less name confusion. """
|
|
self.add_text(selector, new_value, by=by, timeout=timeout)
|
|
|
|
def update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
|
|
timeout=settings.SMALL_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 jquery if the selenium text update fails
|
|
"""
|
|
element = self.wait_for_element_visible(
|
|
selector, by=by, timeout=timeout)
|
|
self._demo_mode_highlight_if_active(selector, by)
|
|
element.clear()
|
|
self._demo_mode_pause_if_active(tiny=True)
|
|
pre_action_url = self.driver.current_url
|
|
element.send_keys(new_value)
|
|
if (retry and element.get_attribute('value') != new_value and (
|
|
not new_value.endswith('\n'))):
|
|
logging.debug('update_text_value is falling back to jQuery!')
|
|
selector = self.jq_format(selector)
|
|
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.SMALL_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. """
|
|
self.update_text_value(selector, new_value, by=by,
|
|
timeout=timeout, retry=retry)
|
|
|
|
def is_element_present(self, selector, by=By.CSS_SELECTOR):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
return page_actions.is_element_present(self.driver, selector, by)
|
|
|
|
def is_element_visible(self, selector, by=By.CSS_SELECTOR):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
return page_actions.is_element_visible(self.driver, selector, by)
|
|
|
|
def is_link_text_visible(self, link_text):
|
|
return page_actions.is_element_visible(self.driver, link_text,
|
|
by=By.LINK_TEXT)
|
|
|
|
def is_text_visible(self, text, selector, by=By.CSS_SELECTOR):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
return page_actions.is_text_visible(self.driver, text, selector, by)
|
|
|
|
def find_visible_elements(self, selector, by=By.CSS_SELECTOR):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
return page_actions.find_visible_elements(self.driver, selector, by)
|
|
|
|
def execute_script(self, script):
|
|
return self.driver.execute_script(script)
|
|
|
|
def set_window_size(self, width, height):
|
|
return self.driver.set_window_size(width, height)
|
|
self._demo_mode_pause_if_active()
|
|
|
|
def maximize_window(self):
|
|
return self.driver.maximize_window()
|
|
self._demo_mode_pause_if_active()
|
|
|
|
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
|
|
self.execute_script(
|
|
'''var script = document.createElement("script"); '''
|
|
'''script.src = "http://code.jquery.com/jquery-2.2.4.min.js"; '''
|
|
'''document.getElementsByTagName("head")[0]'''
|
|
'''.appendChild(script);''')
|
|
for x in xrange(30):
|
|
# jQuery needs a small amount of time to activate. (At most 3s)
|
|
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("Exception: WebDriver could not activate jQuery!")
|
|
|
|
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)
|
|
"""
|
|
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 for jQuery
|
|
return
|
|
|
|
# Only get the first match
|
|
last_syllable = selector.split(' ')[-1]
|
|
if ':' not in last_syllable:
|
|
selector += ':first'
|
|
|
|
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
|
|
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(128, 128, 128, 0.5)');""" % selector
|
|
try:
|
|
self.execute_script(script)
|
|
except Exception:
|
|
self.activate_jquery()
|
|
self.execute_script(script)
|
|
if self.highlights:
|
|
loops = self.highlights
|
|
loops = int(loops)
|
|
for n in xrange(loops):
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(255, 0, 0, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(128, 0, 128, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(0, 0, 255, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(0, 255, 0, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(128, 128, 0, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
script = """jQuery('%s').css('box-shadow',
|
|
'0px 0px 6px 6px rgba(128, 0, 128, 1)');""" % selector
|
|
self.execute_script(script)
|
|
time.sleep(0.02)
|
|
|
|
script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
|
|
self.execute_script(script)
|
|
time.sleep(0.065)
|
|
|
|
def scroll_to(self, selector, by=By.CSS_SELECTOR,
|
|
timeout=settings.SMALL_TIMEOUT):
|
|
''' Fast scroll to destination '''
|
|
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 '''
|
|
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):
|
|
self.scroll_to(selector, by=by)
|
|
self.click(selector, by=by)
|
|
|
|
def jquery_click(self, selector, by=By.CSS_SELECTOR):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
selector = self.convert_to_css_selector(selector, by=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)
|
|
|
|
# Only get the first match
|
|
last_syllable = selector.split(' ')[-1]
|
|
if ':' not in last_syllable:
|
|
selector += ':first'
|
|
|
|
click_script = """jQuery('%s')[0].click()""" % selector
|
|
try:
|
|
self.execute_script(click_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(click_script)
|
|
self._demo_mode_pause_if_active()
|
|
|
|
def jq_format(self, code):
|
|
return page_utils.jq_format(code)
|
|
|
|
def get_domain_url(self, url):
|
|
return page_utils.get_domain_url(url)
|
|
|
|
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. """
|
|
if not destination_folder:
|
|
destination_folder = constants.Files.DOWNLOADS_FOLDER
|
|
page_utils._download_file_to(file_url, destination_folder)
|
|
return True
|
|
|
|
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)
|
|
return True
|
|
|
|
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 actions. """
|
|
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.SMALL_TIMEOUT):
|
|
""" This method uses jQuery to update a text field. """
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
selector = self.convert_to_css_selector(selector, by=by)
|
|
self._demo_mode_highlight_if_active(selector, by)
|
|
self.scroll_to(selector, by=by, timeout=timeout)
|
|
value = json.dumps(new_value)
|
|
|
|
# Only get the first match
|
|
last_syllable = selector.split(' ')[-1]
|
|
if ':' not in last_syllable:
|
|
selector += ':first'
|
|
|
|
set_value_script = """jQuery('%s').val(%s)""" % (selector, value)
|
|
try:
|
|
self.execute_script(set_value_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(set_value_script)
|
|
self._demo_mode_pause_if_active()
|
|
|
|
def jquery_update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
|
|
timeout=settings.SMALL_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 selector.startswith('/') or selector.startswith('./'):
|
|
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)
|
|
|
|
# Only get the first match
|
|
last_syllable = selector.split(' ')[-1]
|
|
if ':' not in last_syllable:
|
|
selector += ':first'
|
|
|
|
update_text_script = """jQuery('%s').val('%s')""" % (
|
|
selector, self.jq_format(new_value))
|
|
try:
|
|
self.execute_script(update_text_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(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.SMALL_TIMEOUT):
|
|
""" The shorter version of jquery_update_text_value()
|
|
(The longer version remains for backwards compatibility.) """
|
|
self.jquery_update_text_value(
|
|
selector, new_value, by=by, timeout=timeout)
|
|
|
|
def hover_on_element(self, selector, by=By.CSS_SELECTOR):
|
|
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 hover_selector.startswith('/') or hover_selector.startswith('./'):
|
|
hover_by = By.XPATH
|
|
if click_selector.startswith('/') or click_selector.startswith('./'):
|
|
click_by = By.XPATH
|
|
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 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 selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
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. """
|
|
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 selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
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() """
|
|
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 """
|
|
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. """
|
|
self.wait_for_element_visible(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_text_visible(self, text, selector, by=By.CSS_SELECTOR,
|
|
timeout=settings.LARGE_TIMEOUT):
|
|
if selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
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() """
|
|
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 """
|
|
return self.wait_for_text_visible(
|
|
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(), but returns nothing.
|
|
As above, will raise an exception if nothing can be found.
|
|
Returns True if successful. Default timeout = SMALL_TIMEOUT. """
|
|
self.wait_for_text_visible(text, 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_link_text_visible(self, link_text,
|
|
timeout=settings.LARGE_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() """
|
|
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 """
|
|
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. """
|
|
self.wait_for_link_text_visible(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 selector.startswith('/') or selector.startswith('./'):
|
|
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. """
|
|
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 selector.startswith('/') or selector.startswith('./'):
|
|
by = By.XPATH
|
|
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. """
|
|
self.wait_for_element_not_visible(selector, by=by, timeout=timeout)
|
|
return True
|
|
|
|
############
|
|
|
|
def wait_for_ready_state_complete(self, timeout=settings.EXTREME_TIMEOUT):
|
|
return page_actions.wait_for_ready_state_complete(self.driver, timeout)
|
|
|
|
def wait_for_and_accept_alert(self, timeout=settings.LARGE_TIMEOUT):
|
|
return page_actions.wait_for_and_accept_alert(self.driver, timeout)
|
|
|
|
def wait_for_and_dismiss_alert(self, timeout=settings.LARGE_TIMEOUT):
|
|
return page_actions.wait_for_and_dismiss_alert(self.driver, timeout)
|
|
|
|
def wait_for_and_switch_to_alert(self, timeout=settings.LARGE_TIMEOUT):
|
|
return page_actions.wait_for_and_switch_to_alert(self.driver, timeout)
|
|
|
|
def save_screenshot(self, name, folder=None):
|
|
return page_actions.save_screenshot(self.driver, name, folder)
|
|
|
|
############
|
|
|
|
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 _package_check(self):
|
|
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 check_assert_element(self, selector, by=By.CSS_SELECTOR,
|
|
timeout=settings.MINI_TIMEOUT):
|
|
""" A non-terminating assertion for an element on a page.
|
|
Any and all exceptions will be saved until the process_checks()
|
|
method is called from inside a test, likely at the end of it. """
|
|
self.page_check_count += 1
|
|
try:
|
|
self.wait_for_element_visible(selector, by=by, timeout=timeout)
|
|
return True
|
|
except Exception:
|
|
self._package_check()
|
|
return False
|
|
|
|
def check_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.
|
|
Any and all exceptions will be saved until the process_checks()
|
|
method is called from inside a test, likely at the end of it. """
|
|
self.page_check_count += 1
|
|
try:
|
|
self.wait_for_text_visible(text, selector, by=by, timeout=timeout)
|
|
return True
|
|
except Exception:
|
|
self._package_check()
|
|
return False
|
|
|
|
def process_checks(self):
|
|
""" To be used at the end of any test that uses checks, which are
|
|
non-terminating verifications that will only raise an exception
|
|
after this method is called. Useful for pages with multiple
|
|
elements to be checked when you want to find as many failures
|
|
as possible on a page before making fixes.
|
|
Might be more useful if this method is called after processing
|
|
all the checks for a single html page, otherwise the screenshot
|
|
in the logs file won't match the location of the checks. """
|
|
if self.page_check_failures:
|
|
exception_output = ''
|
|
exception_output += "\n*** FAILED CHECKS FOR: %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
|
|
raise Exception(exception_output)
|
|
|
|
############
|
|
|
|
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
|
|
self.execute_script(scroll_script)
|
|
self._demo_mode_pause_if_active(tiny=True)
|
|
|
|
def _slow_scroll_to_element(self, element):
|
|
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 xrange(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)
|
|
|
|
|
|
# PyTest-Specific Code #
|
|
|
|
def setUp(self):
|
|
"""
|
|
pytest-specific code
|
|
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:
|
|
test_id = "%s.%s.%s" % (self.__class__.__module__,
|
|
self.__class__.__name__,
|
|
self._testMethodName)
|
|
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.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
|
|
if self.with_db_reporting:
|
|
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.testcaseAddress = 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:
|
|
self.display = Display(visible=0, size=(1200, 800))
|
|
self.display.start()
|
|
self.headless_active = True
|
|
if self.with_selenium:
|
|
self.driver = browser_launcher.get_driver(self.browser)
|
|
|
|
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:
|
|
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 tearDown(self):
|
|
"""
|
|
pytest-specific code
|
|
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()
|
|
"""
|
|
if self.page_check_failures:
|
|
# self.process_checks() was not called after checks were made.
|
|
# We will log those now here, but without raising an exception.
|
|
exception_output = ''
|
|
exception_output += "\n*** FAILED CHECKS FOR: %s\n" % self.id()
|
|
for tb in self.page_check_failures:
|
|
exception_output += "%s\n" % tb
|
|
logging.exception(exception_output)
|
|
if self.is_pytest:
|
|
test_id = "%s.%s.%s" % (self.__class__.__module__,
|
|
self.__class__.__name__,
|
|
self._testMethodName)
|
|
if self.with_selenium:
|
|
# Save a screenshot if logging is on when an exception occurs
|
|
if self.with_testing_base and (sys.exc_info()[1] is not None):
|
|
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(
|
|
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(
|
|
test_logpath, self.driver, self.browser)
|
|
if self.with_page_source:
|
|
log_helper.log_page_source(
|
|
test_logpath, self.driver)
|
|
# Finally close the browser
|
|
self.driver.quit()
|
|
if self.headless:
|
|
if self.headless_active:
|
|
self.display.stop()
|
|
if self.with_db_reporting:
|
|
if sys.exc_info()[1] is not None:
|
|
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 (sys.exc_info()[1] is not None):
|
|
""" After each testcase, upload logs to the S3 bucket. """
|
|
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)
|