Capybara

Capybara helps you test web applications by simulating how a real user would interact with your app. It is agnostic about the driver running your tests and comes with Werkzeug and Selenium support built in.

Need help? Ask on the mailing list (please do not open an issue on GitHub): http://groups.google.com/group/capybara-py/.

Note: Firefox 48+ If you’re using Firefox with selenium and want full functionality stay on either Firefox 45.0esr or 47.0.1. If using selenium 3.0+ this will require configuring your driver with the "marionette": False capability as shown below:

import capybara

@capybara.register_driver("selenium")
def init_selenium_driver(app):
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

    from capybara.selenium.driver import Driver

    capabilities = DesiredCapabilities.FIREFOX.copy()
    capabilities["marionette"] = False

    return Driver(app, browser="firefox", desired_capabilities=capabilities)

Using Firefox 48+ requires geckodriver and selenium v3, the combo of which currently has multiple issues and is feature incomplete.

Key benefits

  • No setup necessary for WSGI-compliant applications. Works out of the box.
  • Intuitive API which mimics the language an actual user would use.
  • Switch the backend your tests run against from fast headless mode to an actual browser with no changes to your tests.
  • Powerful synchronization features mean you never have to manually wait for asynchronous processes to complete.

Setup

To install, add capybara-py to your application’s test requirements.

In your test setup, set capybara.app to your WSGI-compliant app:

import app
import capybara

capybara.app = app

Using Capybara with pytest

Load pytest support by adding it to the pytest_plugins in your conftest.py:

pytest_plugins = ["capybara.pytest_plugin"]

The plugin provides a page fixture for use in your tests:

def test_user_signs_in(page):
    page.visit("/")
    page.click_link("Sign in")

    page.fill_in("Username", value="user@example.com")
    page.fill_in("Password", value="password")
    page.click_button("Sign in")

Use the js mark to switch to the capybara.javascript_driver ("selenium" by default), or use the driver mark to switch to one specific driver. For example:

import pytest

@pytest.mark.js
def test_uses_the_default_js_driver():
    # ...

@pytest.mark.driver("selenium")
def test_switches_to_one_specific_driver():
    # ...

Using Capybara with unittest

Define a base test case that exposes the page session proxy and cleans up between tests:

import capybara
import capybara.dsl
import unittest

class CapybaraTestCase(unittest.TestCase):
    def setUp(self):
        self.page = capybara.dsl.page

    def tearDown(self):
        capybara.reset_sessions()

(Remember to call super in any subclasses that override setUp or tearDown!)

Drivers

Capybara uses the same DSL to drive a variety of browser and headless drivers.

Selecting the Driver

By default, Capybara uses the "werkzeug" driver, which is fast but limited: it does not support JavaScript, nor is it able to access HTTP resources outside of your WSGI application, such as remote APIs and OAuth services. To get around these limitations, you can set up a different default driver for your features. For example if you’d prefer to run everything in Selenium, you could do:

import capybara

capybara.default_driver = "selenium"

You can also change the driver temporarily (typically in the setup and teardown functions):

import capybara

capybara.current_driver = "selenium" # temporarily select different driver
# tests here
capybara.use_default_driver()        # switch back to default driver

Werkzeug

Werkzeug is Capybara’s default driver. It is written in pure Python and does not have any support for executing JavaScript. Since the Werkzeug driver interacts directly with WSGI interfaces, it does not require a server to be started. However, this means that if your application is not a WSGI application (Django, Flask, and most other Python frameworks are WSGI applications) then you cannot use this driver. Furthermore, you cannot use the Werkzeug driver to test a remote application, or to access remote URLs (e.g., redirects to external sites, external APIs, or OAuth services) that your application might interact with.

Selenium

At the moment, Capybara supports Selenium 2.0 (Webdriver), not Selenium RC. In order to use Selenium, you’ll need to install the selenium package. Provided Firefox is installed, everything is set up for you, and you should be able to start using Selenium right away.

The DSL

Navigating

