Skip to main content

Composition

One of the strengths of interactors is that they are composable, this allows you to give context to an interaction.

Find

To compose interactors, we use the find method. Suppose you want to fill out an address and want to fill in the city you live in into the corresponding field:

await TextField("City").fillIn("Toronto");

But maybe the form we have has separate fields for the shipping address and the delivery address. Like we've noted before, if there are multiple elements which match an interactor and we perform an action, then we will get an error. Actions need to be unambiguous. So to successfully fill in the City field, we need to specify which address we're referring to.

Usually in our HTML code, we would use the <fieldset> tag to group a number of fields like our shipping and delivery addresses. Our markup could look something like this:

<fieldset>
<legend>Shipping Address</legend>

<p>
<label for="shipping_address_city">City</label>
<input id="shipping_address_city" type="text"/>
</p>
</fieldset>

<fieldset>
<legend>Delivery Address</legend>

<p>
<label for="delivery_address_city">City</label>
<input id="delivery_address_city" type="text"/>
</p>
</fieldset>

Of course we could use the id of the input tags to specify which input we mean:

await TextField({ id: "shipping_address_city" }).fillIn("Toronto");

But remember that interactors are supposed to test your application as your user sees it, and your user doesn't really know anything about the id of the field.

Instead we can use the FieldSet("Shipping Address") interactor to specify which fieldset we want to interact with, and use the find method to compose these two interactors:

await FieldSet("Shipping Address").find(TextField('City')).fillIn('Toronto');

Here find returns a new interactor which first looks for a fieldset with the legend "Shipping Address", and then within that fieldset tries to find a text field with the label "City". The interactor returned by find has the same actions and filters as the text field that we passed as an argument, so we can call the fillIn action from the TextField interactor on the result!

Taking it further

With find we can compose interactors on the fly, but when you are creating your own interactors, there are many more ways to reuse and compose interactors to create complex interactions and simplify common tasks. Check out our guides on defining locators, filters and actions for more information.

Waiting

You might be worried that composing interactors this way would have an effect on the waiting behaviour that we talked about when discussing Actions, but there is no need to be concerned. Composed interactors use the same waiting strategy, and they will retry the whole chain of interactors, so that when something changes on the page, those changes don't cause interactors further down the chain to fail.