The feature tests in this repository are written using the following gems:
The feature tests are used to test service features and user journeys to avoid repetitive manual testing. They use Given-When-Then syntax so that they are, hopefully understandable (and therefore approvable) by non-technical team members such as analysts, product owners and service owners as well as external stakeholders.
Feature tests are located in the spec/features/
folder and have a _spec.rb
extension. They're written using Given-When-Then syntax and the step definitions
are defined in spec/support/features/steps
. Step definitions are designed to
be reusable so hopefully there will be little need to define your own.
Every page is described by a [Page object] located in the
spec/support/features/pages
folder and have a _page.rb
or _wizard.rb
extension. Page objects provide helpers for navigating from the page,
interacting with form elements on the page, checking the content of the page
and verifying that the expected page has loaded correctly.
Each page object inherits from ::Pages::BasePage
as in this example of the
Privacy Policy page object;
module Pages
class PrivacyPolicyPage < ::Pages::BasePage
set_url "/privacy-policy"
set_primary_heading "Privacy policy"
end
end
A page object needs to include the following at least;
set_url
should be set to the url of the page on loadset_primary_heading
should be set to the text contents of the pagesH1
heading
Navigation methods should use the Capybara action click_on "Link text"
to interact with
clickable text within the page and then should call the static loaded
method of the expected pages object to confirm that the correct page has loaded.
Navigation method names should describe the task to be performed by the navigation and should not specifically try to use the actual text of the navigation link although this may end up being the case for some links;
def view_privacy_policy
click_on "Privacy"
Pages::PrivacyPolicyPage.loaded
end
The full feature scenario suite should prove that a user can achieve any desired tasks and so at least one scenario should navigate the user from the start page to the appropriate page that the task is required to be performed on. If this navigation is already present then it is possible to use the page objects to directly load a specific page, as in;
Pages::CheckAccountPage.load
Again the Primary Heading will be checked once the page has been loaded but as the URL is loaded directly this check is not performed.
Interaction with the page to fill in form fields and click buttons to submit that information to the server are also handled with page object methods named to describe the task.
A page interaction will generally be more than just a single page action such
as in the case of a find
action which will require a value to be entered into
a form and a button to be clicked in order to submit the query to the server.
def find(participant_name)
user_id = User.find_by(full_name: participant_name).id
fill_in "Search records", with: user_id
click_on "Search"
Pages::FinanceParticipantDrilldown.loaded
end
When filling in fields or selecting/choosing field values we should use the appropriate label text rather than the HTML element via CSS selectors as this is what a user would be doing.
Every feature scenario should include a way of proving that the user is able to understand that the task has been performed correctly. The easiest way to check this is to achieve this is to assert that the page is displaying the expect text or information.
Several helper methods are included on the BasePage
class to help describe any
failing expectation better;
element_visible?(element)
element_hidden?(element)
element_has_content?(element, expected_content)
element_without_content?(element, unexpected_content)
These can be used as follows;
def confirm_will_use_dfe_funded_training_provider
element_has_content? self, "Programme Use a training provider funded by the DfE"
end
In this case if the exact content is not found within the page then the scenario will fail with an explanation of the expectations;
expected to find "Programme Use a training provider funded by the DfE" within
===
Manage your training
Induction tutor New SIT
View your early career teacher and mentor details
Lead Provider
Delviery Partner
Programme DfE-accredited materials
report that your school has been confirmed incorrectly
===
Each feature scenario is made up of a series of steps which call the methods of
the page objects to ensure consistent interaction across the suite. Some specific
steps may have to defined within the scenario feature itself and should be set as
private
to ensure they are not accidentally picked up by another scenario.
If a scenario step method is intended to be shared between multiple scenarios
then it should be defined within the spec/support/features/steps
folder in a
file with the extension _steps.rb
.
Hopefully most of the steps within a scenario will be covered by the generic step helpers which are already defined within the steps files.
To use the generic navigation steps you will need to have a page object that describes the page you are currently expected to navigate from. The templates for this sort of step are;
These templated steps will use a specific page object to load its url where page_object
is
constantized. If it includes query_params
then this string is parsed, split
on _and_
and the arguments passed to PageObject#load
so;
is interpreted as;
::Pages::SchoolParticipantDashboardPage.load(participant_id: "ABC-9876")
It is also possible to validate that the correct page is now loaded using two similar templated steps;
In these templated steps a specific page object is used to check the page has
loaded where page_object
is constantized. Agian, if it includes query_params
then this string is parsed, split on _and_
and the arguments passed to
PageObject#loaded
so;
is interpreted as;
::Pages::SchoolParticipantDashboardPage.loaded(participant_id: "1234ZYX")
To use the generic interaction steps you will need to have a page object that describes the page you are currently expected to interact with. Examples of the templates for this sort of step are;
These will call a specific action of a specific page object where method_name
and page_object
are constantized. If it includes query_params
then this
string is parsed, split on _and_
and the arguments passed to
PageObject#method_name
so;
is interpreted as;
::Pages::SchoolParticipantDashboardPage.find_a_particiapnt(participant_name: "The Participant")
Steps should describe the scenario from the point of view of the user - e.g. what they do or what they should see - the language should be in present tense, using active voice and first-person. for example;
given_i_am_on_the_school_dashboard_page
when_i_view_participants_from_the_school_dashboard_page
then_i_am_on_the_school_particiapnt_dashboard_page
Exceptions are for steps that aren't from the point of the user, such as:
and_the_page_is_accessible
The language of steps should be definite rather than speculative, so Then I am on
rather than Then I should be on
.
Most of the time, you won't need to write specific step definitions as the
page objects should encapsulate all the steps needed to achieve a task.
instead of I click on "delete" button
you can write I delete user "The Participant"
and
this will encapsulate all the actions needed to delete the user named
"The Participant" and notify us of any action that fails during this task.
You must consider this when writing new step definitions - make them as reusable as possible so that other people don't have to write similar step definitions in the future.
We use Axe for basic automated accessibility testing. Every time you add a test for a new page (or significant state change to an existing page), add the following lines to your spec:
then_the_page_is_accessible