Skip to content

Instantly share code, notes, and snippets.

@miohtama
Created May 30, 2012 21:29
Show Gist options
  • Select an option

  • Save miohtama/2839086 to your computer and use it in GitHub Desktop.

Select an option

Save miohtama/2839086 to your computer and use it in GitHub Desktop.

Revisions

  1. miohtama revised this gist Jun 1, 2012. 1 changed file with 146 additions and 9 deletions.
    155 changes: 146 additions & 9 deletions gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -18,12 +18,55 @@
    * http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webelement.py
    You can do pdb debugging using ``selenium_helper.selenium_error_trapper()`` if you run
    tests with ``SELENIUM_DEBUG`` turned on::
    SELENIUM_DEBUG=true bin/test -s testspackage -t test_usermenu
    Then you'll get debug prompt on any Selenium error.
    """

    import os

    # Try use ipdb debugger if we have one
    try:
    import ipdb as pdb
    except ImportError:
    import pdb

    from selenium.webdriver.common.by import By
    from selenium.common.exceptions import NoSuchElementException
    from selenium.common.exceptions import WebDriverException
    from selenium.webdriver.support.wait import WebDriverWait

    from plone.app.testing import selenium_layers

    SELENIUM_DEBUG = "SELENIUM_DEBUG" in os.environ


    class SeleniumTrapper(object):
    """
    With statement for break on Selenium errors to ipdb if it has been enabled for this test run.
    """

    def __init__(self, driver):
    self.driver = driver

    def __enter__(self):
    pass

    def __exit__(self, type, value, traceback):
    """
    http://effbot.org/zone/python-with-statement.htm
    """
    if isinstance(value, WebDriverException) and SELENIUM_DEBUG:
    # This was Selenium exception
    print "Selenium API call failed because of browser state error: %s" % value
    print "Selenium instance has been bound to self.driver"
    pdb.set_trace()


    class SeleniumHelper(object):
    """
    @@ -37,35 +80,69 @@ class SeleniumHelper(object):
    * https://github.com/plone/plone.app.testing/blob/master/plone/app/testing/selenium_layers.py
    """

    def __init__(self, testcase):
    def __init__(self, testcase, login_ok_method=None):
    """
    :param testcase: Yout test class instance
    :param login_ok_method: Selenium check function to run to see if login success login_ok_method(selenium_helper)
    """
    self.testcase = testcase
    self.driver = testcase.layer['selenium']
    self.portal = testcase.layer["portal"]

    def login(self, username, password):
    def selenium_error_trapper(self):
    """
    Create ``with`` statement context helper which will invoke Python ipdb debugger if Selenium fails to do some action.
    If you run test with SELENIUM_DEBUG env var set you'll get dropped into a debugger on error.
    """
    return SeleniumTrapper(self.driver)

    def reset(self):
    """
    Perform login using Selenium test browser.
    Reset Selenium test browser between tests.
    """
    selenium_layers.login(self.driver, self.portal, username, password)

    def login(self, username, password, timeout=15, poll=0.5, login_cookie_name="__ac"):
    """
    Perform Plone login using Selenium test browser and Plone's /login_form page.
    """

    submit_button_css = '#login_form input[name=submit]'

    with self.selenium_error_trapper():
    submit_button = self.open(self.portal.absolute_url() + '/login_form', wait_until_visible=submit_button_css)

    self.find_element(By.CSS_SELECTOR, 'input#__ac_name').send_keys(username)
    self.find_element(By.CSS_SELECTOR, 'input#__ac_password').send_keys(password)

    submit_button.click()

    # Check that we get Plone login cookie before the timeout
    waitress = WebDriverWait(self.driver, timeout, poll)
    matcher = lambda driver: driver.get_cookie(login_cookie_name) not in ["", None]
    waitress.until(matcher, "After login did not get login cookie named %s" % login_cookie_name)

    def logout(self):
    """
    Perform logout using Selenium test browser.
    """
    selenium_layers.logout(self.driver)

    def get_plone_page_title(self):
    def get_plone_page_heading(self):
    """
    Get Plone main <h1> contents as lowercase.
    XXX: Looks like Selenium API returns uppercase if there is text-transform: uppercase?
    :return: Empty string if there is no title on the page (convenience for string matching)
    """
    title_elem = self.driver.find_element_by_class_name("documentFirstHeading")

    try:
    title_elem = self.driver.find_element_by_class_name("documentFirstHeading")
    except NoSuchElementException:
    return ""

    if not title_elem:
    return ""

    @@ -85,6 +162,10 @@ def trap_error_log(self, orignal_page=None):
    error_log = self.portal.error_log
    entries = error_log.getLogEntries()

    if len(entries) == 0:
    # No errors, yay!
    return

    msg = ""

    if orignal_page:
    @@ -102,7 +183,7 @@ def is_error_page(self):
    """
    Check that if the current page is Plone error page.
    """
    return "but there seems to be an error" in self.get_plone_page_title()
    return "but there seems to be an error" in self.get_plone_page_heading()

    def is_unauthorized_page(self):
    """
    @@ -120,20 +201,51 @@ def is_not_found_page(self):
    """
    Check if we got 404
    """
    return "this page does not seem to exist" in self.get_plone_page_title()
    return "this page does not seem to exist" in self.get_plone_page_heading()

    def find_element(self, by, target):
    """
    Call Selenium find_element() API and break on not found and such errors if running tests in SELENIUM_DEBUG mode.
    """
    with self.selenium_error_trapper():
    return self.driver.find_element(by, target)

    def find_elements(self, by, target):
    """
    Call Selenium find_elements() API and break on not found and such errors if running tests in SELENIUM_DEBUG mode.
    """
    with self.selenium_error_trapper():
    return self.driver.find_elements(by, target)

    def click(self, by, target):
    """
    Click an element.
    :param by: selenium.webdriver.common.by.By contstant
    :param target: CSS selector or such
    """
    with self.selenium_error_trapper():
    elem = self.driver.find_element(by, target)
    elem.click()

    def open(self, url, check_error_log=True, check_sorry_error=True, check_unauthorized=True, check_not_found=True):
    def open(self, url, wait_until_visible=None, check_error_log=True, check_sorry_error=True, check_unauthorized=True, check_not_found=True):
    """
    Open an URL in Selenium browser.
    If url does not start with http:// assume it is a site root relative URL.
    :param wait_until_visible: CSS selector which must match before we proceed
    :param check_error_log: If the page has created anything in Plone error log then dump this traceback out.
    :param check_sorry_error: Assert on Plone error response page
    :param check_unauthorized: Assert on Plone Unauthorized page (login dialog)
    :return: Element queried by wait_until_visible or None
    """
    elem = None

    # Convert to abs URL
    if not url.startswith("http://"):
    @@ -144,6 +256,11 @@ def open(self, url, check_error_log=True, check_sorry_error=True, check_unauthor
    if check_error_log:
    self.trap_error_log(url)

    if wait_until_visible:
    elem = self.wait_until_visible(By.CSS_SELECTOR, wait_until_visible)

    # XXX: These should be waited also

    if check_sorry_error:
    self.testcase.assertFalse(self.is_error_page(), "Got Plone error page for url: %s" % url)

    @@ -152,3 +269,23 @@ def open(self, url, check_error_log=True, check_sorry_error=True, check_unauthor

    if check_not_found:
    self.testcase.assertFalse(self.is_not_found_page(), "Got Plone not found page for url: %s" % url)

    return elem

    def wait_until_visible(self, by, target, message=None, timeout=10, poll=0.5):
    """
    Wait until some element is visible on the page (assume DOM is ready by then).
    Wraps selenium.webdriver.support.wait() API.
    http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver_support/selenium.webdriver.support.wait.html#module-selenium.webdriver.support.wait
    """

    if not message:
    message = "Waiting for element: %s" % target

    waitress = WebDriverWait(self.driver, timeout, poll)
    matcher = lambda driver: driver.find_element(by, target)
    waitress.until(matcher, message)
    elem = self.driver.find_element(by, target)
    return elem
  2. miohtama created this gist May 30, 2012.
    154 changes: 154 additions & 0 deletions gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,154 @@
    """
    Some PSE 2012 Selenium notes
    * https://github.com/plone/plone.seleniumtesting
    * https://github.com/emanlove/pse2012
    Selenium WebDriver API
    * http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webdriver.py
    Selenium element match options
    * http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/common/by.py
    Selenium element API (after find_xxx())
    * http://code.google.com/p/selenium/source/browse/trunk/py/selenium/webdriver/remote/webelement.py
    """

    from selenium.webdriver.common.by import By

    from plone.app.testing import selenium_layers


    class SeleniumHelper(object):
    """
    Selenium convenience methods for Plone.
    Command Selenium browser to do common actions.
    This mainly curries and delegates to plone.app.testing.selenium_layers helper methods.
    More info:
    * https://github.com/plone/plone.app.testing/blob/master/plone/app/testing/selenium_layers.py
    """

    def __init__(self, testcase):
    """
    :param testcase: Yout test class instance
    """
    self.testcase = testcase
    self.driver = testcase.layer['selenium']
    self.portal = testcase.layer["portal"]

    def login(self, username, password):
    """
    Perform login using Selenium test browser.
    """
    selenium_layers.login(self.driver, self.portal, username, password)

    def logout(self):
    """
    Perform logout using Selenium test browser.
    """
    selenium_layers.logout(self.driver)

    def get_plone_page_title(self):
    """
    Get Plone main <h1> contents as lowercase.
    XXX: Looks like Selenium API returns uppercase if there is text-transform: uppercase?
    :return: Empty string if there is no title on the page (convenience for string matching)
    """
    title_elem = self.driver.find_element_by_class_name("documentFirstHeading")
    if not title_elem:
    return ""

    return title_elem.text.lower()

    def trap_error_log(self, orignal_page=None):
    """
    Read error from the site error log and dump it to output.
    Makes debugging Selenium tests much more fun when you directly see
    the actual errors instead of OHO.
    :param orignal_page: Decorate the traceback with URL we tried to access.
    """

    # http://svn.zope.org/Zope/trunk/src/Products/SiteErrorLog/SiteErrorLog.py?rev=96315&view=auto
    error_log = self.portal.error_log
    entries = error_log.getLogEntries()

    msg = ""

    if orignal_page:
    msg += "Plone logged an error when accessing page %s\n" % orignal_page

    # We can only fail on traceback
    if len(entries) >= 2:
    msg += "Several exceptions were logged.\n"

    entry = entries[0]

    raise AssertionError(msg + entry["tb_text"])

    def is_error_page(self):
    """
    Check that if the current page is Plone error page.
    """
    return "but there seems to be an error" in self.get_plone_page_title()

    def is_unauthorized_page(self):
    """
    Check that the page is not unauthorized page.
    ..note ::
    We cannot distingush login from unauthorized
    """

    # require_login <-- auth redirect final target
    return "/require_login/" in self.driver.current_url

    def is_not_found_page(self):
    """
    Check if we got 404
    """
    return "this page does not seem to exist" in self.get_plone_page_title()

    def open(self, url, check_error_log=True, check_sorry_error=True, check_unauthorized=True, check_not_found=True):
    """
    Open an URL in Selenium browser.
    If url does not start with http:// assume it is a site root relative URL.
    :param check_error_log: If the page has created anything in Plone error log then dump this traceback out.
    :param check_sorry_error: Assert on Plone error response page
    :param check_unauthorized: Assert on Plone Unauthorized page (login dialog)
    """

    # Convert to abs URL
    if not url.startswith("http://"):
    url = self.portal.absolute_url() + url

    selenium_layers.open(self.driver, url)

    if check_error_log:
    self.trap_error_log(url)

    if check_sorry_error:
    self.testcase.assertFalse(self.is_error_page(), "Got Plone error page for url: %s" % url)

    if check_unauthorized:
    self.testcase.assertFalse(self.is_unauthorized_page(), "Got Plone Unauthorized page for url: %s" % url)

    if check_not_found:
    self.testcase.assertFalse(self.is_not_found_page(), "Got Plone not found page for url: %s" % url)