> 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/authoring-rules.md).

# Authoring rules

The rules that apply to every Drizz test, in two tables.

|               |                                                                              |
| ------------- | ---------------------------------------------------------------------------- |
| **Platforms** | Android · iOS                                                                |
| **Structure** | Setup → navigate → act → validate → clean up                                 |
| **Wording**   | Use the app's exact visible text, always                                     |
| **Timing**    | Fixed waits only, placed after navigation and network calls                  |
| **Watch out** | `CLEAR_APP` is effectively a no-op on iOS — don't rely on it for clean state |

## Prerequisites

* Drizz desktop app installed and signed in
* A registered app and a project to hold the tests

## Do

<table data-search="false"><thead><tr><th>Rule</th><th>Why it matters</th></tr></thead><tbody><tr><td>One scenario per test, ending in <code>CLEAR_APP</code></td><td>A failure names one feature, and the next test starts from a known state</td></tr><tr><td>Copy the app's exact visible wording</td><td>Paraphrasing is the most common cause of a tap landing on the wrong element</td></tr><tr><td>Disambiguate, don't describe</td><td>Drizz picks one element out of everything on screen. A label, a position in a list, a neighbor or a section narrows it to one</td></tr><tr><td>Add a wait after anything that navigates or calls the network</td><td>Validations retry a few times, which covers a slightly slow screen, not a four-second one</td></tr><tr><td>Set a variable before comparing it</td><td>A number hardcoded last quarter fails when the price changes</td></tr><tr><td>Validate directionally — <code>greater than</code>, <code>less than</code></td><td>Survives price and tax changes; an exact computed total does not</td></tr><tr><td>Validate after anything non-deterministic</td><td>Scroll stops silently at its attempt limit, so the validation is what catches the miss. Applies after <code>MAP_ACTION</code>, after a load-bearing <code>SET</code>, and after a scroll</td></tr><tr><td>Put anywhere-popups in <a href="/pages/wl2GqBAWrbDmrlNZGHGT">Memory</a></td><td>One rule replaces the same <code>IF</code> block in forty tests</td></tr><tr><td>Put flow-specific popups in <code>IF</code> blocks</td><td>An org-wide rule makes every test in the organization pay for a check only one flow needs</td></tr><tr><td>Put anything repeated into a <a href="/pages/92D9ohPqc6eyuPm4vkQJ">module</a></td><td>A login flow that changes twice a year exists in one place</td></tr><tr><td>Quote scroll targets and give a direction</td><td>An unquoted target with no direction doesn't resolve</td></tr><tr><td>Use <code>Tap</code> whenever the element has a visible label</td><td><code>MAP_ACTION</code> is the least deterministic command in Drizz</td></tr><tr><td>Validate a variable's <strong>value</strong> — <code>{{order_id}}</code></td><td><code>order_id</code> without braces is searched for as literal on-screen text</td></tr><tr><td>Store only what's rendered on screen</td><td>Vision AI reads pixels. System timestamps and internal IDs aren't visible to it</td></tr><tr><td>Reset state explicitly on iOS — log out, or reinstall</td><td><code>CLEAR_APP</code> is effectively a no-op on iOS</td></tr><tr><td>Use <code>Tap on the back button</code> for cross-platform back</td><td><code>PRESS_DEVICE_BACK_BUTTON</code> doesn't exist on iOS</td></tr><tr><td>Pick one style per value — <code>{{phone}}</code></td><td><code>&#x3C;phone></code> is inert text. Drizz types the literal characters</td></tr><tr><td>Bind a dataset to any plan whose tests use variables</td><td>A plan with variables and no test data bound can misread <code>SET</code> steps</td></tr><tr><td>Name variables for the moment — <code>total_before</code></td><td>Reusing a name overwrites the first capture permanently</td></tr><tr><td>Put environment and account values in a dataset</td><td>One script covers staging and production</td></tr><tr><td>Work the healed-steps list as a maintenance queue</td><td>A step that heals every run has a bad description</td></tr><tr><td>Delete tests that no longer mean anything</td><td>A green suite nobody trusts is worse than a smaller one people do</td></tr></tbody></table>

## Don't

