- Getting Started
- Architecture
- The Basics
- Digging Deeper
-
EDGE Components
- Introduction
- Accordion
- Activity Indicator
- Badge
- Bottom Navigation
- Bottom Sheet
- Button
- Button Group
- Canvas
- Carousel
- Checkbox
- Chip
- Column
- Divider
- Floating Action Button
- Gesture Area
- Icon
- Image
- Lazy Grid
- Layout & Styling
- List
- Menus
- Modal
- Pressable
- Progress Bar
- Radio Group
- Pager
- Refreshable
- Row
- Scroll View
- Select
- Shapes
- Side Navigation
- Slider
- Spacer
- Stack
- Tab Row
- Text
- Text Input
- Toggle
- Top Bar
- Virtual List
- Web View
- Plugins
- Testing
- Publishing Your App
Interactions
Overview#
Interactions drive a screen the same way a person would — pressing buttons, typing into fields, flipping toggles. Each one dispatches a real event through the component's normal event path and then re-renders, so the assertions after it see the updated frame.
Every interaction method returns the harness, so they chain fluently.
Targeting elements#
Most interactions take a target. There are two ways to point at an element.
By visible text#
tap() finds the nearest pressable element whose subtree contains the given text. This is the most natural way to
press a labelled button or card:
Native::test(Dashboard::class)->tap('Refresh');
By ref#
For anything without unique visible text — an icon-only button, a specific input, a card among many — give the
element a ref and target it by name. A ref works in Blade on any element:
<native:pressable ref="vibrate-card" @press="vibrate"> <native:text>Tap to vibrate</native:text></native:pressable> <native:outlined-text-input ref="message-input" native:model="message" />
...and via ->ref() on element builders:
Pressable::make()->ref('vibrate-card')->onPress('vibrate');
Then target that name from the test:
Native::test(HapticsDemo::class) ->tap('vibrate-card') ->assertSet('buzzes', 1);
Refs make a test read cleanly and survive copy changes — the label can move without breaking the test.
4.7
A ref also reaches the platform's accessibility tree, as the accessibility identifier on iOS and the resource ID on
Android, so tools that drive the real app, such as Maestro or XCUITest, can find elements by the same name.
The same ref on two screens makes that element morph between them
when you navigate with the morph transition. Add morph="none" to an element whose ref is only there for tests
if you don't want that.
Pressing#
tap($target)— press byrefor visible text (the friendliest default).press($target)— press an element bound to a method, expression, orref.longPress($target)— fire a long-press.
it('opens the menu on long press', function () { Native::test(Gallery::class) ->longPress('photo-1') ->assertSee('Delete');});
Inputs and form controls#
Each control fires the same event its native counterpart emits:
input($target, $text)— type into a text field.submit($target, $text = '')— submit a field (e.g. the return key).toggle($target, $value)— flip a toggle on or off.check($target, $value = true)— check or uncheck a checkbox.slide($target, $value)— move a slider to a float value.select($target, $value)— choose a value in a select.selectRadio($target, $value)— pick a radio option.changeTab($target, $index)— switch a tab row to an index.dismissSheet($target)— dismiss a bottom sheet.
Fields bound with native:model sync their property as you'd expect:
it('fills and submits the toast form', function () { Native::visit('/dialogs/toast') ->input('message-input', 'Hello from CI') ->assertSet('message', 'Hello from CI') ->press('show-toast') ->assertNativeCalled('Dialog.Toast', fn (array $p) => $p['message'] === 'Hello from CI');});
List rows#
A list item's checkbox, radio and swipe-to-delete callbacks are best targeted by the expression you bound, since each row in a loop has its own:
<native:list-item :headline="$task->title" :leadingCheckbox="$task->done" on-leading-change="toggleTask({{ $task->id }})" on-swipe-delete="deleteTask({{ $task->id }})"/>
Native::test(Tasks::class) ->check('toggleTask(1)') // calls toggleTask(1, true) ->press('deleteTask(1)'); // swipe-to-delete arrives as a press
Setting properties and calling methods#
Two lower-level tools reach the component directly.
set($property, $value) sets a public property through the same binding path a native input uses — so it fires any
updatedFoo() hook — then re-renders:
Native::test(SecureStorageDemo::class) ->set('key', 'api-key') ->set('value', 's3cret') ->call('store');
call($method, ...$args) invokes a component method directly, then re-renders. Reach for it to trigger logic that
isn't wired to a visible control, or to set up state:
Native::test(Home::class) ->call('doubleTapped') ->assertSet('gesture', 'Double-tapped!');
The generic primitive: fireEvent()#
All the input sugar above is built on fireEvent($target, $type, $fields = []), which dispatches a wire event at the
callback registered for a target. You rarely need it directly, but it's there when you're driving a custom element
that emits an event the sugar doesn't cover:
Native::test(CustomControl::class) ->fireEvent('custom-widget', TestableComponent::EVENT_SLIDER_CHANGE, ['value' => 0.5]);
Polling#
Screens that refresh with #[Poll] don't wait for a timer under test — you fire them on demand.
firePolls()fires every#[Poll]method immediately and re-renders, as if all timers came due at once.firePoll($method)fires a single#[Poll]method by name.
it('polls the native recording status on demand', function () { Native::fakeBridge()->respondTo('Microphone.GetStatus', ['status' => 'recording']); Native::test(MicrophoneDemo::class) ->call('startRecording') ->firePoll('pollStatus') ->assertSet('status', 'Native status: recording') ->assertSee('Native status: recording');});
Search#
For screens that override onSearchQuery(), run a query through the same handler the runloop uses. search($query)
drives the handler and stages the results for the next frame; searchResults() returns those staged results:
it('filters the contact list', function () { $screen = Native::test(ContactSearch::class)->search('ada'); expect($screen->searchResults())->toHaveCount(1); $screen->assertSee('Ada Lovelace');});
Back button#
pressBack() simulates the system back gesture or hardware back button:
Native::test(EditPost::class) ->pressBack() ->assertWentBack();
See Navigation & Flows for asserting on where back takes you.
in no time