Stay accessible: Robot Framework library for WAVE Toolbar

  • 2

WAVE Web Accessibility Tool is a popular service for detecting accessibility issues on your websites. WAVE Toolbar is an offline version of the service, distributed as a downloadable, self-installable, Firefox add-on. Both the service and the toolbar are produced and copyrighted by WebAIM a US based non-profit organization, but are usable without cost.

During the last PLOG I was asked by Paul, if it would be possible to automate WAVE Toolbar -powered accessibility checks with Robot Framework (and its Selenium-library). It is.

robotframework-wavelibrary

WAVELibrary is a new Robot Framework library, packaged as robotframework-wavelibrary, to provide keywords for executing WAVE Toolbar accessibility analysis directly withing Robot Framework tests.

Together with the Selenium-library, it allows you to prepare any test situation on your web product (e.g. login and open some form), execute WAVE-analysis and either pass or fail the test according to the results. And those tests can also be integrated with your CI to avoid accidentally introducing new accessibility issues.

WAVELibrary is open source, so if its current features are not enough, thanks to Robot Framework syntax, you can easily contribute and make it better.

(Please, note that you should not solely rely only on WAVE Toolbar for validating your products accessibility, because accessibility should always be verified by a human. Yet, WAVE Toolbar could assist you on detecting possible accessibility issues, WAVELibrary could help in automating that, and once you are accessible, WAVE Toolbar and WAVELibrary together can help you to stay accessible.)

Basic usage

./bootstrap.py

$ curl -O http://downloads.buildout.org/2/bootstrap.py

./buildout.cfg

[buildout]
parts = pybot

[pybot]
recipe = zc.recipe.egg
eggs =
     robotframework
     robotframework-wavelibrary

./example.robot

*** Settings ***

Library  WAVELibrary

Suite setup  Open WAVE browser
Suite teardown  Close all browsers

*** Test Cases ***

Test single page
    [Documentation]  Single page test could interact with the target
    ...              app as much as required and end with triggering
    ...              the accessibility scan.
    Go to  http://www.plone.org/
    Check accessibility errors

Test multiple pages
    [Documentation]  Template based test can, for example, take a list
    ...              of URLs and perform accessibility scan for all
    ...              of them. While regular test would stop for the
    ...              first failure, template based test will just jump
    ...              to the next URL (but all failures will be reported).
    [Template]  Check URL for accessibility errors
    http://www.plone.org/
    http://www.drupal.org/
    http://www.joomla.org/
    http://www.wordpress.org/

See also all the available keywords. (in addition to robot keywords and selenium keywords).

Installing

$ python bootstrap.py
$ bin/buildout

Running

$ bin/pybot example.robot

==============================================================================
Example
==============================================================================
Test single page :: Single page test could interact with the target   | PASS |
------------------------------------------------------------------------------
Test multiple pages :: Template based test can, for example, take ... | FAIL |
Wave reported errors for http://wordpress.org/: ERROR: Form label missing !=
------------------------------------------------------------------------------
Example                                                               | FAIL |
2 critical tests, 1 passed, 1 failed
2 tests total, 1 passed, 1 failed
==============================================================================
Output:  /.../output.xml
Log:     /.../log.html
Report:  /.../report.html

In addition to generated Robot Framework test report and log, there should be WAVE Toolbar -annotated screenshot of each tested page and WAVELibrary also tries to take a cropped screenshot of each visible accessibility error found on the page.

Plone usage

While WAVELibrary has no dependencies on Plone, it's tailored to work well with plone.app.robotframework so that it's easy to run accessibility test against sandboxed test instance.

./bootstrap.py

$ curl -O http://downloads.buildout.org/2/bootstrap.py

./buildout.cfg

[buildout]
extends = http://dist.plone.org/release/4.3-latest/versions.cfg
parts = robot

[robot]
recipe = zc.recipe.egg
eggs =
     plone.app.robotframework
     robotframework-wavelibrary

./plone.robot

*** Settings ***

Library  WAVELibrary

Resource  plone/app/robotframework/server.robot

Suite Setup  Setup
Suite Teardown  Teardown

*** Variables ***

${START_URL}  about:

*** Keywords ***

Setup
    Setup Plone site  plone.app.robotframework.testing.AUTOLOGIN_ROBOT_TESTING
    Import library  Remote  ${PLONE_URL}/RobotRemote
    Enable autologin as  Site Administrator
    Set autologin username  test-user-1

Teardown
    Teardown Plone Site

*** Test Cases ***

Test Plone forms
    [Template]  Check URL for accessibility errors
    ${PLONE_URL}/@@search
    ${PLONE_URL}/folder_contents
    ${PLONE_URL}/@@personal-information
    ${PLONE_URL}/@@personal-preferences

Test new page form tabs
    [Template]  Check new page tabs for accessibility errors
    default
    categorization
    dates
    creators
    settings

*** Keywords ***

Check new page tabs for accessibility errors
    [Arguments]  ${fieldset}
    Go to  ${PLONE_URL}/createObject?type_name=Document
    ${location} =  Get location
    Go to  ${PLONE_URL}
    Go to  ${location}#fieldsetlegend-${fieldset}
    Check accessibility errors

