【问题标题】:Recording Selenium tests for functional Plone test cases记录功能性 Plone 测试用例的 Selenium 测试
【发布时间】:2012-04-24 06:22:21
【问题描述】:

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

  • 测试用例准备 Plone 站点环境,将在其中运行测试(安装插件、模拟邮件主机、创建示例内容)

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

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

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

【问题讨论】:

    标签: selenium plone


    【解决方案1】:

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

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

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

    【讨论】:

    • 好的。我很害怕。从您的功能测试代码中以编程方式驱动 Selenium 并在浏览器中预览每一步(使用 pdb 设置)怎么样?
    • 使用 Python 绑定到 Selenium 非常简单:pypi.python.org/pypi/selenium
    • 我非常了解绑定。我希望在问题的上下文中得到答案,并标有项目符号。
    • 看看 Funittest 项目,它解决了你的第一个和第三个要点。我很久没有更新这个项目了,但我会在接下来的几个月里回复它。 coactivate.org/projects/funittest/project-home
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 2021-07-06
      • 2012-10-09
      • 1970-01-01
      相关资源
      最近更新 更多