5

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

  • 测试用例准备将运行测试的 Plone 站点环境(安装附加组件,模拟邮件主机,创建示例内容)

  • 如何将 Plone 功能测试用例运行到可以在浏览器中启动 Selenium 录制以及如何打开启用录制的浏览器?

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

是否有其他与 Plone 结合的测试记录框架?能够针对 Javascripted UI 进行测试是一项要求。

4

2 回答 2

1

我的猜测是,有些工具可以独立执行各个步骤,但这些工具并不能完全按照您的描述一起工作。

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

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

于 2012-04-24T08:58:08.140 回答
0

plone.app.testing 自 4.1 以来就带有 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上)

于 2012-07-02T23:14:59.607 回答