Cukes and Apples: Advanced Cucumber Steps

Welcome Back

In the previous post, we implemented the Page Object pattern to drive a simple Cucumber scenario. The steps used in that scenario are expressive enough, but not very reusable and not well-organized. In this post, we will explore some good practices for writing and using Cucumber steps for mobile test automation.

Get the code from the previous post here:

Arrange, Act, Assert

We recommend organizing general, reusable step definitions with a pattern used in unit testing: Arrange-Act-Assert. The Arrange-Act-Assert pattern divides step definitions into three logical groupings, predictably: arrange, act, and assert.

  • The “arrange” section sets up the preconditions necessary for a test to succeed or fail correctly. This will include things like logging in and navigating to pages.
  • The “act” section describes an action for which the result must be validated, like tapping a button or performing a gesture.
  • The “assert” section finishes a test by validating the result of the action which preceded it, by checking conditions like the visibility and value of page elements.

Organizing our step definitions according to the Arrange-Act-Assert pattern makes it easier for new contributors to learn the most reusable steps in the test suite and reminds us of the purpose of these steps as we use them.

Some steps are not easy to place in the Arrange-Act-Assert pattern – for example, a step which validates the display of a page might be used most often during the arrange and act sections of a test, but still constitutes a very good assertion. If you are not sure where to place a step, consider how the step will be used most often, how it will provide the most value, and what first impression a new collaborator should have.

Custom Steps

If the Arrange-Act-Assert pattern is followed too literally, and other categories of a step are prohibited, the benefits of following the pattern are lost. Collections of arrange, act, and assert steps should include steps that are generalized and reusable to make those steps easy to find.

Steps which are too specific for the general collection can be organized separately. For example, a step which handles user login is only applicable to the application under test, and does not describe a general mobile device interaction. Start small by collecting these steps in one place, like “custom_steps.rb”. As the custom steps collection grows in size and becomes unwieldy, identify related steps and create new step files for them.

Select Any Element

In the previous post, we used a step definition that selected a button on the Welcome page: “the user selects Next” or “the user selects Get started”. We could make that step more valuable by making it reusable. If this step could be written to select any element, then it could be used more scenarios.

First, move the step into a file that aligns with the Arrange-Act-Assert pattern: “step_definitions/action_steps.rb”. This makes sense as an action step – many scenarios are likely to validate the result of tapping an element.

Next, update the step pattern to accept any element name.

We will parse the element name and send it to a page object to invoke the method which will select the named element. The element name that is written in our scenarios – and captured by the step – is likely to be mixed-case, and include spaces, so we need to modify it first. See the string modification below, and the “send” method which accepts it:

That “send” method is an incredibly helpful construct that allows us to invoke a method on a page object without knowing the name of that method until runtime – that is, when we begin executing the test.

Because our scenario was written to select both “Next” and “Get started”, we also need to define a separate button named :get_started

Now, the step above is capable of selecting any element on the Welcome page… but there aren’t many elements on that page. This step would be much more valuable if it could also select any element, on any page.

Any Element, Any Page

The step can be further modified to select an element on any page, but there is a catch. See @current_page below:

The @current_page variable will be familiar for users of the web automation gem page-object, which uses the same variable name for the same purpose. We can call the “send” method on an instance variable named @current_page and assume that preceding steps have set the variable, but we must update other steps.

Update the step “the app is on the Welcome page” to set @current_page.

Now the step “the user selects <element_name>” can be used on any page, assuming the preceding step sets @current_page.

The other step, “the app is on the Welcome page”, would be even better if it could be used to describe any page.

Navigate to Any Page

To set @current_page with an instance of any page class, call Kernel.const_get and pass it the name of a page class. As with the element name above, it is necessary to manipulate the string first. Follow the example below to change page names into class names:

Now the step definition “the app is on the <page name> page” can be used to navigate to any page and validate the visibility of that page using the “on_page?” method, assuming “on_page?” is implemented for the named page.

Further Optimizations

Fans of the page-object gem might be looking for the on_page method. Page-object uses a PageFactory module to manage the @current_page, which includes the on_page method used to create new instances of page classes and set the @current_page variable. Our test suite can do the same if we implement a factory method as page-object does.


In this post, we used the Arrange-Act-Assert pattern to organize our steps by category and updated our step definitions to handle any element, on any page. By following the same principles, and leaning on constructs like “@current_screen.send” and “Kernel.const_get”, we can write step definitions that will describe almost any user interaction in a generalized and reusable way.

Get the code from this post here:

Coming Up Next

Updating this test framework to support cross-platform execution will require access to some new hardware. Another post will explore execution with iOS and Android in the future, but for now this series will be on hold while we publish some other articles. Stay tuned!


Published by Joshua Russell

My name is Josh, and I’m a test automation developer in Columbus, Ohio. I’ve developed test automation for applications on several platforms - web applications, Android and iOS apps, mainframe systems, Windows desktop applications, and more - to serve the needs of insurance, banking, and retail.

Leave a Reply

%d bloggers like this: