5

Ploneアドオンに対する簡単な機能テストを作成するためにSeleniumを使用したいと思います。ここでの主な推進要因は、プログラマー以外の人がWebブラウザーで何が起こっているかを確認できるため、ある程度の努力でテストケースを作成して理解できることです。推奨されるベストプラクティスは何ですか

  • テストケースは、テストが実行されるPloneサイト環境を準備します(アドオンのインストール、メールホストのモックアップ、サンプルコンテンツの作成)

  • ブラウザでSeleniumの記録を開始できるようになるまでPlone機能テストケースを実行する方法と、記録を有効にしてブラウザを開く方法は?

  • Pythonコードから記録されたテスト出力を後でどのように実行しますか?

Ploneと組み合わせる他のテストレコーディングフレームワークはありますか?JavascriptedUIに対してテストできることが要件です。

4

2 に答える 2

1

私の推測では、個々のステップを個別に実行するツールがあると思いますが、それらのツールは、あなたが説明したとおりには連携しません。

私の経験では、記録されたテストの品質は非常に悪いため、いずれにせよプログラマーはテストを書き直さなければなりません。JavaScript がたくさんある場合にのみ悪化します。さらに、AJAX を使用するサイトがある場合、発生する問題の 1 つは、次のクリックを行う前に、特定の要素が表示されるのを待たなければならないことがあるということです。これは、ほとんどのレコーダーが失敗する場所です。

また、エンドユーザーを対象として、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 に答える