> For the complete documentation index, see [llms.txt](https://docs.drizz.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.drizz.dev/writing-tests/modules.md).

# Modules

A **module** is a named script fragment called from any test with `CALL`. A flow repeated across tests — login, choosing a city, setting up an address — is written once as a module.

|                 |                                                                                  |
| --------------- | -------------------------------------------------------------------------------- |
| **Platforms**   | Android · iOS                                                                    |
| **Define with** | `# BEGIN <name>` … `# END <name>`                                                |
| **Parameters**  | `PARAM <name>` in the body, `CALL name(param={{value}})` at the call site        |
| **Nesting**     | Allowed. Cyclic calls are rejected                                               |
| **Watch out**   | After typing `CALL`, pause for the dropdown — typing straight through outruns it |

## Prerequisites

* Drizz desktop app installed and signed in
* A project to hold the module file
* A dataset bound to the plan, for any `{{variable}}` the module body references

## Copy this

The module, saved as its own file:

```
# BEGIN shopease_login
OPEN_APP {{app_package}}
Wait Until 5 Seconds

Type {{phone}} in the mobile number field
Tap on Get OTP
Wait Until 2 Seconds

Type {{otp}} in the OTP field
Tap on Continue
Wait Until 5 Seconds

Validate that the home screen is visible
# END shopease_login
```

The test that uses it:

```
# T04 — ShopEase: search and add to cart

CALL shopease_login

Tap on the search icon
Wait Until 2 Seconds

Type running shoes in the search field
Tap on the first search result
Wait Until 3 Seconds

Validate that the product detail page is visible

Tap on Add to Cart
Wait Until 2 Seconds

Validate that Item added to cart is visible

CLEAR_APP com.shopease.android
```

## Create and call a module

1. Create a script file for the module.
2. Save\<Select Module
3. Name the module in `snake_case`, for what it does — `shopease_login`, `city_selection_from_home`.
4. Open the test that needs it and type `CALL` on its own line.
5. Pause for the module dropdown, then select the module by name.
6. Run the test and confirm the module's steps appear in the report.

Anything valid in a test is valid in a module, including `IF` blocks, `Validate` and other `CALL`s. A module doesn't have to start with `OPEN_APP` — a module that picks a city or dismisses onboarding doesn't launch the app.

## CALL forms

| Form               | Written as                            | Behavior                                                         |
| ------------------ | ------------------------------------- | ---------------------------------------------------------------- |
| Plain call         | `CALL shopease_login`                 | Runs the module body in place                                    |
| Parameterized call | `CALL shopease_search(term={{food}})` | Binds each named argument to the matching `PARAM` before running |
| Nested call        | A `CALL` inside a module body         | Allowed to any depth. Cyclic calls are rejected                  |

The module's body appears inlined under the call line in the editor, indented and **read-only**:

```
CALL shopease_login
  # BEGIN shopease_login
  OPEN_APP {{app_package}}
  Wait Until 5 Seconds
  Type {{phone}} in the mobile number field
  # END shopease_login
```

| Behavior of the inlined block     | Detail                                                                               |
| --------------------------------- | ------------------------------------------------------------------------------------ |
| Editable                          | No. Edit the module file and every caller updates                                    |
| Enter at the end of the call line | Jumps past the block, so typing continues in the calling script                      |
| Deleting                          | Removes the whole block as a unit. It can't be half-deleted                          |
| At run time                       | Drizz collapses the expansion before running, so the module executes once, not twice |

## Parameters

```
SET food = "Burger"
CALL shopease_search(term={{food}})
  # BEGIN shopease_search
  PARAM term
  Tap on the search icon
  Type {{term}} in the search field
  # END shopease_search
```

| Element                   | Rule                                                                                                                                                                             |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Declaration               | `PARAM <name>` on its own line, immediately after `# BEGIN`                                                                                                                      |
| Reference in the body     | `{{name}}`                                                                                                                                                                       |
| Argument at the call site | Named: `CALL shopease_search(term={{food}})`                                                                                                                                     |
| Autocomplete              | Selecting a module inserts its parameter signature with the caret in the first slot                                                                                              |
| Passing semantics         | By value. A module that assigns a variable does not change the caller's copy. A module that sets `total` internally leaves the caller's `total` untouched after the call returns |

## Rules and limits

<table data-search="false"><thead><tr><th>Rule</th><th>Detail</th></tr></thead><tbody><tr><td>Naming</td><td><code>snake_case</code>, for what the module does. Not <code>T01_module</code></td></tr><tr><td>Scope</td><td>One purpose per module. A module that logs in <em>and</em> navigates forces the next caller to copy it to get half of it</td></tr><tr><td>Nesting</td><td>A module can call another module. Cyclic calls, which would loop forever, are rejected</td></tr><tr><td>Definitions</td><td>Every <code>CALL</code> needs a matching definition. Deleting the project that holds a module breaks every test calling it, and the warning is weak</td></tr><tr><td>Autocomplete lag</td><td>Type <code>CALL</code>, then pause for the module dropdown. Typing the name straight through outruns it and produces a <code>CALL</code> that looks right but isn't linked</td></tr><tr><td>Not a module</td><td>A single <code>Tap</code>. The indirection costs more than it saves</td></tr><tr><td>Not a module</td><td>A popup that can appear anywhere. Those go in <a href="/pages/wl2GqBAWrbDmrlNZGHGT">Memory</a> once and need no step in any script</td></tr></tbody></table>

## Common mistakes

<table data-search="false"><thead><tr><th>What you write</th><th>What happens</th></tr></thead><tbody><tr><td><code>CALL shopease_login</code> typed fast, no pause</td><td>The dropdown never fires and the call isn't linked to a module</td></tr><tr><td>Editing the inlined block under a <code>CALL</code></td><td>It's read-only. Edit the module file</td></tr><tr><td><code># BEGIN login</code> … <code># END shopease_login</code></td><td>Names don't match — the module won't parse</td></tr><tr><td>A module that calls itself, directly or via another</td><td>Rejected as a cyclic call</td></tr><tr><td>Deleting the project that holds a shared module</td><td>Every test calling it breaks, Drizz warns you and blocks deletion until modules are resolved.</td></tr><tr><td><code>PARAM term</code> halfway down the body</td><td>Declare parameters immediately after <code># BEGIN</code></td></tr><tr><td>Expecting a module's <code>SET</code> to change the caller's variable</td><td>Parameters and variables are pass-by-value. It won't</td></tr><tr><td>One module that logs in <em>and</em> navigates to checkout</td><td>The next test needs half of it and copies the whole thing</td></tr></tbody></table>

## Next

* [The shape of a test](/writing-tests/writing-tests.md)
* [Which variable do I use?](/writing-tests/which-variable.md)
* [Memory & blockers](/writing-tests/memory-and-blockers.md)

***

*Last updated: 6 August 2026*
