Testing
Actions
An action does something to the device: a tap, text, a gesture, a key, a rotation. This page lists the actions on a locator and on the screen, how they wait, and which platform supports which.
Actions on an element
Call these on a locator. Each is one call to the runtime, which waits for the element, acts and answers.
| Method | What it does |
|---|---|
tap(options?) | Taps the element, or position inside it |
doubleTap(options?) | Taps twice |
longPress(options?) | Presses and holds, for duration milliseconds (800 by default) |
fill(value) | Clears an editable element and sets its value |
clear() | Empties an editable element |
type(text, { delay? }) | Taps the element, then types text key by key. pressSequentially is the same |
swipe(direction, options?) | Swipes on the element. direction is where the finger travels |
scroll(direction, options?) | Scrolls this container once. direction is where the content goes |
scrollIntoView(options?) | Scrolls the screen, or container, until the element is visible |
dragTo(target, options?) | Drags the element onto another locator's element |
waitFor({ state? }) | Waits for the element to be visible, attached, hidden or detached |
await screen.getByTestId('email').fill('ada@ex.co')
await screen.getByTestId('sign-in').tap()
await screen.getByTestId('gesture-area').longPress()
await screen.getByTestId('row-40').scrollIntoView({ maxScrolls: 15 })Every method but clear also takes timeout in milliseconds. The other options:
| Option | On | Meaning | Default |
|---|---|---|---|
position | tap, doubleTap, longPress | A point inside the element, from its top left, in points | the driver's own point |
force | tap, doubleTap, longPress | Skip the actionability checks | false |
duration | longPress, swipe, dragTo | How long the gesture takes, in milliseconds | 800, 300, 500 |
distance | swipe, scroll | A fraction of the element's extent, above 0 and at most 1 | 0.6 for swipe |
delay | type | Milliseconds between keys | none |
hold | dragTo | Milliseconds to hold before moving | 0 |
direction | scrollIntoView | Which way to scroll | 'down' |
container | scrollIntoView | The locator of the container to scroll | the screen |
maxScrolls | scrollIntoView | Give up after this many scrolls, 1 to 100 | 10 |
How an action waits
You do not wait before an action. The runtime resolves the locator on the device again and again until the element passes the action's checks, then acts:
- attached: the element is in the tree
- visible: it has an area on screen
- enabled
- stable: its frame is the same on two looks in a row
- hittable: the device reports that a touch would reach it
- editable, for
fillonly: its role istextbox,searchbox,comboboxorspinbutton
The wait lasts until the action's timeout, else the actionTimeout option, else what
is left of the test's time, and never more than 300 seconds. What happens when it runs out depends on why:
| Error | Meaning |
|---|---|
LOCATOR_NOT_FOUND | Nothing matched the locator in time |
LOCATOR_NOT_ACTIONABLE | An element matched but never passed a check. The error names the check and carries a log of the attempts |
LOCATOR_STRICT_VIOLATION | More than one element matched. This fails at once, without waiting |
APP_CRASHED | The app crashed or exited during the action |
TIMEOUT | waitFor did not see its state in time |
force: true skips the checks. A tap at a point with screen.tapAt never waits.
Text
fill replaces the whole value in one step and is the usual way to enter text. type sends the text as key
events after tapping the element, which matters when the app reacts to each key.
await screen.getByLabel('Search').type('oat', { delay: 50 })
await screen.type(' milk') // into whatever has focus
await screen.pressKey('Enter') // Android Emulator only
await screen.hideKeyboard() // Android Emulator onlyfill takes up to 65,536 characters and type up to 4,096. screen.pressKey(key, modifiers?) presses one
key by its W3C KeyboardEvent.key name, with any of the modifiers Alt, Control, Meta and Shift. It goes
to the focused element. screen.hideKeyboard() returns whether a keyboard was showing. The
table below says where each one works.
Gestures and scrolling
The two direction arguments mean different things:
swipe('up')says where the finger travels. Swiping up on a list drags it up and shows what is below.scroll('down')says where you want to go in the content. Scrolling down shows what is below.
await screen.getByTestId('carousel').swipe('left')
await screen.swipe('up', { distance: 0.8 }) // across the whole screen
await screen.getByRole('list').scroll('down') // one scroll of this list
await screen.scroll('down', { until: screen.getByText('Terms'), maxScrolls: 20 })
await screen.getByTestId('card').dragTo(screen.getByTestId('trash'))
await screen.tapAt({ x: 200, y: 400 }) // a point, in pointsscrollIntoView and screen.scroll with until scroll until the element is visible, and fail with
LOCATOR_NOT_FOUND when it is not after maxScrolls. A long list often creates its rows lazily, so a far row
does not exist until you scroll near it:
test('scroll a long list to a far row', async ({ screen }) => {
await screen.getByTestId('nav-list').tap()
// The list is lazy: row 40 is not in the tree until it is scrolled near (about 3 screens down).
await expect(screen.getByTestId('row-40')).toBeHidden()
await screen.getByTestId('row-40').scrollIntoView({ maxScrolls: 15 })
await expect(screen.getByTestId('row-40')).toBeVisible()
})Buttons and orientation
await screen.pressButton('home')
await screen.setOrientation('landscapeLeft')
await expect(screen.getByTestId('nav-controls')).toBeVisible()
await screen.setOrientation('portrait')pressButton takes home, back, appSwitch, power, volumeUp or volumeDown, and an optional
duration for a long press. setOrientation takes portrait, portraitUpsideDown, landscapeLeft or
landscapeRight. To start every test of a project in an orientation, set the
orientation option instead.
What each platform supports
A platform that cannot perform an action refuses it with CAPABILITY_UNSUPPORTED before it touches the device.
| Action | iOS Simulator | Android Emulator | Mac Catalyst |
|---|---|---|---|
tap | yes | yes | yes: it activates the element |
fill, type, screen.type | yes | yes | yes |
scroll, scrollIntoView | yes | yes | yes |
doubleTap, longPress, swipe, dragTo, screen.tapAt | yes | yes | no |
screen.pressKey | no | yes | no |
screen.pressButton | home | all six buttons | no |
screen.setOrientation | yes | yes | no |
screen.hideKeyboard | no | yes | no |
Mac Catalyst has no touch input: a tap there activates the element through accessibility, and text goes into
the focused field. A test that needs a gesture says so with
test.requires('input.touch') and is skipped where it cannot run.
Next
- Assertions
- Fixtures: the
appfixture's launch, deep links and permissions - The
inputprocedures