You can use the visit method to navigate to other pages:

visit("/projects")

The visit method only takes a single parameter, the request method is always GET.

You can get the current path of the browsing session, and test it using the has_current_path matcher:

assert page.has_current_path("/posts/1/comments/2")

Note: You can also assert the current path by testing the value of current_path directly. However, using the has_current_path matcher is safer since it uses Capybara’s waiting behavior to ensure that preceding actions (such as a click_link) have completed.

Clicking links and buttons

Full reference: capybara.node.actions.ActionsMixin

You can interact with the webapp by following links.

click_link("id-of-link")
click_link("Link Text")
click_button("Save")
click_on("Link Text")  # clicks on either links or buttons
click_on("Button Value")

Interacting with forms

Full reference: capybara.node.actions.ActionsMixin

There are a number of tools for interacting with form elements:

fill_in("First Name", value="John")
fill_in("Password", value="Seekrit")
fill_in("Description", value="Really Long Text...")
choose("A Radio Button")
check("A Checkbox")
uncheck("A Checkbox")
attach_file("Image", "/path/to/image.jpg")
select("Option", field="Select Box")

Querying

Full reference: capybara.node.matchers.MatchersMixin

Capybara has a rich set of options for querying the page for the existence of certain elements, and working with and manipulating those elements.

page.has_selector("table tr")
page.has_selector("xpath", "//table/tr")

page.has_xpath("//table/tr")
page.has_css("table tr.foo")
page.has_text("foo")

Finding

Full reference: capybara.node.finders.FindersMixin

You can also find specific elements, in order to manipulate them:

find_field("First Name").value
find_button("Send").click()

find("xpath", "//table/tr").click()
find("#overlay").find("h1").click()

Note: find will wait for an element to appear on the page, as explained in the Ajax section. If the element does not appear it will raise an error.

These elements all have all the Capybara DSL methods available, so you can restrict them to specific parts of the page:

find("#navigation").click_link("Home")

Scoping

Capybara makes it possible to restrict certain actions, such as clicking links, to within a specific area of the page. For this purpose you can use the generic scope context manager. Optionally you can specify which kind of selector to use.

with scope("li#employee"):
    click_link("Jimmy")

with scope("xpath", "//li[@id='employee']"):
    click_link("Jimmy")

Working with windows

Capybara provides some methods to ease finding and switching windows:

facebook_window = window_opened_by(
    lambda: click_button("Like"))
with window(facebook_window):
    find("#login_email").set("a@example.com")
    find("#login_password").set("qwerty")
    click_button("Submit")

Scripting

In drivers which support it, you can easily execute JavaScript:

page.execute_script("$('body').empty()")

For simple expressions, you can return the result of the script. Note that this may break with more complicated expressions:

result = page.evaluate_script("4 + 4")

Modals

In drivers which support it, you can accept, dismiss and respond to alerts, confirms and prompts.

You can accept or dismiss alert messages by wrapping the code that produces the alert in a context manager:

with accept_alert():
    click_link("Show Alert")

You can accept or dismiss a confirmation by wrapping it in a context manager, as well:

with dismiss_confirm():
    click_link("Show Confirm")

You can accept or dismiss prompts as well, and also provide text to fill in for the response:

with accept_prompt(response="Linus Torvalds"):
    click_link("Show Prompt About Linux")

Debugging

It can be useful to take a snapshot of the page as it currently is and take a look at it:

save_page("output.html")

You can also retrieve the current state of the DOM as a string using page.html.

print(page.html)

This is mostly useful for debugging. You should avoid testing against the contents of page.html and use the more expressive finder methods instead.

Finally, in drivers that support it, you can save a screenshot:

save_screenshot("screenshot.png")

Matching

It is possible to customize how Capybara finds elements. At your disposal are two options, capybara.exact and capybara.match.

Exactness

capybara.exact and the exact option work together with the is_ expression inside the XPath package. When exact is true, all is_ expressions match exactly; when it is false, they allow substring matches. Many of the selectors built into Capybara use the is_ expression. This way you can specify whether you want to allow substring matches or not. capybara.exact is false by default.

