Mâché (pronounced "mash-ay") is a tool that helps you to write cleaner and more expressive acceptance tests for your Ruby web applications using page objects.
A page object is a data structure that provides an interface to your web application for the purposes of test automation. For example, it could represent a single HTML page, or perhaps even a fragment of HTML on a page.
From Martin Fowler:
A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.
Capybara can get us part of the way there. It allows us to work with an API rather than manipulating the HTML directly, but what it provides isn't an application specific API. It gives us low-level API methods like find, fill_in, and click_button, but it doesn't provide us with high-level methods to do things like "sign in to the app" or "click the Dashboard item in the navigation bar".
This is where page objects come in. Using Mâché, we can define a page object class called SignInPage and use it any time we want to automate authenticating with our app. It could handle visiting the sign in page, entering the user's credentials, and clicking the "Sign in" button.
Let's dive straight in and take a look at an example. Consider the following HTML fragment for the welcome page in our app:
<html><body><header><h1>Welcome</h1><divid="flash" class="notice">lorem ipsum</div></header><nav><ul><li><ahref="/foo" class="selected">foo</a></li><li><ahref="/bar">bar</a></li><li><ahref="/baz">baz</a></li></ul></nav><main> lorem ipsum </main></body></html>To define a WelcomePage page object class to wrap this HTML page, we extend the Mache::Page class. The only method our class needs to provide is path, this tells Mâché where to go when we want to visit the page:
require"mache"classWelcomePage < Mache::Pagedefpath"/welcome"endendWe can visit our welcome page using our page object:
page=WelcomePage.visitpage.current?# trueMâché also handily exposes the Capybara API on our page object:
page.find("main").text# "lorem ipsum"To make our page object more useful, we can define an element on our page object class using the element macro. An element is simply an HTML element that we expect to find on the page using a CSS selector.
Let's define a main element to represent the main section of our HTML page:
require"mache"classWelcomePage < Mache::Pageelement:main,"main"defpath"/welcome"endendWe can query the main element as an attribute of our page object:
page.main.text# "lorem ipsum"For elements that can be shared across any number of page object classes it may be useful to define a reusable component by extending the Mache::Node class. A component can contain any number of elements (or even other components).
Let's define a Header component to represent the header of our HTML page:
require"mache"classHeader < Mache::Nodeelement:title,"h1"endWe can mount the Header component in our page object class at a given CSS selector using the component macro:
require"mache"classWelcomePage < Mache::Pagecomponent:header,Header,"header"element:main,"main"defpath"/welcome"endendQuerying a component of our page object is much the same as with an element:
page.header.title.text# "Welcome"Mâché provides helpers for testing Rails apps.
The Flash helper provides methods for testing flash messages. First define a flash in your page object class:
require"mache"require"mache/helpers/rails"classWelcomePage < Mache::PageincludeMache::Helpers::Rails::Flashflash"#flash"endThen you can query the flash on your page object:
page.has_message?(:success,"lorem ipsum")page.has_message?(:success,/lorem ipsum/)There are even convenience matchers for the common types of flash messages:
page.has_success_message?("lorem ipsum")page.has_notice_message?("lorem ipsum")page.has_alert_message?("lorem ipsum")page.has_error_message?("lorem ipsum")The Routes helper mixes the Rails URL helpers into your page object class. This allows you to use the *_path and *_url methods as you normally would in your Rails.
require"mache"require"mache/helpers/rails"classWelcomePage < Mache::PageincludeMache::Helpers::Rails::Routesdefpathwelcome_pathendendLet's look at an example of an acceptance test for our WelcomePage. Note that the Header, NavItem, and Nav components can be reused in any other page object classes we may define later for our web application.
require"mache"require"mache/helpers/rails"classHeader < Mache::Nodeelement:title,"h1"endclassNavItem < Mache::Nodedefselected?node[:class].include?("selected")endendclassNav < Mache::Nodecomponents:items,NavItem,"a"defselected_itemitems.find(&:selected?)endendclassWelcomePage < Mache::PageincludeMache::Helpers::Rails::FlashincludeMache::Helpers::Rails::Routescomponent:header,Header,"header"component:nav,Nav,"nav"element:main,"main"flash"#flash"defpathwelcome_pathendendfeature"Welcome page"dolet(:home_page){WelcomePage.visit}scenario"A user visits the welcome page"doexpect(home_page).tobe_current# headerexpect(home_page).tohave_headerexpect(home_page.header.title).toeq("Welcome")# navexpect(home_page).tohave_navexpect(home_page.nav).tohave_itemsexpect(home_page.nav.items.count).tobe(3)expect(home_page.nav.items[0].text).toeq("foo")expect(home_page.nav.items[1].text).toeq("bar")expect(home_page.nav.items[2].text).toeq("baz")expect(home_page.nav.selected_item).toeq("foo")# mainexpect(home_page.main.text).toeq("lorem ipsum")# flashexpect(home_page).tohave_flashexpect(home_page).tohave_notice_message("lorem ipsum")endendRead the API reference on RubyDoc.
Mâché is licensed under the MIT License.