Installing

$ python bootstrap.py
$ bin/buildout

Running

$ bin/pybot plone.robot

One more thing...

What about recording those test runs?

  1. Get VirtualBox and Vagrant.

  2. Get and build my Robot Recorder kit:

    $ git clone git://github.com/datakurre/robotrecorder_vagrant.git
    $ cd robotrecorder_vagrant && vagrant up && cd ..
    
  3. Figure out your computers local IP...

  4. Record the previously described Plone-suite:

    $ ZSERVER_HOST=mycomputerip bin/pybot -v ZOPE_HOST:mycomputerip -v REMOTE_URL:http://localhost:4444/wd/hub plone.robot
    

Recordings are saved at ./robotrecorder_vagrant/recordings.

Test fixture driven development for Plone add-ons?

  • 0

You may have already seen Maurizio Delmonte's demo of collective.cover, the new Tiles based front-page product for Plone. It's based on the same technologies as Deco, but it actually delivers. Just amazing work from our South American Plone friends.

Here's a new (developer) way to try out this wonderful product:

  1. Download bootstrap.py

    $ curl -O http://downloads.buildout.org/2/bootstrap.py
    
  2. Write buildout.cfg

    [buildout]
    extends = http://dist.plone.org/release/4.3-latest/versions.cfg
    versions = versions
    parts = robot
    
    [robot]
    recipe = zc.recipe.egg
    eggs =
        collective.cover[test]
        plone.app.robotframework
    
    [versions]
    # These versions are from collective.cover/versions.cfg
    collective.js.jqueryui = 1.10.1.2
    plone.app.blocks = 1.0
    plone.app.drafts = 1.0a2
    plone.app.jquery = 1.7.2
    plone.app.jquerytools = 1.5.5
    plone.app.tiles = 1.0.1
    plone.tiles = 1.2
    
  3. Run the buildout

    $ python bootstrap.py
    $ bin/buildout
    
  4. Start the installed bin/robot-server with a specific functional test fixture shipped in collective.cover

    $ bin/robot-server collective.cover.testing.FUNCTIONAL_TESTING
    
  5. Wait for bin/robot-server to start a volatile demo instance for you

    12:05:09 [ wait ] Starting Zope 2 server
    12:05:41 [ ready ] Started Zope 2 server
    
  6. Open browser at http://localhost:55001/plone/

  7. Login with username admin and password secret, click Add new ...-menu to add a new cover and have fun.

Wait... what just happened?

Meet the robot-server

robot-server is a test fixture based development server for Plone add-on development, and is currently shipped with plone.app.robotframework. It's main function is to ease writing of Robot Framework tests for Plone, but it can be used as a more general development tool.

About a year ago, Godefroid Chapelle figured out that we could re-use Plone's and its add-ons' modern test fixtures (see plone.app.testing) to start a testable Plone sandbox directly from Robot Framework's own test runner. Even better finding was that you could re-use the test fixtures to start a Plone sandbox in a way that you could ran multiple isolated Robot Framework test against it with running expensive setups and teardowns only once. This work was finished at the Barcelona Plone Testing Sprint in the last February.

To see better, how test fixtures are set up by robot-server, run it with -v for --verbose:

$ bin/robot-server collective.cover.testing.FUNCTIONAL_TESTING -v
12:07:10 [ wait ] Starting Zope 2 server
12:07:10 [ wait ] Set up plone.testing.zca.LayerCleanup
12:07:10 [ wait ] Set up plone.testing.z2.Startup
12:07:10 [ wait ] Set up plone.app.testing.layers.PloneFixture
12:07:18 [ wait ] Set up collective.cover.testing.Fixture
12:07:25 [ wait ] Set up plone.testing.z2.ZServer
12:07:26 [ wait ] Set up collective.cover.testing.collective.cover:Functional
12:07:26 [ ready ] Started Zope 2 server

But that's not all.