For example:

click_link("Password")  # also matches "Password confirmation"
capybara.exact = True
click_link("Password")  # does not match "Password confirmation"
click_link("Password", exact=False)  # can be overridden

Strategy

Using capybara.match and the equivalent match option, you can control how Capybara behaves when multiple elements all match a query. There are currently four different strategies built into Capybara:

  1. first: Just picks the first element that matches.
  2. one: Raises an error if more than one element matches.
  3. smart: If exact is True, raises an error if more than one element matches, just like one. If exact is False, it will first try to find an exact match. An error is raised if more than one element is found. If no element is found, a new search is performed which allows partial matches. If that search returns multiple matches, an error is raised.
  4. prefer_exact: If multiple matches are found, some of which are exact, and some of which are not, then the first exactly matching element is returned.

The default for capybara.match is "smart".

Transactions and database setup

Some Capybara drivers need to run against an actual HTTP server. Capybara takes care of this and starts one for you in the same process as your test, but on another thread. Selenium is one of those drivers, whereas Werkzeug is not.

If you are using a SQL database, it is common to run every test in a transaction, which is rolled back at the end of the test. Django’s TestCase does this by default out of the box, for example. Since transactions are usually not shared across threads, this will cause data you have put into the database in your test code to be invisible to Capybara.

Django provides TransactionTestCase, which uses truncation instead of transactions, i.e., it empties out the entire database after each test.

Asynchronous JavaScript (Ajax and friends)

When working with asynchronous JavaScript, you might come across situations where you are attempting to interact with an element which is not yet present on the page. Capybara automatically deals with this by waiting for elements to appear on the page.

When issuing instructions to the DSL such as:

click_link("foo")
click_link("bar")
assert page.has_text("baz")

If clicking on the foo link triggers an asynchronous process, such as an Ajax request, which, when complete will add the bar link to the page, clicking on the bar link would be expected to fail, since that link doesn’t exist yet. However Capybara is smart enough to retry finding the link for a brief period of time before giving up and throwing an error. The same is true of the next line, which looks for the content baz on the page; it will retry looking for that content for a brief time. You can adjust how long this period is (the default is 2 seconds):

import capybara

capybara.default_max_wait_time = 5

Be aware that because of this behavior, the follow two statements are not equivalent, and you should always use the latter!

not page.has_xpath("a")
page.has_no_xpath("a")

The former would immediately fail because the content has not yet been removed. Only the latter would wait for the asynchronous process to remove the content from the page.

Capybara’s waiting behavior is quite advanced, and can deal with situations such as the following line of code:

assert find("#sidebar").find("h1").has_text("Something")

Even if JavaScript causes #sidebar to disappear off the page, Capybara will automatically reload it and any elements it contains. So if an AJAX request causes the contents of #sidebar to change, which would update the text of the h1 to “Something”, and this happened, this test would pass. If you do not want this behavior, you can set capybara.automatic_reload to False.

Using sessions

Capybara manages named sessions (“default” if not specified) allowing multiple sessions using the same driver and test app instance to be interacted with. A new session will be created using the current driver if a session with the given name using the current driver and test app instance is not found.

Named sessions

To perform operations in a different session and then revert to the previous session:

import capybara

with capybara.using_session("Bob's session"):
     # do something in Bob's browser session
# reverts to previous session

To permanently switch the current session to a different session:

import capybara

capybara.session_name = "some other session"

Using sessions manually

For ultimate control, you can instantiate and use a Session manually.

from capybara.session import Session

session = Session("selenium", my_wsgi_app)
with session.scope("//form[@id='session']"):
    session.fill_in("Email", value="email@example.com")
    session.fill_in("Password", value="password")
session.click_button("Sign in")

Using the DSL elsewhere

You can access the page session proxy from anywhere by importing it:

from capybara.dsl import page

# ...

