记录功能Plone测试用例的Selenium测试

时间:2012-04-24 06:22:21

标签: selenium plone

我想使用Selenium为Plone附加组件创建简单的功能测试。这里的主要驱动因素是非程序员可以通过一些努力来创建和理解测试用例,因为他们在Web浏览器中看到了正在发生的事情。

建议的最佳做法是什么?
  • 测试用例准备运行测试的Plone站点环境(安装附加组件,模拟邮件主机,创建示例内容)

  • 如何运行Plone功能测试用例,以便在浏览器中启动Selenium录制以及如何在启用录制的情况下打开浏览器?

  • 以后如何运行Python代码中记录的测试输出?

还有其他与Plone结合的测试记录框架吗?能够对Javascripted UI进行测试是必需的。

2 个答案:

答案 0 :(得分:1)

我的猜测是,有些工具可以独立完成各个步骤,但这些工具并不像您描述的那样完全一致。

根据我的经验,记录测试的质量非常糟糕,程序员无论如何都必须重写它们。只有拥有大量JavaScript才会变得更糟。如果此外,您有一个使用AJAX的站点,出现的问题之一是您有时必须等待特定元素出现,然后再进行下一次单击,这是大多数录像机失败的地方。

我也很想听到一个针对最终用户的工具,并允许他们自己记录和运行Plone测试,如果有人知道这类项目,我真的很想参与它的发展。

答案 1 :(得分:0)

自4.1以来,plone.app.testing附带seleniumtestlayer。

这是我自己更复杂的帮助代码:

“”“

    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

    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):
    """
    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, driver=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
        if driver:
            # Use specific Selenium WebDriver instance
            self.driver = driver
        else:
            # plone.app.tesrting selenium layer
            self.driver = testcase.layer['selenium']
        self.portal = testcase.layer["portal"]

    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):
        """
        Reset Selenium test browser between tests.
        """

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

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

        if not login_url:
            # Default Plone login URL
            login_url = self.portal.absolute_url() + '/login_form'

        with self.selenium_error_trapper():
            submit_button = self.open(login_url, 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, logout_url=None):
        """
        Perform logout using Selenium test browser.

        :param logout_url: For non-default Plone logout view
        """

        if not logout_url:
            logout_url = self.portal.absolute_url() + "/logout"

        self.open(logout_url)

    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)
        """

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

        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()

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

        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_heading()

    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_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, wait_until_visible=None):
        """
        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://") or url.startswith("https://")):
            url = self.portal.absolute_url() + url

        selenium_layers.open(self.driver, url)

        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)

        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)

        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

(尚未在github上)