robot-server, reloaded

  1. Write the following develop.cfg (on a Mac or Linux) next to the previous buildout.cfg

    [buildout]
    extends = buildout.cfg
    extensions = mr.developer
    auto-checkout = collective.cover
    
    [sources]
    collective.cover = git https://github.com/collective/collective.cover.git
    
    [robot]
    eggs += plone.app.robotframework[reload]
    
  2. Run the buildout

    $ bin/buildout -c develop.cfg
    
  3. Start bin/robot-server

    $ bin/robot-server collective.cover.testing.FUNCTIONAL_TESTING -v
    12:26:25 [ wait ] Starting Zope 2 server
    12:26:25 [ wait ] Set up plone.testing.zca.LayerCleanup
    12:26:25 [ wait ] Set up plone.testing.z2.Startup
    12:26:25 [ wait ] Set up plone.app.testing.layers.PloneFixture
    12:26:33 [ wait ] Watchdog is watching for changes in src
    12:26:33 [ wait ] Fork loop now starting on parent process 98241
    12:26:33 [ wait ] Fork loop forked a new child process 98244
    12:26:33 [ wait ] Set up collective.cover.testing.Fixture
    12:26:40 [ wait ] Set up plone.testing.z2.ZServer
    12:26:41 [ wait ] Set up collective.cover.testing.collective.cover:Functional
    12:26:41 [ ready ] Zope 2 server started
    
  4. Edit a file under src/collective/cover and see how the sandbox is teared down to PLONE_FIXTURE and all code for collective.cover is being reloaded

    12:27:49 [ wait ] Watchdog got modified event on collective.cover/src/collective/cover/config.py
    12:27:49 [ wait ] Pruning Zope 2 server
    12:27:49 [ wait ] Tear down collective.cover.testing.collective.cover:Functional
    12:27:49 [ wait ] Tear down plone.testing.z2.ZServer
    12:27:50 [ wait ] Tear down collective.cover.testing.Fixture
    12:27:50 [ wait ] Fork loop terminated child process 98244
    12:27:50 [ wait ] Fork loop forked a new child process 98536
    12:27:50 [ wait ] Set up collective.cover.testing.Fixture
    12:27:57 [ wait ] Set up plone.testing.z2.ZServer
    12:27:58 [ wait ] Set up collective.cover.testing.collective.cover:Functional
    12:27:58 [ ready ] Zope 2 server started
    

In short, when plone.app.robotframework is required with [reload] it comes with a code reloading fork loop applied from sauna.reload. It is not as fast as the original, but this time it works for all add-ons. And it does not only reload your code, but also re-builds your test fixture. For example, all changes for add-on Generic Setup profile are applied when the profile is configured in test fixture.

So, may be next time, when you start writing a new add-on for Plone, you could start with writing a functional test fixture for it (see plone.app.testing and include a z2.ZSERVER_FIXTURE) and give robot-server a spin.

Issues can be filed at GitHub. Thank you.

Make your Robot tests go Phantom

  • 0

A new version of robotframework-selenium2library has just been released, and it (>= 1.2.0) comes with full support for PhantomJS, the famous headless WebKit browser. Thanks a lot to Jeremy for cutting that release and GhostDriver-project for the actual WebDriver-driver for PhantomJS.

To make the story short:

  1. Download PhantomJS (>= 1.8.0), unpack it and place the phantomjs-binary somehere on your path so that it can be located by the Selenium bindings for Python.
  2. Update your Selenium-packages (for Plone, update version pins and do the buildout). You'll need at least Selenium >= 2.28.0 and robotframework-selenium2library >=1.2.0.
  3. Execute your tests so that the Open Browser-keyword of Selenium2Library will be called with a named argument browser=phantomjs. See also the keyword documentation.

With plone.app.robotframework (tutorial) and a buildout environment, you can do part 3. simply with

$ bin/robot -v BROWSER:phantomjs ...

when you are running tests against bin/robot-server as recommended while writing your tests.

Or

$ ROBOT_BROWSER=phantomjs bin/test -t ...

when you are running test with zope.testrunner as recommended with a CI setup.

Generate annotated screenshots with plone.app.robotframework

  • 0

Greetings from Sorrento, from Plone Open Garden 2013. The symposium/sprint was amazing, with the greatest possible venue, the nicest community out there and my most productive sprint days so far. Thanks a lot to Maurizio Delmonte and Abstract IT folks for organizing the event. Also, thanks to my employer, University of Jyväskylä for allowing and sponsoring me to participate the sprint. I believe, it was worth it...

plone.app.robotframework

