Interact with the app
Your test gets two things you’ll use constantly: app to navigate and make requests, and I to interact with the UI. I is Interactor.js, rooted in your app’s current document.
await app.goto('/contacts/new');await I.find('Name').then.fill('Robert');await I.click('Save');await I.find('$([role=status])').text('Contact saved');app.goto waits for the document. The last assertion waits for the result you care about. You don’t need a sleep between those steps.
Find what someone would use
Section titled “Find what someone would use”Strings match text, placeholders and related label controls. They match the whole text; use a regular expression for a partial match. Use $(...) when you need CSS:
await I.find('Email').then.fill('rob@example.com');await I.find(/Contact saved/).rendered();await I.find('$(input[name=email])').value('rob@example.com');rendered() checks displayed content, including below the fold. visible() also requires it to be in the viewport. Assertions retry until they match, then continue immediately. expectTimeout sets the assertion deadline.
For changing browser state, use an assertion callback:
await I.assert( () => app.window.location.pathname === '/dashboard', 'Expected the dashboard URL');Work within a component
Section titled “Work within a component”Use scope() to narrow the root. It finds that root again after navigation, so you can reuse the scope within a test:
let contact = I.scope('$(form)');await contact.find('Email').then.fill('rob@example.com');A missing root fails. Create scopes before chaining; use I.scope(selector) rather than I.find(selector).then.scope().
fill() replaces an editable input or textarea value and sends input/change events. To select a file:
await contact.find('Photo').then.upload({ name: 'note.txt', bytes: 'Storm notes', type: 'text/plain',});Pass no files to clear the input. Use custom interactors when these actions form a workflow you’ll share.
Check the right thing
Section titled “Check the right thing”Built-in assertions cover text, values, visibility and more. contains(selector) checks for a descendant, not a text substring. For a DOM predicate, bind a scope:
await I.scope('$([role=status])').assert( ({ $ }) => $.textContent.includes('Saved'));Ordinary actions use DOM methods and synthetic events. For browser defaults such as Tab order or CSS hover, use native browser behavior.