with page.scope("//form[@id='session']"):
    page.fill_in("Email", value="user@example.com")
    page.fill_in("Password", value="password")
page.click_button("Sign in")

You can mix the DSL methods into any class by inheriting from DSLMixin:

from capybara.dsl import DSLMixin

class MyClass(DSLMixin):
    def login(self):
        with self.scope("//form[@id='session']"):
            self.fill_in("Email", value="user@example.com")
            self.fill_in("Password", value="password")
        self.click_button("Sign in")

You can also mix the DSL methods into any module by importing all of capybara.dsl:

from capybara.dsl import *

def main():
    with scope("//form[@id='session']"):
        fill_in("Email", value="user@example.com")
        fill_in("Password", value="password")
    click_button("Sign in")

if __name__ == "__main__":
    main()

This enables its use in unsupported testing frameworks, and for general-purpose scripting.

Calling remote servers

Normally Capybara expects to be testing an in-process WSGI application, but you can also use it to talk to a web server running anywhere on the internet, by setting capybara.app_host:

capybara.app_host = "http://www.google.com"
# ...
visit("/")

With drivers that support it, you can also visit any URL directly:

visit("http://www.google.com")

XPath, CSS and selectors

Capybara does not try to guess what kind of selector you are going to give it, and will always use CSS by default. If you want to use XPath, you’ll need to do:

with scope("xpath", "//ul/li"):
    # ...
find("xpath", "//ul/li").text

Alternatively you can set the default selector to XPath:

import capybara

capybara.default_selector = "xpath"

find("//ul/li").text

Capybara allows you to add custom selectors, which can be very useful if you find yourself using the same kinds of selectors very often:

from capybara.selector import add_selector
from xpath import dsl as x

with add_selector("id") as s:
    s.xpath = lambda id: x.descendant[x.attr("id") == str(id)]

with add_selector("row") as s:
    s.xpath = lambda num: ".//tbody/tr[{}]".format(num)

with add_selector("flash_type") as s:
    s.css = lambda flash_type: "#flash.{}".format(flash_type)

The block given to xpath must always return an XPath expression as a string, or an XPath expression generated through the xpath-py package. You can now use these selectors like this:

find("id", "post_123")
find("row", 3)
find("flash_type", "notice")

Beware the XPath // trap

In XPath the expression // means something very specific, and it might not be what you think. Contrary to common belief, // means “anywhere in the document” not “anywhere in the current context”. As an example:

page.find("xpath", "//body").find_all("xpath", "//script")

You might expect this to find all script tags in the body, but actually, it finds all script tags anywhere in the entire document, not only in the body! What you’re looking for is the .// expression which means “any descendant of the current node”:

page.find("xpath", "//body").find_all("xpath", ".//script")

The same thing goes for scope:

with scope("xpath", "//body"):
    page.find("xpath", ".//script")
    with scope("xpath", ".//table/tbody"):
        # ...

Configuring and adding drivers

Capybara makes it convenient to switch between different drivers. It also exposes an API to tweak those drivers with whatever settings you want, or to add your own drivers. This is how to override the Selenium driver configuration to use Chrome:

import capybara

@capybara.register_driver("selenium")
def init_selenium_driver(app):
    from capybara.selenium.driver import Driver

    return Driver(app, browser="chrome")

However, it’s also possible to give this configuration a different name.

@capybara.register_driver("selenium_chrome")
def init_selenium_chrome_driver(app):
    from capybara.selenium.driver import Driver

    return Driver(app, browser="chrome")

Then tests can switch between using different browsers effortlessly:

capybara.current_driver = "selenium_chrome"

Whatever is returned from the initialization function should conform to the API described by capybara.driver.base.Base, it does not however have to inherit from this class.

The Selenium wiki has additional info about how the underlying driver can be configured.

Gotchas:

  • Server errors will only be raised in the session that initiates the server thread. If you are testing for specific server errors and using multiple sessions make sure to test for the errors using the initial session (usually "default").

Indices and tables

Fork me on GitHub