# -*- coding: utf-8 -*-
"""
ghostly.ghostly
~~~~~~~~~~~~~~~
Lightweight wrapper and helpers around Selenium Webdriver.
"""
from __future__ import absolute_import, print_function, unicode_literals
import time
import six
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException, \
StaleElementReferenceException
from selenium.webdriver import ActionChains
from selenium.webdriver.remote.webelement import WebElement
from .errors import DriverDoesNotExistError, GhostlyTestFailed, \
GhostlyTimeoutError
[docs]class Ghostly:
"""
Lightweight wrapper and helper utilities around Selenium webdriver.
"""
def __init__(self, driver, maximise_window=True):
"""
:param driver: String name of driver, it's expected that it's an attribute
on webdriver. IE. 'Chrome' or 'Firefox' are valid.
"""
try:
self.driver = getattr(webdriver, driver)()
""":type : webdriver.Chrome"""
except AttributeError as e:
raise DriverDoesNotExistError("Driver '%s' does not exist." % driver)
if maximise_window:
self.driver.maximize_window()
[docs] def end(self):
self.driver.quit()
[docs] def get(self, url):
"""
Load the provided URL in the web driver
"""
return self.driver.get(url)
[docs] def xpath_click(self, xpath, wait=0.1, move_to=True):
"""
Click an element selected using xpath.
:param xpath: The xpath locator of the element to be clicked or an
WebElement
:param wait: Wait after the click - set to None for no wait.
:param move_to: If True (default) then an ActionChains is created and
move_to_element called - this approach works well for
elements that respond to clicks such as a/span/div tags.
If False, click is called on the element - this approach
works well for choosing items in a select tag.
"""
if isinstance(xpath, WebElement):
element = xpath
else:
element = self.xpath(xpath)
if move_to:
ActionChains(self.driver)\
.move_to_element(element)\
.click()\
.perform()
else:
element.click()
if wait is not None:
self.wait(wait)
return element
[docs] def xpath_wait(self, xpath, visible=True, timeout=5, sleep=0.25,
click=False, click_move_to=True, click_wait=0.1):
"""
Wait for timeout seconds for xpath to exist and optionally be visible.
:param xpath: The xpath locator of the element to find.
:param visible: If True, also wait for the element to become visible.
:param timeout: Timeout in seconds before GhostlyTimeoutError is raised.
:param sleep: How long to sleep for between each check to see if
:param click: If True, then :py:meth:`.Ghostly.xpath_click` is called
upon completion.
:param click_move_to: Passed onto :py:meth:`.Ghostly.xpath_click` if click
is True.
:param click_wait: Passed onto :py:meth:`.Ghostly.xpath_click` if click
is True.
:return: selenium.webdriver.remote.webelement.WebElement
"""
if not visible and click:
raise NotImplementedError("An element must be visible in order to "
"click it.")
start = current = time.time()
stop = start + timeout
attempts = 0
# Initially wait till the element can be found
while time.time() < stop:
attempts += 1
# We haven't yet found the element
try:
# Attempt to select the element.
element = self.xpath(xpath)
break
except NoSuchElementException:
# The element isn't available yet, so wait.
self.wait(sleep)
else:
raise GhostlyTimeoutError(
"Could not select xpath '%s' within %s seconds - attempted %s "
"times." % (xpath, timeout, attempts)
)
if not visible:
return element
# Wait till it's visible
while time.time() < stop:
attempts += 1
try:
if element.is_enabled() and element.is_displayed():
break
else:
# The element isn't displayed, wait
self.wait(sleep)
except StaleElementReferenceException:
self.wait(sleep)
else:
raise GhostlyTimeoutError(
"Element selected via xpath '%s' but is not yet visible within %s seconds - attempted %s "
"times." % (xpath, timeout, attempts)
)
if not click:
return element
return self.xpath_click(element, wait=click_wait, move_to=click_move_to)
[docs] def xpath(self, xpath):
"""
Finds an element by xpath.
This simply passes through to
:py:class:`.WebDriver.find_element_by_xpath`.
:param xpath: The xpath locator of the element to find.
:return: selenium.webdriver.remote.webelement.WebElement
"""
return self.driver.find_element_by_xpath(xpath)
[docs] def wait(self, seconds):
"""
Wait for a specified number of seconds
"""
if type(seconds) == str:
seconds = int(seconds)
time.sleep(seconds)