Skip to main content

UI Test Actions Reference

Complete reference for all supported actions in YAML-based UI tests.

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 list

It 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.

matchTypePasses when
containsthe expected value appears somewhere in the actual one
notContainsit does not appear
equalsthe two values are exactly the same
equalsIgnoreCasethe same, ignoring case
regexthe whole value matches the pattern
notRegexthe whole value does not match

Leave it out and nothing changes. Each action keeps the default it has always had:

ActionDefault
VALIDATE_TEXTcontains
VALIDATE_TITLEcontains
VALIDATE_URLcontains
VALIDATE_ATTRIBUTEequals

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 value

This 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

Browser back button. No selector or value needed.

- action: NAVIGATE_BACK

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.

Fixed 2026-08-09 (86bbaxefn)

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 return is legal; does not await Promises returned from executeScript.
  • Playwright: top-level return legal (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

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_KEYS now replaces the field (clears it first) on both engines, so an explicit CLEAR before it is no longer required — it's harmless if present. Use CLEAR on 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

Use variables for credentials

Never hardcode usernames or passwords in test YAML. Use {{username}} and {{password}} — values are injected from the environment's default credential configuration.

Prefer data-testid selectors

[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.

Add waits before interactions

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"
Use baseUrl variable

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"
Take screenshots at key checkpoints

Add TAKE_SCREENSHOT after login, after critical actions, and before logout. Screenshots are saved with results for debugging failures.

Keep tests independent

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.