During the sprint I released a package called plone.app.robotframework to ease writing of Robot Framework tests for Plone and add-ons. plone.act is dead, long live plone.app.robotframework. It contains everything in plone.act (the same code from the same authors), but also much much more:

  • Common variables and keywords to open Selenium test browsers so that the same tests can be run locally, on CI, with Selenium grid and even with Sauce Labs (with or without Travis-CI) without modifying the tests themselves.
  • Sauce Labs -integration keywords, which allow not only run tests on locally or at Travis-CI with Sauce Labs' browsers, but also send build-numbers, test names, test tags to Sauce Labs.
  • Helper library for logging users automatically into Plone so that we can skip login forms and save a lot of time in tests.
  • Helper library base for implementing database dependent test setup keywords in Python to save even more time in tests.
  • Keywords for annotating open pages and cropping captured images so that Robot Framework test can be used to both make screencasts and illustrate Sphinx documentation (and keep both of them up to date with the current code.

Read the new tutorial about writing Robot Framework tests with plone.app.robotframework at Plone Developer Documentation.

But there's one specific new feature, I'd like to rise above others:

Generating screenshots

We know that this would be possible, but we thought that it would be much harder. Read from Guido's PloneSocial PLOG2013 Sprint Report, how we implemented the base for functional testing with Robot Framework, Selenium, Travis-CI and Sauce Labs for PloneSocial-suite, and wondered about the possibility of creating screencasts with Robot Framework.

One thing led to another, and just a day later I had the initial implementations of screen annotation keywords and screenshot cropping keywords ready.

You can see everything in action with this minimal example:

bootstrap.py

$ curl -O http://downloads.buildout.org/2/bootstrap.py

buildout.cfg

[buildout]
extends = http://dist.plone.org/release/4.3-latest/versions.cfg
parts = pybot

[pybot]
recipe = zc.recipe.egg
eggs =
    Pillow
    plone.app.robotframework
scripts = pybot

docs.robot

*** Settings ***

Resource  plone/app/robotframework/server.robot
Resource  plone/app/robotframework/annotate.robot

Suite Setup  Setup
Suite Teardown  Teardown

*** Keywords ***

Setup
    Setup Plone site  plone.app.robotframework.testing.AUTOLOGIN_ROBOT_TESTING
    Import library  Remote  ${PLONE_URL}/RobotRemote

Teardown
    Teardown Plone Site

*** Test Cases ***

Portal factory add menu

    Enable autologin as  Contributor
    Set autologin username  John Doe
    Go to  ${PLONE_URL}

    Click link  css=#plone-contentmenu-factories dt a
    Element should be visible
    ...    css=#plone-contentmenu-factories dd.actionMenuContent

    ${dot1} =  Add dot
    ...    css=#plone-contentmenu-factories dt a  1

    ${note1} =  Add note
    ...    css=#plone-contentmenu-factories
    ...    At first, click Add new… to open the menu
    ...    width=180  position=left

    ${dot2} =  Add dot
    ...    css=#plone-contentmenu-factories dd.actionMenuContent  2
    ${note2} =  Add note
    ...    css=#plone-contentmenu-factories dd.actionMenuContent
    ...    Then click any option to add new content
    ...    width=180  position=left

    Align elements horizontally  ${dot2}  ${dot1}
    Align elements horizontally  ${note2}  ${note1}

    Capture and crop page screenshot  add-new-menu.png
    ...    contentActionMenus
    ...    css=#plone-contentmenu-factories dd.actionMenuContent
    ...    ${dot1}  ${note1}  ${dot2}  ${note2}

    Remove elements  ${dot1}  ${note1}  ${dot2}  ${note2}

Running

$ python bootstrap.py
$ bin/buildout
$ mkdir -p docs/source/images
$ bin/pybot -d docs/source/images -r NONE -l NONE -o NONE docs.robot

Results

This all should result the next nicely annotated and cropped screenshot as docs/source/images/add-new-menu.png:

How to add new content in Plone

Just think about having that bin/pybot-line in the next Sphinx-makefile...

... and, of course, those annotation keywords will work for creating screencasts too. Actually, those keywords are not even Plone specific, but they do rely on jQuery being found on the captured page.

PS. You may wonder, how pybot can start Plone for capturing screenshots. For that, I have to thank Godefroid Chapell for writing the original robot keywords in plone.app.robotframework for setting up and tearing down test layers without zope.testrunner (see the keywords Setup Plone site and Teardown Plone site).

Cross-browser test your Plone add-on with Robot Framework, Travis-CI and Sauce Labs

  • 2

Thanks to Rok Garbas, I became aware of Sauce Labs during the Plone testing sprint.

Finally, I had some time to try it myself, and I managed to make it work pretty well with Robot Framework and Travis-CI: travis saucelabs

I try to start from the very beginning, but if you already have Robot Framework tests, or even Travis-CI-integration, you could just skip these initial steps.

Bootstrap Templer

Create buildout directory for Templer installation:

$ mkdir templer-buildout
$ cd templer-buildout/

Get bootstrap.py:

$ curl -o http://downloads.buildout.org/2/bootstrap.py

Create buildout.cfg:

[buildout]
parts = templer

[templer]
recipe = zc.recipe.egg
eggs =
    templer.core
    templer.plone

Run bootstrap and buildout to install Templer:

$ python bootstrap.py
$ bin/buildout

Create a new product with Templer

Call the buildout-installed to create a new product with Robot Framework test example:

$ templer-buildout/bin/templer plone_basic example.product

Be careful to answer true for the following question about including Robot test templates:

robot tests (should the default robot test be included) [false]: true

Run buildout:

$ cd example.product
$ python bootstrap.py --distribute
$ bin/buildout

Update Robot Framework tests to be Selenium grid ready

Using Sauce Labs with Robot Framework (Selenium library) is similar to using robot with your own selenium grid. It mainly requires making the browser opening keyword configurable with a few selected variables.

Update src/example/product/tests/robot_test.txt with:

*** Settings ***

Library  Selenium2Library  timeout=10  implicit_wait=0.5

Suite Setup  Start browser
Suite Teardown  Close All Browsers

*** Variables ***

${ZOPE_HOST}  localhost
${ZOPE_PORT}  55001
${ZOPE_URL}  http://${ZOPE_HOST}:${ZOPE_PORT}

${PLONE_SITE_ID}  plone
${PLONE_URL}  ${ZOPE_URL}/${PLONE_SITE_ID}

${BROWSER}  Firefox
${REMOTE_URL}
${DESIRED_CAPABILITIES}  platform:Linux
${BUILD_NUMBER}  manual

*** Test Cases ***

Plone site
    [Tags]  start
    Go to  ${PLONE_URL}
    Page should contain  Plone site

*** Keywords ***

Start browser
    ${BUILD_INFO} =  Set variable
    ...           build:${BUILD_NUMBER},name:${SUITE_NAME} | ${TEST_NAME}
    Open browser  ${PLONE_URL}  ${BROWSER}
    ...           remote_url=${REMOTE_URL}
    ...           desired_capabilities=${DESIRED_CAPABILITIES},${BUILD_INFO}

Let me explain what all those variables are about:

  • ZOPE_HOST should match the host for which ZServer is started during the test setup (ZServer host is configured with ZSERVER_HOST-environment variable. It defaults to localhost.
  • ZOPE_PORT should match the port number which ZServer is started to listen during the test setup (ZServer pot is configured with ZSERVER_PORT-environment variable. It defaults to 55001, but we reconfigure it later by environment variables with one of the ports currently supported by Sauce Labs.
  • ZOPE_URL is a convenience variable for accessing Zope application root.
  • PLONE_SITE_ID is the Plone portal object id (and path name) for the test site. It default to plone, but it can be configured with PLONE_SITE_ID-environment variable. The default should be ok for most cases.
  • PLONE_URL is a convenience variable for accessing the Plone site front-page.
  • BROWSER selects the browser to run the tests with. The supported values depend on Selenium Python-package and can also be read from the documentation of Open browser-keyword in Selenium2Library keywords documentation.
  • REMOTE_URL enables testing with Selenium grid by defining the url of the Selenium hub to use.
  • DESIRED_CAPABILITIES is used to pass various extra parameters for Selenium hub (e.g. the browser version to use or test metadata).
  • BUILD_NUMBER is used to identify the Travis-CI build on Sauce Labs.

When robot tests for Plone are run using bin/test, all the variables above can be overridden by defining corresponding ROBOT_-prefixed environment variable (e.g. ROBOT_REMOTE_URL).

Add Travis-CI configuration with Sauce Labs -support

There are a few steps in adding Travis-CI-support into your product.

At first, create travis.cfg to do the required magic for minimizing buildout-time and setting a few required environment variables. Thanks to the great community, we can just extend a public template:

[buildout]
extends =
    https://raw.github.com/collective/buildout.plonetest/master/travis-4.x.cfg

package-name = example.product
package-extras = [test]

allow-hosts +=
    code.google.com
    robotframework.googlecode.com

[versions]
plone.app.testing = 4.2.2

[environment]
ZSERVER_PORT= 8080
ROBOT_ZOPE_PORT= 8080

[test]
environment = environment

Create .travis.yml for letting Travis-CI to know how the environment should be set up and the tests run:

---
language: python
python: '2.7'
install:
- mkdir -p buildout-cache/downloads
- python bootstrap.py -c travis.cfg
- bin/buildout -N -t 3 -c travis.cfg
- curl -O http://saucelabs.com/downloads/Sauce-Connect-latest.zip
- unzip Sauce-Connect-latest.zip
- java -jar Sauce-Connect.jar $SAUCE_USERNAME $SAUCE_ACCESS_KEY -i $TRAVIS_JOB_ID
  -f CONNECTED &
- JAVA_PID=$!
before_script:
- bash -c "while [ ! -f CONNECTED ]; do sleep 2; done"
script: bin/test
after_script:
- kill $JAVA_PID
env:
  global:
  - ROBOT_BUILD_NUMBER=travis-$TRAVIS_BUILD_NUMBER
  - ROBOT_REMOTE_URL=http://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@ondemand.saucelabs.com:80/wd/hub
  matrix:
  - ROBOT_BROWSER=firefox ROBOT_DESIRED_CAPABILITIES=tunnel-identifier:$TRAVIS_JOB_ID
  - ROBOT_BROWSER=ie ROBOT_DESIRED_CAPABILITIES=tunnel-identifier:$TRAVIS_JOB_ID
  - ROBOT_DESIRED_CAPABILITIES="platform:OS X 10.8,browserName:iPad,version:6,tunnel-identifier:$TRAVIS_JOB_ID"

Let me describe:

  1. Lines 4-7: Run buildout.
  2. Lines 8-14: Download and start Sauce Connect.
  3. Line 15: Run tests.
  4. Lines 16-17: Shutdown Sauce Connect.
  5. Lines 18-21: Define required environment variables for letting robot to use Sauce Labs.
  6. Lines 22-25: Define build matrix for running the tests with Sauce Labs' default Firefox, Internet Explorer and Mobile Safari. tunnel-identifier-stuff is required for Sauce Labs to allow more than one simultaneous tunnels for the same user account.

Next, define your Sauce Labs username and access key as secret, encrypted, environment variables SAUCE_USERNAME and SAUCE_ACCESS_KEY.

Currently, Sauce Labs offers unlimited free subscription with three simultaneous connections (e.g. running tests for three different browsers at the same time) for Open Source projects. Just make sure to register the account for your project, not yourself. Public repository url is required for the creating the account and it cannot be changed afterwards.

  1. Install Travis gem for Ruby (and install Ruby before that when required):

    $ gem install travis  # or sudo gem ...
    
  2. use travis-command to insert encrypted environment variables into the product's .travis.yml:

    $ travis encrypt SAUCE_USERNAME=myusername -r mygithubname/example.product --add env.global
    $ travis encrypt SAUCE_ACCESS_KEY=myaccesskey -r mygithubname/example.product --add env.global
    

Make sure to use your own Sauce Labs username and access key, and your product's Github-repository path (with format username/repo).

Finally, enable Travis-CI-tests for you product either at Travis-CI.org or at GitHub.

Done. If I forgot something, I'll update this post.

Behind the basics: Test level status reporting for Sauce Labs

By default, Sauce Labs doesn't really know did the Selenium tests on it pass or fail. To pass that information from our test runner on Travis-CI to Sauce Labs, we need to add some extra code into our test setup.

At first, append the following into the end of src/example/product/testing.py:

import re
import os
import httplib
import base64
try:
    import json
    assert json  # pyflakes
except ImportError:
    import simplejson as json

from robot.libraries.BuiltIn import BuiltIn

USERNAME_ACCESS_KEY = re.compile('^(http|https):\/\/([^:]+):([^@]+)@')


class Keywords:
    def report_sauce_status(self, status, tags=[], remote_url=''):
        job_id = BuiltIn().get_library_instance(
            'Selenium2Library')._current_browser().session_id

        if USERNAME_ACCESS_KEY.match(remote_url):
            username, access_key =\
                USERNAME_ACCESS_KEY.findall(remote_url)[0][1:]
        else:
            username = os.environ.get('SAUCE_USERNAME')
            access_key = os.environ.get('SAUCE_ACCESS_KEY')

        if not job_id:
            return u"No Sauce job id found. Skipping..."
        elif not username or not access_key:
            return u"No Sauce environment variables found. Skipping..."

        token = base64.encodestring('%s:%s' % (username, access_key))[:-1]
        body = json.dumps({'passed': status == 'PASS',
                           'tags': tags})

        connection = httplib.HTTPConnection('saucelabs.com')
        connection.request('PUT', '/rest/v1/%s/jobs/%s' % (
            username, job_id), body,
            headers={'Authorization': 'Basic %s' % token}
        )
        return connection.getresponse().status

This code defines a custom Robot Framework keyword library with a keyword for passing the test status (and other information) back to Sauce Labs.

Next, we update src/example/product/tests/robot_test.txt to store the session id during the setup of every test and send the test result back to Sauce Labs during the teardown of every test:

*** Settings ***

Library  Selenium2Library  timeout=10  implicit_wait=0.5
Library  example.product.testing.Keywords

Test Setup  Start browser
Test Teardown  Run keywords  Report test status  Close All Browsers

*** Variables ***

${ZOPE_HOST} =  localhost
${ZOPE_PORT} =  55001
${ZOPE_URL} =  http://${ZOPE_HOST}:${ZOPE_PORT}

${PLONE_SITE_ID} =  plone
${PLONE_URL} =  ${ZOPE_URL}/${PLONE_SITE_ID}

${BROWSER} =  Firefox
${REMOTE_URL} =
${DESIRED_CAPABILITIES} =  platform:Linux
${BUILD_NUMBER} =  manual

*** Test Cases ***

Plone site
    [Tags]  start
    Go to  ${PLONE_URL}
    Page should contain  Plone site

*** Keywords ***

Start browser
    ${BUILD_INFO} =  Set variable
    ...           build:${BUILD_NUMBER},name:${SUITE_NAME} | ${TEST_NAME}
    Open browser  ${PLONE_URL}  ${BROWSER}
    ...           remote_url=${REMOTE_URL}
    ...           desired_capabilities=${DESIRED_CAPABILITIES},${BUILD_INFO}

Report test status
    Report sauce status  ${TEST_STATUS}  ${TEST_TAGS}  ${REMOTE_URL}

Please, note how we had to replace suite setup and teardown with test setup and teardown) to open a new Selenium session for every test.

This worked for me. I hope it works for you too.

example.product is available at: https://github.com/datakurre/example.product

Overview of the new Robot Framework goodies for Plone

  • 0

Greetings from Plone testing sprint!

I had never been in Barcelona before, and to be honest, I didn't really see much of the city yet. The sprint, however, was great, fun and well organized. Thank you Ramon, Timo and iskraTIC folks! Also, thanks to my employer, University of Jyväskylä for allowing and sponsoring me to participate the sprint.

In the beginning of the sprint I gave others short introduction on writing functional Selenium test for Plone with Robot Framework. At the end of the sprint, I was already learning new robot tricks from them. Thanks Carles, Kees, Laura and Víctor. Amazing.

Oh, the goodies:

Tutorials

plone.act has now to-the-point tutorials for writing robot tests for a new (templer based) or existing add-on.

They are not reference manuals (yet), but with some good examples they should give you a good start for writing robot tests for Plone.

Templer templates

templer.plone (From version 1.0b4), thanks to Maik Röder, has now a new question for populating the created product with a functional robot test example.

plone.app.testing[robot]

plone.app.testing (from version 4.2.2) can now be required in your setup.py with [robot] extras to require all the needed dependencies for writing and running functional Selenium tests for Plone with robot.

Robot test server

plone.act repository has included a secret development server called act_server. It can start a temporary Plone site with the given testing layer configured. Instead of running tests on top if it itself, however, it just leaves the site on, and allows you to test it with any tools you can imagine (even with a browser).

The server was originally written by Godefroid Chapelle for months ago, but we finally managed to finish it to support test isolation with pybot -- the default test runner installed with Robot Framework.

So, now act_server makes it possible you to run your unfinished tests over and over again with pybot against a real test layer based Plone sandbox, with test isolation, but without needing to wait for the expensive test layer setup and teardown.

It's awesome.

Robot remote library example for Plone

In BDD-style tests it should be possible to prepare tests in Given-clauses, preferably as fast as possible (= without Selenium).

plone.act repository contains now an example on, how to create a robot remote library in Plone site during test layer setup, populate it with keywords written in Python, and then run those keywords like any other keywords in robot tests.

Because robot will call those keywords through XML-RPC, you can e.g. create and delete objects without need to think about transactions or commits by yourself.

That's all

No plone.act release? I'm sorry.

We are not yet sure, if there' really need for a separate package (plone.act), or should the stuff currently in plone.act be moved into existing packages, mainly into plone.testing and plone.app.testing.

plone.act will not disappear, but until we have agreed on where the robot stuff for Plone should live, it may remain only as a development sandbox. Although, you can still checkout it (e.g. with mr.developer), to use it in development and make internal releases out of it.

Of course, also many other things were completed during the sprint, but I'm sure that there will be other reports focusing on them.

Create custom views for Dexterity-types TTW

  • 0

Plone 4.3 will ship with Dexterity, the new content type framework of Plone. One of the many great features in Dexterity is the schema editor, which makes it possible to create new content types completely through-the-web (TTW) -- no coding, no packaging, no buildout, no restarts.

But once you have the new types, you'll need to be able to write custom views for them. Dexterity was supposed to be shipped next to a thing called Deco Layout Editor, but because it's not yet here, there's no official way for defining custom views TTW.

Of course, because Plone adds the current content type name as a class name into HTML body tag, you can apply CSS and Diazo (plone.app.theming) rules for the built-in default view.

With some old friends, however, you can get much further.

Disclaimer: This method has not been tested yet with Plone 4.3, but only with Plone 4.2.x and Dexterity 1.x -series.

Create a new content type

Creating a new Dexterity based content type is almost as easy as it could get. Well, once you have successfully included plone.app.dexterity into your buildout and started started your site with it.

  1. Activate Dexterity Content Types form Add-ons panel under Site Setup.

  2. Click Add New Content Type... on Dexterity Content Types -panel under Site Setup.

  3. Enter required details for the new type.

    http://3.bp.blogspot.com/-fAjf89wmbb0/UQoL9hzs6hI/AAAAAAAAAe4/8h6ZpTb7fFY/s400/add.png
  4. Click Add to create it.

  5. Click your newly created type on Dexterity Content Types -panel.

  6. Add the fields your data requires.

    http://1.bp.blogspot.com/-z4ozwyJg2O4/UQoMNZCbTOI/AAAAAAAAAfE/yWvy3A2m-p8/s400/edit.png

Note, that every new type will be created with Dublin Core -behavior enabled. It means that every type will have the usual Plone metadata fields automatically (including title and description), and you only need to add your custom data fields.

While creating the new type, write down the following technical details:

  • Short Name for your content type (selected during the 3rd step).
  • Short Name for every custom field of your content type (created during the last step).

As soon as you have created a content type, it's addable from the Add new... -menu everywhere in your site.

Define a custom default view

Currently, defining a custom view for your content type (TTW) requires visiting some older parts of Plone:

  1. Enter to ZMI (Zope Management Interface) from Site Setup.

  2. Open a tool called portal_types.

  3. Scroll to the bottom of the displayed content type list.

  4. Open the link with the name of your new content type to open the type information form of your new type.

  5. Locate fields Default view method and Available view methods.

  6. Just below the default value view in Available view methods, enter a new line with a filename-like name of your new custom view, e.g. shortnameofmytype_view.

    It's important that no other type have used the same name before. Usually you are safe by prefixing the view name with the short name of your type.

    (You can remove the default line view later to drop the option to show content with the built-in default view.)

  7. Replace the value of Default view method with the new view name you entered into Available view methods (e.g. shortnameofmytype_view).

    http://2.bp.blogspot.com/-emkSDsUPtjc/UQoMkMgVOnI/AAAAAAAAAfo/Ll_2tZM_C4g/s400/type.png
  8. Save Changes at the bottom of the form.

With these steps you've told Plone to use a custom view of your own as the default view of your content type. But because that view doesn't really exist yet, Plone would raise an error when trying view a content of the new type, until a matching page template has been written.

Write a template for the view

To write a page template to work as you newly defined custom default view for you new content type, you have to re-enter ZMI:

  1. Enter to ZMI (Zope Management Interface) from Site Setup.

  2. Open a tool called portal_skins.

  3. Open a folder named custom.

  4. Select Page Template from the Add-list.

  5. Click Add (only if Add Page Template -form didn't open already).

  6. Enter the name of your view as the id of the new page template (e.g. shortnameofmytype_view).

    http://4.bp.blogspot.com/-nU7jzPp5BCw/UQoMc3CQadI/AAAAAAAAAfc/NWPjUYkE06g/s400/template.png
  7. Click Add and Edit.

Now you should be able to:

  1. Enter a title for your new view (title may be visible for the content editors in content item's Display-menu).

  2. Type in a template for your view:

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
          xmlns:tal="http://xml.zope.org/namespaces/tal"
          xmlns:metal="http://xml.zope.org/namespaces/metal"
          xmlns:i18n="http://xml.zope.org/namespaces/i18n"
          lang="en"
          metal:use-macro="context/main_template/macros/master"
          i18n:domain="plone">
    
    <metal:css fill-slot="style_slot">
    <style type="text/css">
        <!-- Replace this with your views' custom CSS -->
    </style>
    </metal:css>
    
    <metal:javascript fill-slot="javascript_head_slot">
    <script type="text/javascript">
    jQuery(function($) {
        // Replace this with your view's custom onLoad-jQuery-code.
    });
    </script>
    </metal:javascript>
    
    <body>
    
    <metal:content-core fill-slot="content-core">
        <metal:content-core define-macro="content-core"
                            tal:define="widgets nocall:context/@@view">
    
           <!-- Replace this with the HTML layouf of your custom view.
    
           The widgets-variable, which is defined above, gives you access
           to the field widgets of your custom fields through the built-in
           default view included in Dexterity (but only for the fields that
           are visible in the built-in default view, excluding e.g. widgets
           for Dublin Core metadata fields).
    
           It's crucial to use the available widgets for rendering
           RichText-fields, but widgets also do some special formatting for
           numeric fields, at least. In general, it's a good practice to
           use the widget for rendering the field value.
    
           You can render a field widget (e.g. for **Rich Text** -field or
           **File Upload** -field) with the following TAL-syntax:
    
           <div tal:replace="structure widgets/++widget++shortnameofmyfield/render">
                This will be replaced with the rendered content of the field.
           </div>
    
           Widgets for fields of activated behaviors are prefixed with the
           interface of the behavior:
    
           <div tal:replace="structure widgets/++widget++IMyBehavior.fieldname/render">
                This will be replaced with the rendered content of the field.
           </div>
    
           Images are best rendred with plone.app.imaging-tags, like:
    
           <img tal:replace="structure context/@@images/shortnameofmyfield/thumb" />
    
           You can define the available sizes (e.g. **thumb**) in **Site
           Setup**.
    
           Finally, you can always get and render values manually, like
           required for hidden Dublin Core -fields:
    
           <p>Last updated:
           <span tal:define="modification_date context/modification_date"
                 tal:content="python:modification_date.strftime('%Y-%m-%d')">
           YYYY-MM-DD</span></p>
    
           -->
    
        </metal:content-core>
    </metal:content-core>
    
    </body>
    </html>
    

An example of a template

http://3.bp.blogspot.com/-fl7ZW2jMqLg/UQoMUaWhylI/AAAAAAAAAfQ/JAomHSvKbGk/s400/example.png
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      lang="en"
      metal:use-macro="context/main_template/macros/master"
      i18n:domain="plone">

<metal:css fill-slot="style_slot">
<style type="text/css">
.documentDescription { margin-bottom: 1em; }
#content-core img { float: left; margin: 0 1em 1em 0; }
</style>
</metal:css>

<body>

<metal:content-core fill-slot="content-core">
    <metal:content-core define-macro="content-core"
                        tal:define="widgets nocall:context/@@view">

       <img tal:replace="structure context/@@images/portrait/thumb" />

       <div tal:replace="structure widgets/++widget++contact_information/render">
            Contact information.
       </div>

    </metal:content-core>
</metal:content-core>

</body>
</html>

Done, what next?

Workflows
plone.app.workflowmanager provides a sane TTW workflow editor for Plone.
Add forms
collective.pfg.dexterity provides a PloneFormGen-adapter for creating Dexterity-content from the form input (and therefore allowing anonymous users to submit new content).
Referenceability
plone.app.referenceablebehavior provides UID-linking support for Dexterity-types.
Membership management
dexterity.membrane allows Dexterity-types with fields first_name, last_name and email to define new users for a Plone site.
Versioning
plone.app.versioningbehavior provides the familiar content versioning support for Dexterity-types.
Indexing
collective.dexteritytextindexer allows to define, which fields should be indexed into SearchableText-index. Unfortunately, TTW configuration, while being possible, is not trivial and may require an another blog post...

Have fun!