UI Test Actions Reference
Complete reference for all supported actions in YAML-based UI tests.
Navigation
NAVIGATE_TO
Navigate to a URL.
- action: NAVIGATE_TO
value: "{{baseUrl}}/login"
WAIT_FOR_ELEMENT
Wait for an element to appear (seconds).
- action: WAIT_FOR_ELEMENT
selector: "#dashboard"
value: "10"
WAIT_FOR_CLICKABLE
Wait for an element to become enabled and accept clicks. Useful when buttons start disabled and enable after a form is filled.
- action: WAIT_FOR_CLICKABLE
selector: "button[type=submit]"
value: "10"
WAIT_FOR_TEXT
Wait until an element's text matches the expected string (uses contains match).
- action: WAIT_FOR_TEXT
selector: ".job-status"
expectedText: "COMPLETED"
value: "30"
WAIT_FOR_VISIBLE
Wait for an element to become visible.
- action: WAIT_FOR_VISIBLE
selector: ".modal"
value: "5"
Interaction
CLICK
Click an element.
- action: CLICK
selector: "button[type=submit]"
SEND_KEYS
Set an input field to value. Replaces any existing contents (the field is
cleared first), and behaves identically on WebDriver and Playwright — so the same
YAML gives the same result on both engines. You don't need a separate CLEAR
before SEND_KEYS.
- action: SEND_KEYS
selector: "#username"
value: "{{username}}"
CLEAR
Clear the contents of an input field.
- action: CLEAR
selector: "#search-box"
SELECT_BY_TEXT
Select an option from a dropdown by visible text.
- action: SELECT_BY_TEXT
selector: "#country"
value: "United States"
SELECT_BY_VALUE
Select an option from a dropdown by value attribute.
- action: SELECT_BY_VALUE
selector: "#country"
value: "US"
HOVER
Hover over an element.
- action: HOVER
selector: ".dropdown-menu"
DOUBLE_CLICK
Double-click an element.
- action: DOUBLE_CLICK
selector: ".file-row"
RIGHT_CLICK
Right-click an element (opens the browser's native context menu).
- action: RIGHT_CLICK
selector: ".tree-node"
SUBMIT
Submit a form. Pass the selector of the <form> (or any element inside it).
- action: SUBMIT
selector: "form#checkout"
SELECT_BY_INDEX
Pick a <select> option by zero-based index.
- action: SELECT_BY_INDEX
selector: "select[name=country]"
index: "2"
FILL_FORM
Fill several fields of a form in one step. Point selector at the form (or any
container) and give a {fieldName: value} map — each field is matched by its
name (or id) within that form. The map can be a JSON string under value
or a YAML map under formData.
# value as a JSON object (field names → values)
- action: FILL_FORM
selector: "#login"
value: '{"username":"{{username}}","password":"{{password}}"}'
# formData as a YAML map — field name to value
- action: FILL_FORM
selector: "#login"
formData:
username: "{{username}}"
password: "{{password}}"
Either form is enough on its own. If both are present, formData wins.
formData is a map, never a listIt is fieldName: value pairs. A list of {selector, value} objects is rejected — that shape was
in an older version of this page and is not what the platform reads.
The field names are matched by name (or id) within the form you pointed selector at, so
they are form field names, not selectors.
FILL_FORM fails the step if it can't fill anything (no map and no
fields) — it never silently passes. Behaves identically on WebDriver and Playwright.
SCROLL_TO_ELEMENT
Scroll an element into view.
- action: SCROLL_TO_ELEMENT
selector: "#footer"
SCROLL_TO
Alias of SCROLL_TO_ELEMENT — scrolls the element matched by selector into
view. (The runtime is element-based; there is no raw x/y-coordinate scroll.)
- action: SCROLL_TO
selector: "#footer"
Validation
All text validations use contains matching (not exact). The assertion passes if the actual text contains the expected text.
VALIDATE_TEXT
Verify an element's text.
- action: VALIDATE_TEXT
selector: ".welcome-message"
expectedText: "Welcome, Admin"
Add matchType to compare a different way — see
Match types below.
- action: VALIDATE_TEXT
selector: "#order-ref"
expectedText: "^ORD-\\d{6}$"
matchType: regex
Match types
matchType is optional on VALIDATE_TEXT, VALIDATE_TITLE, VALIDATE_URL and
VALIDATE_ATTRIBUTE.
The operators follow WireMock's, which this platform already runs for its mock backend — so if you have written a stub here, you already know how these behave. The rule behind the naming is simply that each name means what it says.
matchType | Passes when |
|---|---|
contains | the expected value appears somewhere in the actual one |
notContains | it does not appear |
equals | the two values are exactly the same |
equalsIgnoreCase | the same, ignoring case |
regex | the whole value matches the pattern |
notRegex | the whole value does not match |
Leave it out and nothing changes. Each action keeps the default it has always had:
| Action | Default |
|---|---|
VALIDATE_TEXT | contains |
VALIDATE_TITLE | contains |
VALIDATE_URL | contains |
VALIDATE_ATTRIBUTE | equals |
That difference is deliberate. A page's text is a fragment of something bigger, so contains is what
you almost always want. An attribute is the whole value, so equals is.
regex matches the WHOLE valueThis is the one that catches people, and it is deliberate — it is what WireMock's matching does:
"Deems a match if the entire attribute value matched the expected regular expression."
expectedText: "\\d{4}" # does NOT match "Order 1234"
expectedText: ".*\\d{4}.*" # does — this is how you say "contains a four-digit number"
expectedText: "\\d{4}" # matches "1234", where the whole value is four digits
A searching regex would quietly overlap contains, and then the operator name would not be true.
Use .*…*. when you want to find a pattern inside a longer value.
What regex is for. Values that are correct but never the same twice — an order reference, a generated id, a date:
- action: VALIDATE_TEXT
selector: "#order-ref"
expectedText: "ORD-\\d{6}"
matchType: regex
- action: VALIDATE_URL
expectedText: ".*/orders/\\d+"
matchType: regex
- action: VALIDATE_TEXT
selector: ".delivery-date"
expectedText: ".*\\d{4}-\\d{2}-\\d{2}.*"
matchType: regex
notContains is the other one worth knowing — asserting that something is absent is often the
check that matters:
- action: VALIDATE_TEXT
selector: "body"
expectedText: "Internal Server Error"
matchType: notContains
When a pattern will not compile: NOT_MATCHED
A regex with a typo in it — (unclosed — does not fail the step and does not pass it. The step is
reported NOT_MATCHED, and the run does not go green.
That is a third status alongside PASSED and FAILED, and it exists because the other two cannot
tell the truth here. Nobody evaluated the assertion. The application did nothing wrong, so
counting it as a failure blames the wrong system and inflates the number people read to decide
whether a release is healthy. But nothing was checked either, so it is emphatically not a pass.
totalSteps 6 passed 4 failed 0 skipped 1 notMatched 1
The run is not successful, and the failure count is still zero — because your application did not fail. Your test did.
The same check runs when you save the test, so a bad pattern is normally refused at authoring rather than discovered from a run minutes later.
VALIDATE_TITLE
Verify the page title contains text.
- action: VALIDATE_TITLE
expectedText: "Dashboard"
VALIDATE_URL
Verify the current URL contains a string.
- action: VALIDATE_URL
expectedText: "/dashboard"
VALIDATE_VISIBLE
Verify an element is visible on the page.
- action: VALIDATE_VISIBLE
selector: "#dashboard-widget"
VALIDATE_ELEMENT_EXISTS
Verify an element exists in the DOM (may not be visible).
- action: VALIDATE_ELEMENT_EXISTS
selector: "#hidden-input"
VALIDATE_ATTRIBUTE
Verify an element's attribute has a specific value. Uses exact matching.
- action: VALIDATE_ATTRIBUTE
selector: "#status"
attribute: "class"
expectedText: "active"
VALIDATE_ELEMENT_VISIBLE
Alias of VALIDATE_VISIBLE. Both action names dispatch the same code; prefer VALIDATE_VISIBLE for brevity.
Read element state
These actions read from the page rather than acting on it. The captured value lands in the step's output field and is available for assertions in later steps.
GET_TEXT
Read the innerText of an element.
- action: GET_TEXT
selector: ".job-status"
GET_ATTRIBUTE
Read a specific attribute. attribute is required.
- action: GET_ATTRIBUTE
selector: "input#email"
attribute: "value"
GET_VALUE
Read the value property of a form control (input / select / textarea).
- action: GET_VALUE
selector: "input[name=username]"
Utility
TAKE_SCREENSHOT
Capture a screenshot of the current page.
- action: TAKE_SCREENSHOT
filename: "after-login" # optional
MAXIMIZE_WINDOW
Maximize the browser window.
- action: MAXIMIZE_WINDOW
NAVIGATE_BACK
Browser back button. No selector or value needed.
- action: NAVIGATE_BACK
NAVIGATE_FORWARD
Browser forward button.
- action: NAVIGATE_FORWARD
REFRESH
Reload the current page. Useful after a server-side state change you want the UI to re-read.
- action: REFRESH
SWITCH_FRAME
Switch the test context into an iframe matched by selector. Element lookups in
the following steps run inside that frame until you switch back.
- action: SWITCH_FRAME
selector: "#mce_0_ifr"
To return to the main document, pass an empty selector (or selector: "parent"
/ "default" / "main").
SWITCH_TAB
Switch focus to another browser tab (popups, target=_blank links). index is
the zero-based tab index; subsequent steps run against that tab.
- action: CLICK
selector: ".open-new-tab" # opens a second tab
- action: SWITCH_TAB
index: "1" # focus it (the runner waits for the tab to open)
The runner waits for a tab opened by a prior click to register before switching, so you don't need an explicit wait step in between.
EXECUTE_JS
Execute JavaScript in the browser. Accepts either value: or script: — both are validated, both
run on both engines, and both resolve {{variables}} before the script is executed.
script: is what the action catalogue (GET /api/ui-tests/actions) has always advertised, but the
validator demanded value: and the WebDriver engine ignored script: — so following the documented
vocabulary failed validation, and a script: that slipped through ran empty on WebDriver while
working on Playwright. Both engines and the validator now agree.
Single-line:
- action: EXECUTE_JS
value: "document.querySelector('#cookie-banner').remove()"
Multi-line (use YAML's | literal block scalar — newlines and indentation are preserved verbatim, no escaping needed):
- action: EXECUTE_JS
value: |
const token = localStorage.getItem('token');
return fetch('/api/findings/60/status', {
method: 'PUT',
headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token},
body: JSON.stringify({status: 'TRIAGED'})
}).then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status);
document.title = 'patent-2-ok';
return 'ok';
});
Engine differences (see yaml-format.md):
- Selenium: top-level
returnis legal; does not await Promises returned fromexecuteScript. - Playwright: top-level
returnlegal (script is IIFE-wrapped); awaits the returned Promise.
To assert on async results from a later step, write a marker to document.title from inside the .then() and use VALIDATE_TITLE afterwards. For tests that need async behaviour (e.g. chained fetch calls), pin the test's driver to PLAYWRIGHT.
Selectors
CSS Selectors (recommended)
selector: "#login-button" # by ID
selector: ".btn-primary" # by class
selector: "button[type=submit]" # by attribute
selector: "div.card > h2" # child combinator
selector: "[data-testid=login]" # by data attribute (best practice)
XPath
xpath: "//button[contains(text(),'Login')]"
Variables
Both {{variable}} and ${variable} syntax are supported. Variable lookup is case-insensitive.
Variables come from:
- Environment credentials —
{{username}},{{password}}(from default credential) - Target URL —
{{baseUrl}}(from environment target configuration) - Environment variables —
{{env.MY_VAR}} - Credential prefix —
{{credential.username}}(explicit)
steps:
- action: NAVIGATE_TO
value: "{{baseUrl}}"
- action: CLEAR
selector: "#username"
- action: SEND_KEYS
selector: "#username"
value: "{{username}}"
- action: CLEAR
selector: "#password"
- action: SEND_KEYS
selector: "#password"
value: "{{password}}"
SEND_KEYSnow replaces the field (clears it first) on both engines, so an explicitCLEARbefore it is no longer required — it's harmless if present. UseCLEARon its own when you want to empty a field without typing.
Complete Example
steps:
- action: NAVIGATE_TO
value: "{{baseUrl}}"
- action: WAIT_FOR_ELEMENT
selector: "[data-testid=login-form]"
value: "5"
- action: CLEAR
selector: "[data-testid=login-username]"
- action: SEND_KEYS
selector: "[data-testid=login-username]"
value: "{{username}}"
- action: CLEAR
selector: "[data-testid=login-password]"
- action: SEND_KEYS
selector: "[data-testid=login-password]"
value: "{{password}}"
- action: CLICK
selector: "[data-testid=login-submit]"
- action: WAIT_FOR_ELEMENT
selector: "[data-testid=dashboard]"
value: "5"
- action: TAKE_SCREENSHOT
- action: VALIDATE_TEXT
selector: "body"
expectedText: "Welcome"
- action: VALIDATE_URL
expectedText: "/dashboard"
- action: CLICK
selector: "[data-testid=logout-btn]"
- action: WAIT_FOR_ELEMENT
selector: "[data-testid=login-form]"
value: "3"
- action: TAKE_SCREENSHOT
Best Practices
Never hardcode usernames or passwords in test YAML. Use {{username}} and {{password}} — values are injected from the environment's default credential configuration.
[data-testid=login-btn] is stable and doesn't break when CSS classes or text change. Add data-testid attributes to your app's interactive elements.
Elements may not be visible immediately after navigation or clicks. Use WAIT_FOR_ELEMENT before SEND_KEYS, CLICK, or VALIDATE_TEXT.
- action: WAIT_FOR_ELEMENT
selector: "[data-testid=dashboard]"
value: "5"
- action: VALIDATE_TEXT
selector: ".welcome"
expectedText: "Welcome"
Always use {{baseUrl}} for navigation — never hardcode URLs. This lets the same test run against dev, QA, staging, and production.
- action: NAVIGATE_TO
value: "{{baseUrl}}/login"
Add TAKE_SCREENSHOT after login, after critical actions, and before logout. Screenshots are saved with results for debugging failures.
Each test should set up its own state (navigate, login) and not depend on another test's output. This enables parallel execution and prevents cascading failures.