<table data-search="false"><thead><tr><th>Rule</th><th>Why it matters</th></tr></thead><tbody><tr><td>Chain three scenarios into one long test</td><td>A failure doesn't say which feature broke</td></tr><tr><td>Paraphrase — "Proceed to Pay" for a button that says "Charge Now"</td><td>The tap lands on the wrong element or nothing at all</td></tr><tr><td>Scatter long waits everywhere as insurance</td><td>Hides real timing problems and slows the whole suite</td></tr><tr><td>Compare against a number hardcoded last quarter</td><td>Breaks on the next price or tax change</td></tr><tr><td>Compute an exact expected total by hand</td><td>Same failure, one release later</td></tr><tr><td>Copy the same <code>IF</code> popup block into forty tests</td><td>Forty edits when the popup changes</td></tr><tr><td>Add an org-wide blocker rule for a popup only you see</td><td>Every test in the organization pays for it</td></tr><tr><td>Copy-paste the login flow into every test</td><td>Every test changes when login changes</td></tr><tr><td>Write <code>Scroll until Proceed</code> with no quotes and no direction</td><td>The target doesn't resolve</td></tr><tr><td>Trust a grid-cell gesture without validating after it</td><td><code>MAP_ACTION</code> can hit the wrong thing and still report success</td></tr><tr><td>Reach for <code>MAP_ACTION</code> the first time a tap misses</td><td>Fix the description instead</td></tr><tr><td>Validate a variable's name — <code>order_id</code> as visible text</td><td>Drizz searches the screen for that literal string</td></tr><tr><td>Store a system timestamp or an internal ID</td><td>Not rendered, so nothing to read</td></tr><tr><td>Rely on <code>CLEAR_APP</code> for clean state on iOS</td><td>State carries over to the next test</td></tr><tr><td>Use <code>PRESS_DEVICE_BACK_BUTTON</code> in a script that runs on iOS</td><td>The command doesn't exist there</td></tr><tr><td>Mix <code>{{phone}}</code> and <code>&#x3C;phone></code> for the same value</td><td>One resolves, the other is typed literally</td></tr><tr><td>Run a plan with variables and no test data bound</td><td><code>SET</code> steps can be misread</td></tr><tr><td>Reuse one variable name for two captures</td><td>The second write destroys the first</td></tr></tbody></table>

## The shape that works

```
# 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

Store the price as listed_price
Tap on Add to Cart
Wait Until 2 Seconds

Validate that Item added to cart is visible

CLEAR_APP com.shopease.android
```

Five beats, in order:

1. **Setup** — get to a known state, with a `CALL` to a shared login module.
2. **Navigate** — reach the screen under test.
3. **Act** — the scenario itself.
4. **Validate** — assert the outcome.
5. **Clean up** — `CLEAR_APP` so the next test starts fresh.

## Common mistakes

<table data-search="false"><thead><tr><th>What you write</th><th>What happens</th></tr></thead><tbody><tr><td><code>Tap on the button</code></td><td>Ambiguous. Resolves differently between runs</td></tr><tr><td><code>Wait until the page loads</code></td><td>Not a fixed duration. Write <code>Wait Until 5 Seconds</code></td></tr><tr><td><code>Scroll until Proceed</code></td><td>No quotes, no direction. Quote the target and say <code>down</code></td></tr><tr><td>One test covering login, search, cart and checkout</td><td>When it fails you don't know which feature broke</td></tr><tr><td><code>CLEAR_APP</code> as your iOS teardown</td><td>Does effectively nothing on iOS. State carries over</td></tr><tr><td><code>PRESS_DEVICE_BACK_BUTTON</code> in a cross-platform test</td><td>Doesn't exist on iOS</td></tr><tr><td>A 20-second wait to fix a flaky step</td><td>Hides a real timing problem and slows the whole suite</td></tr><tr><td><code>MAP_ACTION</code> on an element that has a label</td><td>The least deterministic command in Drizz, used for no reason</td></tr></tbody></table>

## Next

* [The shape of a test](/writing-tests/writing-tests.md)
* [Recipes](/writing-tests/recipes.md)
* [Known limitations](/reference/known-limitations.md)

***

*Last updated: 6 August 2026*
