Field steps cover the state assertions and direct interactions QA repeats most: is a field empty, present, enabled, required; is a checkbox checked; is a radio selected; does a select list contain - or currently select - a given option. The group also carries fill helpers for the trickier input types that plain I fill in does not reach: multi-value name[] arrays, <input type="color">, CKEditor / contenteditable WYSIWYG fields, and paired date + time controls including start/end date ranges.
Fields are resolved generously. When the quoted value starts with #, ., or [ it is treated as a raw CSS selector. Otherwise webship-js first tries Playwright's accessible getByLabel, then falls back to [name="..."] and #id. That means the field "Email" should be empty, the field "email" should be empty, and the field "#email" should be empty can all address the same input.
Because every state-changing step (fill, check, choose, select) is followed by a silent BBR smart-settle pass, an assertion in this group can safely follow an interaction without an explicit wait - see wait steps for the underlying model. For filling by visible label or attribute, see the companion form steps group.
Quick reference
| Step | What it does |
|---|---|
Then the field "username" should be empty | Assert a field's current value is the empty string. |
Then the field "username" should not be empty | Assert a field has a non-empty value. |
Then the field "username" should exist | Assert a field exists in the DOM (any state). |
Then the field "missing-field" should not exist | Assert a field does NOT exist in the DOM. |
Then the field "username" should have "enabled" state | Assert a field's enabled / disabled state. |
Then the field "username" should be required | Assert a field has required or aria-required="true". |
Then the field "bio" should not be required | Assert a field is NOT required. |
Then the "Username" field should contain "John Smith" | Assert a field contains (or with not, does not contain) text. |
When I fill in the multi-value field "tags" with the following values: | Fill a multi-value name[] field from a table. |
When I fill in the color field "favorite" with the value "#ff0000" | Fill an <input type="color"> with a hex value. |
Then the color field "favorite" should have the value "#ff0000" | Assert a color input's value (case-insensitive). |
When I fill in the WYSIWYG field "body" with the "Hello world" | Fill a CKEditor / contenteditable rich-text field. |
Then the option "Mercedes" should exist within the select element "#cars" | Assert a <select> contains an option. |
Then the option "Manager" should not exist within the select element "#role" | Assert a <select> lacks an option. |
Then the option "Mercedes" should be selected within the select element "#cars" | Assert an option is currently selected. |
Then the option "Admin" should not be selected within the select element "#role" | Assert an option is NOT currently selected. |
When I unselect "Red" from "#colors" | Unselect one option from a <select multiple>. |
When I clear the select "#colors" | Clear all selections in a <select multiple>. |
When I check the checkbox "#agree" | Check a CSS-addressed checkbox. |
When I uncheck the checkbox "#newsletter" | Uncheck a CSS-addressed checkbox. |
When I choose the radio button "#gender-male" | Select a CSS-addressed radio button. |
Then the "#agree" checkbox should be checked | Assert checkbox state (also is checked and the checkbox "..." phrasings). |
Then the radio button "#gender-male" should be selected | Assert radio state by selector. |
Then the radio button with value "male" should be selected | Assert radio state by value attribute. |
When I fill in the field "#username" with "alice" | Fill a CSS-addressed input / textarea. |
Given browser validation for the form "#signup" is disabled | Set novalidate on a form. |
When I fill in the datetime field "Start" with date "2026-05-08" and time "10:00" | Fill paired date + time inputs for a label. |
When I fill in the date part of the datetime field "Start" with "2026-05-08" | Fill only the date component. |
When I fill in the time part of the datetime field "Start" with "10:00" | Fill only the time component. |
When I fill in the start datetime field "Event" with date "2026-05-08" and time "10:00" | Fill the start half of a date-range pair. |
When I fill in the end datetime field "Event" with date "2026-05-09" and time "18:00" | Fill the end half of a date-range pair. |
Steps in detail
Then the field "..." should be empty / should not be empty
Reads the field's inputValue() and compares against the empty string. Accepts a label, a name, an id, or a raw CSS selector. The failure message reports the actual value found, which makes autofill and default-value regressions easy to spot.
When I fill in "" for "Username"
Then the field "username" should be empty
When I fill in "alice" for "Username"
Then the field "username" should not be empty
Then the field "..." should exist / should not exist
Counts matching nodes in the DOM regardless of visibility. Use this for presence checks - for example that a legacy field was removed after a module update, or that a conditional field appears only for the right role.
Then the field "username" should exist
And the field "missing-field" should not exist
And the field "[name=internal_only]" should not exist
Then the field "..." should have "enabled|disabled" state
Checks Playwright's isDisabled(). Pass exactly "disabled" to assert the field is disabled; any other value asserts it is enabled.
Then the field "Email" should have "disabled" state
And the field "#search" should have "enabled" state
Then the field "..." should be required / should not be required
Passes when either the native required attribute or aria-required="true" is present, so it works for both plain HTML5 forms and ARIA-driven widgets.
Then the field "username" should be required
And the field "bio" should not be required
Then the "..." field should contain "..."
Text-content assertion with an optional not. When the quoted field is not a #id / .class selector, the step finds the exact label text and follows its for attribute to the input, then waits up to 5 seconds for the element before reading its value.
When I fill in "Username" with "John Smith"
Then the "Username" field should contain "John Smith"
And the "#username" field should not contain "Jane"
When I fill in the multi-value field "..." with the following values:
Feeds a Gherkin table into repeated inputs named field[0], field[1], … or field[]. Values are applied in row order; if fewer inputs exist than rows, the first matching input receives the overflow.
When I fill in the multi-value field "tags" with the following values:
| bdd |
| testing |
When I fill in the color field ... / Then the color field ... should have the value ...
Fill and assert <input type="color"> controls with hex values. The assertion compares case-insensitively, so #FF0000 and #ff0000 both pass.
When I fill in the color field "favorite" with the value "#ff0000"
Then the color field "favorite" should have the value "#FF0000"
When I fill in the WYSIWYG field "..." with the "..."
Targets CKEditor 5/4 and generic contenteditable editors. The step sets the backing [name] / #id element's value when one exists, and writes the content into the first .ck-editor__editable or [contenteditable="true"] region as a paragraph. HTML in the value is kept, so <strong>Bold</strong> produces real markup.
When I fill in the WYSIWYG field "body" with the "Hello world"
And I fill in the WYSIWYG field "Description" with the "<strong>Bold</strong>"
Then the option "..." should (not) exist / should (not) be selected within the select element "..."
The existence pair counts option:has-text(...) nodes; the selection pair reads the live selectedOptions collection, so it also works for <select multiple>. The select element is always addressed by CSS selector here.
Then the option "Admin" should exist within the select element "#role"
And the option "Manager" should not exist within the select element "#role"
And the option "Admin" should not be selected within the select element "#role"
When I unselect "..." from "..." / When I clear the select "..."
Multi-select housekeeping. unselect deselects the option whose text or value matches; clear the select deselects everything. Both dispatch a bubbling change event so framework listeners (React, Vue, Drupal states) react as if the user had done it.
When I unselect "Red" from "#colors"
And I clear the select "#tags"
When I check / uncheck the checkbox "..." and I choose the radio button "..."
CSS-selector variants of the label-based I check / I uncheck / I select radio button steps in the form group. Use them when the input has no usable label - Playwright's check() / uncheck() auto-wait applies.
When I check the checkbox "#agree"
And I uncheck the checkbox "input[name=marketing]"
And I choose the radio button "[data-testid=annual]"
Checkbox and radio state assertions
Four interchangeable checkbox phrasings - the "#x" checkbox should be checked, the "#x" checkbox is checked, the checkbox "#x" should be checked, the checkbox "#x" is checked - each accepting not. Radios have the same pattern plus a lookup by value attribute for when ids are generated.
When I check "Remember me"
Then the "#remember" checkbox should be checked
When I select radio button "Premium"
Then the radio button with value "premium" should be selected
And the "#gender-female" radio button is not selected
Given browser validation for the form "..." is disabled
Sets novalidate on the form so the browser will not block submission of invalid input. Use it to exercise server-side error rendering that native HTML5 validation would otherwise mask.
Given browser validation for the form "#signup" is disabled
When I press "Submit"
Then I should see "Email address is required"
Datetime and date-range fill steps
The combined step fills paired <input type="date"> and <input type="time"> controls located near a label or via a name*= partial match; the "date part" / "time part" variants touch only one half. The start/end pair targets inputs whose name contains both the label and start or end, which matches Drupal date-range widgets. Dates use YYYY-MM-DD and times use 24-hour HH:MM. Missing halves are silently skipped in the combined and range steps, so a date-only widget does not fail on the time fill.
When I fill in the datetime field "Start" with date "2026-05-08" and time "10:00"
And I fill in the date part of the datetime field "Birth date" with "1990-01-15"
And I fill in the start datetime field "Event" with date "2026-05-08" and time "10:00"
And I fill in the end datetime field "Event" with date "2026-05-09" and time "18:00"
Complete example
Adapted from tests/features/field.feature, running against the bundled fixture examples/field.html:
Feature: Field step definitions
Scenario: Field state assertions
Given I am on "/field.html"
Then the field "username" should exist
And the field "username" should be empty
And the field "username" should be required
And the field "bio" should not be required
And the field "missing-field" should not exist
And the option "Admin" should exist within the select element "#role"
And the option "Manager" should not exist within the select element "#role"
And the option "Admin" should not be selected within the select element "#role"
Scenario: Field interactions
Given I am on "/field.html"
When I fill in the field "#username" with "alice"
And I check the checkbox "#agree"
And I choose the radio button "#r-red"
And I fill in the color field "fav" with the value "#ff0000"
Then the field "username" should not be empty
And the color field "fav" should have the value "#ff0000"
Scenario: Disable browser validation
Given I am on "/field.html"
And browser validation for the form "#f" is disabled
Tips
- Prefer labels over selectors in feature files:
the field "Email"survives markup refactors that break#edit-mail. When you must use a selector repeatedly, register it once in the selector registry. - The phrasing
the radio button "#x" should be selectedis provided once, in this group - the same regex is intentionally not duplicated elsewhere to avoid ambiguous step matches. - All fill and check steps are followed by an automatic BBR smart-settle pass (budget 1500 ms), so conditional fields revealed by a checkbox are usually ready by the next step without an explicit wait.