Workflows Actions
CLI workflow actions allow you to test complex workflows such as those used in forms and single-page web apps.
When running axe DevTools CLI with the spec command, you can specify actions to run against the page. Run these actions before or after CLI runs accessibility tests. Actions allow you to perform tasks like clicking a button, entering text into a form field, dismissing a popup, or waiting for a specific page state.
Action Example
It's helpful to see a complete action example. The following example logs into the Deque University page and runs an analysis on the logged-in user's dashboard. Both YAML and JSON versions are included.
First is the YAML version of the example:
---
projects:
- name: Deque University login flow
id: deque-university-login-flow
pageList:
- name: homepage
url: https://dequeuniversity.com/
actions:
- click element ".loginLink"
- wait for element ".loginUsername" to be found
- type "user@example.com" into element ".loginUsername"
- type "secretpassword" into element "#loginPassword"
- click element "input[type=submit]"
- wait for element ".logoutLink" to be found
- analyze page
Below is the equivalent JSON example:
{
"projects": [
{
"name": "Deque University login flow",
"id": "deque-university-login-flow",
"pageList": [
{
"name": "homepage",
"url": "https://dequeuniversity.com/",
"actions": [
"click element \".loginLink\"",
"wait for element \".loginUsername\" to be found",
"type \"user@example.com\" into element \".loginUsername\"",
"type \"secretpassword\" into element \"#loginPassword\"",
"click element \"input[type=submit]\"",
"wait for element \".logoutLink\" to be found",
"analyze page"
]
}
]
}
]
}
Action Types
There are two types of actions: page-based actions and global actions.
Page-based actions are procedural actions that run in the order specified on a page-by-page basis within a project. See Page Actions below for more information.
Global actions listen for state changes on all pages in a project and react accordingly. For example, the CLI global action, dismiss modal, detects state changes when a page displays a modal the user must dismiss before proceeding with any other task. See Global Actions for more information.
Selectors
Whenever a selector can be used to specify an element on the page, it can be specified as either a string or a list of strings, and you can use CSS selectors or XPath selectors.
Using a list selects elements within iframes if you use CSS selectors.
The array examples below select a submit button <input> element within an iframe with the id inner-frame inside an iframe with the id outer-frame. The examples are YAML and JSON and demonstrate CSS selectors and XPath selectors.
An array of CSS selectors is shown below in YAML:
click element [ "#outer-frame", "#inner-frame", "input[type=submit]" ]
Or as follows in JSON:
"click element [\"#outer-frame\", \"#inner-frame\", \"input[type=submit]\"]",
The equivalent using XPATH selectors in YAML is:
click element [ "//iframe[@id='outer-frame']", "//iframe[@id='inner-frame']", "//input[@type='submit']" ]
In JSON, the equivalent selector is:
"click element [\"//iframe[@id='outer-frame']\", \"//iframe[@id='inner-frame']\", \"//input[@type='submit']\"]",
Page actions
CLI supports nine page-based actions:
- analyze: run an accessibility analysis
- change: change the value of an <input> or <select> via JavaScript
- click: click an element
- dismiss: dismiss a popup (or modal)
- eval: evaluate arbitrary JavaScript
- press: press a key (with or without modifiers)
- select: select an option in a <select>
- type: type into an <input>
- wait: wait for a specific state
You may use any combination of these actions in any order, but analyze must be called at least once.
Analyze action
The analyze action performs an accessibility analysis. This action must be used at least once per page.
You can provide an optional ruleset. The default ruleset is wcag2. The available optional rulesets are wcag2, wcag21, wcag22, and 508. You may also use a custom ruleset.
The wcag21 ruleset is the default ruleset for other axe DevTools products. The ruleset includes WCAG2A, WCAG2AA, WCAG2.1A, and WCAG2.1AA rules with best practices turned off.
You may specify only to analyze specific elements or not to analyze elements.
For more information on including or excluding elements, see the "Context Parameter" section of the axe API documentation.
Provide an optional title string, useful when running multiple analyses on the same page.
Analyze examples
Analyze the page using the 508 ruleset (YAML):
analyze page with ruleset "508"
JSON:
"analyze page with ruleset \"508\""
Analyze the page using the default (WCAG2) ruleset (YAML):
analyze the page
JSON:
"analyze the page"
Analyze the page with fewer keystrokes (YAML):
analyze
JSON:
"analyze"
Analyze the page with a custom title (YAML):
analyze the page with title "custom title"
JSON:
"analyze the page with title \"custom title\""
Analyze only a specific element on the page (YAML):
analyze only element "#idOfElement"
JSON:
"analyze only element \"#idOfElement\""
Analyze only a specific element and elements with a specific class (YAML):
analyze only element "#idOfElement" and element ".classToAnalyze"
JSON:
"analyze only element \"#idOfElement\" and element \".classToAnalyze\""
Analyze everything except images that are immediate children of paragraphs (YAML):
analyze the page excluding element "p > img"
JSON:
"analyze the page excluding element \"p > img\""
Analyze everything except a specific element inside <frame> elements with a specific class (YAML):
analyze the page excluding element [ ".classOfFrameToExclude\", "#idOfElement\" ]
JSON:
"analyze the page excluding element [\".classOfFrameToExclude\", \"#idOfElement\"]"
Set a Custom Export Directory
Setting the export directory enables you to specify where to save analysis results.
Follow these examples to specify a custom export directory.
The following syntax saves analysis results in the directory homepage-team (YAML):
analyze the page and save in "./homepage-team/"
JSON:
"analyze the page and save in \"./homepage-team/\""
The following syntax saves analysis results in both the default location for saved results as well as homepage-team (YAML):
analyze the page and save a copy in "./homepage-team/"
JSON:
"analyze the page and save a copy in \"./homepage-team/\""
Save the results for this in the project-wide folder and save a copy in a different folder (YAML):
analyze the page and save a copy in "some/other/directory"
JSON:
"analyze the page and save a copy in \"some/other/directory\""
The following example saves analysis results in both the default location for saved results as well as homepage-team (YAML):
analyze the page and save a copy in "./homepage-team/"
JSON:
"analyze the page and save a copy in \"./homepage-team/\""
Use the default ruleset for the version of axe in use instead of wcag2 (YAML):
analyze the page with the source default ruleset
JSON:
"analyze the page with the source default ruleset"
Putting everything together: Analyze only images within iframe elements with the class third-party and forms that aren't validated on submission, excluding elements with the class old-api, using the 508 ruleset, with "What is this testing" as the title and saving in the folder Results for Some test.
First, the example in YAML:
analyze only element [ "#third-party", "img"] and element "form[novalidate]" excluding element "#old-api" with ruleset "508" with title "What is this testing" and save in "Results for Some test"
The same example in JSON is as follows:
"analyze only element [\"#third-party\", \"img\"] and element \"form[novalidate]\" excluding element \"#old-api\" with ruleset \"508\" with title \"What is this testing\" and save in \"Results for Some test\""
Change Action
The change action changes the value of <input>, <textarea> or <select> elements using JavaScript. The change action is useful for cases where "regular" DOM events are prohibited.
Change example
Here is an example of changing the value of the <input> element (YAML):
change the value of "input[name=song]" to "too many puppies"
JSON:
"change the value of \"input[name=song]\" to \"too many puppies\""
Click action
The click action instructs the browser to click an element (specified by a CSS selector).
Click examples
Click the first element which matches the .myButton CSS selector (YAML):
click element ".myButton"
JSON:
"click element \".myButton\""
Click the body (YAML):
click "body"
JSON:
"click \"body\""
Dismiss action
The dismiss action closes the page's popup or modal element. Provide a CSS selector for the modal and its close button. The action fails gracefully if either the modal or close button is missing.
This action does not dismiss native alert() or confirm() calls.
Dismiss example
Close the modal matching the .myModal CSS selector using the button matching .myModal .close (YAML):
dismiss modal ".myModal" with close button ".myModal .close"
JSON:
"dismiss modal \".myModal\" with close button \".myModal .close\""
Eval action
The eval action executes arbitrary JavaScript on the page and is useful for performing custom actions or manipulating the DOM.
Eval examples
Change the page's title (YAML):
eval "document.title = 'hello world'"
JSON:
"eval \"document.title = 'hello world'\""
Scroll an element into view (YAML):
eval "document.querySelector('.someElement').scrollIntoView()"
JSON:
"eval \"document.querySelector('.someElement').scrollIntoView()\""
Scroll to the bottom of the page (YAML):
eval "window.scrollTo(0, document.body.scrollHeight)"
JSON:
"eval \"window.scrollTo(0, document.body.scrollHeight)\""
Press action
The press action performs a single key down (with or without modifiers) on an element.
Find the supported key names in the Selenium documentation.
Press examples
Press H on the body element (YAML):
press "H" on "body"
JSON:
"press \"H\" on \"body\""
Press Shift+Tab on the .navigation element:
press "shift+tab" on element ".navigation"
JSON:
"press \"shift+tab\" on element \".navigation\""
Press Shift+Control+7 on the .foo element (YAML):
press "shift+control+7" on element ".foo"
JSON:
"press \"shift+control+7\" on element \".foo\""
Select action
The select action selects an <option> of a <select> element by its visible text, not its value.
Select examples
Assuming the DOM structure:
<select class="mySelect">
<option></option>
<option value="1">dog</option>
<option value="2">cat</option>
<option value="3">fish</option>
</select>
Select the "dog" option (YAML):
select the "dog" option in ".mySelect"
JSON:
"select the \"dog\" option in \".mySelect\""
Select the "cat" option (YAML):
select the "cat" option in element ".mySelect"
JSON:
"select the \"cat\" option in element \".mySelect\""
Type action
The type action types a string into an <input> element and is useful for filling out forms and populating search bars, for instance.
Type examples
Type "user@example.com" into the email address input (YAML):
type "user@example.com" into element "input[type=email]"
JSON:
"type \"user@example.com\" into element \"input[type=email]\""
Type "hello world" into the <textarea> matching .Message (YAML):
type "hello world" into "textarea.Message"
JSON:
"type \"hello world\" into \"textarea.Message\""
Wait action
The wait action does one of two things:
- Waits for a specific element state.
- Sleeps for a specified amount of time.
When used to wait for an element to enter a state, provide both the element's CSS selector and the state. Supported wait states include:
- visible: the element is visible on the page.
- hidden: the element is hidden, but exists on the page.
- selected: the element is selected.
- enabled: the element is enabled.
- disabled: the element is disabled.
- found: the element exists on the page.
When used to sleep, the wait action accepts a timeout value. Milliseconds correspond to numeric values. When provided as a string, the CLI attempts to convert the string to milliseconds. For example, if provided 3m, CLI sleeps for 180,000 milliseconds (3 minutes); if provided 1000, CLI sleeps for 1,000 milliseconds.
The ms package provides string-to-millisecond parsing.
Wait examples
Wait for 1 minute (YAML):
wait for 1m
JSON:
"wait for 1m"
Wait for 30 milliseconds (YAML):
wait for 30
JSON:
"wait for 30"
Wait for 1 second (YAML):
wait for 1s
JSON:
"wait for 1s"
Wait for the first element matching the CSS selector .myElement to be hidden (YAML):
wait for element ".myElement" to be hidden
JSON:
"wait for element \".myElement\" to be hidden"
Wait for the first element matching the CSS selector .myElement to be found (YAML):
wait for element ".myElement" to be found
JSON:
"wait for element \".myElement\" to be found"
Type "sloth" into the search box with a specified key delay (to mimic human typing) (YAML):
type "sloth" into "input[type=search]" with a 150ms key delay
JSON:
"type \"sloth\" into \"input[type=search]\" with a 150ms key delay"
Global Actions
There are some essential points to remember about the CLI global dismiss modal action:
- Global actions run on every page included in a CLI project running in spec mode.
- Global actions are not procedural and do not happen in any specific order. Global actions respond to events and page state changes. For example, the dismiss modal action waits for a page to display a modal that the user must dismiss before any further page-based procedural actions may continue.
- CLI global actions work in the CLI's spec and headless URI mode.
A site where every page contains random popup modal dialogs requiring users to dismiss the dialogs manually is problematic. The CLI provides a global action that listens for and dismisses the random modal popups as the CLI tests the page-specific actions.
Add the CLI dismiss modal global action to your projects after the projects name and before the pagelist properties. The following example shows the YAML version:
---
projects:
- id: demo
name: CLI demo
globalActions:
- dismiss modal "#__next .survey" with close button ".survey button.close"
pageList:
- name: homepage
url: https://dequelabs.github.io/aget-demo-site
- name: popup
url: https://dequelabs.github.io/aget-demo-site
actions:
- wait for element "#__next header nav" to be visible
- click element "#__next header nav a[href*=popup]"
- wait for element ".content button" to be found
- analyze with title "before popup"
- click element ".content button"
- analyze with title "with popup"
- dismiss modal ".ReactModal__Content" with close button ".ReactModal__Content
.close"
- analyze with title "after popup"
- name: contact
url: https://dequelabs.github.io/aget-demo-site/contact
actions:
- analyze with title "form disabled"
- wait for element "#__next .toggle" to be found
- click element ".toggle button"
- wait for element "input[name=name]" to be enabled
- analyze with title "form enabled"
- type "stephen" into element "input[name=name]"
- type "555-555-5555" into element "input[name=phone]"
- type "stephen@deque.com" into element "input[name=email]"
- type "hello world" into element "textarea[name=message]"
- click element "button[type=submit]"
- wait for element ".thanks" to be found
- analyze with title "thanks message"
The equivalent JSON version is shown below:
{
"projects": [
{
"id": "demo",
"name": "CLI demo",
"globalActions": [
"dismiss modal \"#__next .survey\" with close button \".survey button.close\""
],
"pageList": [
{
"name": "homepage",
"url": "https://dequelabs.github.io/aget-demo-site"
},
{
"name": "popup",
"url": "https://dequelabs.github.io/aget-demo-site",
"actions": [
"wait for element \"#__next header nav\" to be visible",
"click element \"#__next header nav a[href*=popup]\"",
"wait for element \".content button\" to be found",
"analyze with title \"before popup\"",
"click element \".content button\"",
"analyze with title \"with popup\"",
"dismiss modal \".ReactModal__Content\" with close button \".ReactModal__Content .close\"",
"analyze with title \"after popup\""
]
},
{
"name": "contact",
"url": "https://dequelabs.github.io/aget-demo-site/contact",
"actions": [
"analyze with title \"form disabled\"",
"wait for element \"#__next .toggle\" to be found",
"click element \".toggle button\"",
"wait for element \"input[name=name]\" to be enabled",
"analyze with title \"form enabled\"",
"type \"stephen\" into element \"input[name=name]\"",
"type \"555-555-5555\" into element \"input[name=phone]\"",
"type \"stephen@deque.com\" into element \"input[name=email]\"",
"type \"hello world\" into element \"textarea[name=message]\"",
"click element \"button[type=submit]\"",
"wait for element \".thanks\" to be found",
"analyze with title \"thanks message\""
]
}
]
}
]
}