Skip to content

Commit

Permalink
Add helper fill_in_govuk_text_field (#12)
Browse files Browse the repository at this point in the history
This is aimed to be a drop-in replacement for the standard `fill_in`
helper, but with some additional checks and features.

The helper will:

* make sure you've specified the full field label text, rather than
usual a partial match or an ID reference
* check the label is associated with the field using the `for="x"`
matching an `id="x"`
* check that the referenced ID is unique on the page
* check that any `aria-describedby` IDs match IDs on the page

Additionally, you can optionally specify the hint text:

```ruby
  fill_in_govuk_text_field("What is the name of the event?", 
   hint: "The name you’ll use on promotional material", 
   with: "Design System Day"
  )
```

...and if you do, it'll check that that the hint is correctly associated
with the field. I've not made this required though, so if there's a hint
in the HTML but it's not specified within the test, then it'll still
pass (as some teams might consider specifying the hint text in tests to
be overkill?)
  • Loading branch information
frankieroberto authored Sep 13, 2023
1 parent e4fc0f6 commit 03dd196
Show file tree
Hide file tree
Showing 7 changed files with 516 additions and 3 deletions.
45 changes: 45 additions & 0 deletions docs/fill_in_govuk_text_field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Fill in a text field
layout: sub-navigation
order: 3
---

This helper is a drop-in replacement for the standard `fill_in` helper, which adds some additional usability and accessibility checks.

Use this within tests that navigate between multiple pages.

Here’s a simple example:

```ruby
scenario "Filling in an event name" do
visit "/"

fill_in_govuk_text_field("Event name", with: "Design System Day")
click_govuk_button("Continue")

expect(page).to have_content("Design System Day")
end
```

The helper will check that:

* there is a label with the given text
* a text field is correctly associated with that label using the `for` attribute

## Hints

If a text field has a hint, you can check that this is correctly associated with the field.

```ruby
scenario "Filling in an event name" do
visit "/"

fill_in_govuk_text_field("What is the name of the event?",
hint: "The name you’ll use on promotional material",
with: "Design System Day"
)
click_govuk_button("Continue")

expect(page).to have_content("Design System Day")
end
```
3 changes: 2 additions & 1 deletion docs/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ description: A set of helpers to make it easier to test GOV.UK services using th
startButton:
href: /get-started
content: |
## Navigating
## Navigating and filling in forms

* [Clicking links](click-govuk-link)
* [Filling in text fields](fill_in_govuk_text_field)

## Expecting content

Expand Down
2 changes: 1 addition & 1 deletion docs/summarise-errors.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Error summaries
layout: sub-navigation
order: 3
order: 4
---

Use this helper to test that the [Error summary component](https://design-system.service.gov.uk/components/error-summary/) is visible on the page with the correct content.
Expand Down
2 changes: 1 addition & 1 deletion docs/summarise.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Summary lists
layout: sub-navigation
order: 4
order: 5
---

Use this helper to test that the [Summary list component](https://design-system.service.gov.uk/components/summary-list/) is visible on the page with the correct content, for example on a Check your answers page.
Expand Down
150 changes: 150 additions & 0 deletions lib/fill_in_govuk_text_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
module GovukRSpecHelpers
class FillInGovUKTextField

attr_reader :page, :label, :hint, :with

def initialize(page:, label:, hint:, with:)
@page = page
@label = label
@hint = hint
@with = with
end

def fill_in
labels = page.all('label', text: label, exact_text: true, normalize_ws: true)

if labels.size == 0
check_for_inexact_label_match
check_for_field_name_match

raise "Unable to find label with the text #{label}"
end

@label = labels.first

check_label_has_a_for_attribute

label_for = @label[:for]
@inputs = page.all(id: label_for)

check_label_is_associated_with_a_field
check_there_is_only_1_element_with_the_associated_id

@input = @inputs.first

check_associated_element_is_a_form_field
check_input_type_is_text

aria_described_by_ids = @input["aria-describedby"].to_s.strip.split(/\s+/)

@described_by_elements = []

if aria_described_by_ids.size > 0
aria_described_by_ids.each do |aria_described_by_id|

check_there_is_only_one_element_with_id(aria_described_by_id)
@described_by_elements << page.find(id: aria_described_by_id)

end
end

if hint
check_field_is_described_by_a_hint
check_hint_matches_text_given
end

@input.set(with)
end

private

def check_for_inexact_label_match
labels_not_using_exact_match = page.all('label', text: label)
if labels_not_using_exact_match.size > 0
raise "Unable to find label with the text \"#{label}\" but did find label with the text \"#{labels_not_using_exact_match.first.text}\" - use the full label text"
end
end

def check_for_field_name_match
inputs_matching_name = page.all("input[name=\"#{label}\"]")

if inputs_matching_name.size > 0

input_matching_name = inputs_matching_name.first

labels = page.all("label[for=\"#{input_matching_name['id']}\"]")

if labels.size > 0
raise "Use the full label text \"#{labels.first.text}\" instead of the field name"
end
end
end

def check_label_has_a_for_attribute
if @label[:for].to_s.strip == ""
raise "Found the label but it is missing a \"for\" attribute to associate it with an input"
end
end

def check_label_is_associated_with_a_field
if @inputs.size == 0
raise "Found the label but there is no field with the ID \"#{@label[:for]}\" which matches the label‘s \"for\" attribute"
end
end

def check_there_is_only_1_element_with_the_associated_id
if @inputs.size > 1
raise "Found the label but there there are #{@inputs.size} elements with the ID \"#{@label[:for]}\" which matches the label‘s \"for\" attribute"
end
end

def check_associated_element_is_a_form_field
if !['input', 'textarea', 'select'].include?(@input.tag_name)
raise "Found the label but but it is associated with a <#{@input.tag_name}> element instead of a form field"
end
end

def check_input_type_is_text
raise "Found the field, but it has type=\"#{@input[:type]}\", expected type=\"text\"" unless @input[:type] == "text"
end

def check_field_is_described_by_a_hint
if @described_by_elements.size == 0
check_if_the_hint_exists_but_is_not_associated_with_field

raise "Found the field but could not find the hint \"#{hint}\""
end
end

def check_if_the_hint_exists_but_is_not_associated_with_field
if page.all('.govuk-hint', text: hint).size > 0
raise "Found the field and the hint, but not field is not associated with the hint using aria-describedby"
end
end

def check_hint_matches_text_given
hint_matching_id = @described_by_elements.find {|element| element[:class].include?("govuk-hint") }
if hint_matching_id.text != hint
raise "Found the label but the associated hint is \"#{hint_matching_id.text}\" not \"#{hint}\""
end
end

def check_there_is_only_one_element_with_id(aria_described_by_id)
elements_matching_id = page.all(id: aria_described_by_id)
if elements_matching_id.size == 0
raise "Found the field but it has an \"aria-describedby=#{aria_described_by_id}\" attribute and no hint with that ID exists"
elsif elements_matching_id.size > 1
raise "Found the field but it has an \"aria-describedby=#{aria_described_by_id}\" attribute and 2 elements with that ID exist"
end
end

end

def fill_in_govuk_text_field(label, hint: nil, with:)
FillInGovUKTextField.new(page:, label:, hint:, with:).fill_in
end

RSpec.configure do |rspec|
rspec.include self
end
end
1 change: 1 addition & 0 deletions lib/govuk_rspec_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
require_relative 'summarise_matcher'

require_relative 'click_govuk_link'
require_relative 'fill_in_govuk_text_field'
Loading

0 comments on commit 03dd196

Please sign in to comment.