> ## Documentation Index
> Fetch the complete documentation index at: https://momentic.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing effective AI actions

> A practical guide to writing AI action steps, from rolling up imperative clicks into declarative goals to grouping goals into semantic phases, guarding flows with conditions, and the limits to design around.

[AI action](/core-concepts/agentic-testing) lets you hand Momentic a
natural-language goal instead of a click-by-click series of steps. With an AI
action, Momentic plans the flow, runs it in the browser or app, caches the
resolved steps after the first successful run, and self-heals when a cached step
misses. Each section below covers a pattern, when to reach for it, and the
trade-off.

If you are new to agentic steps, read
[Agentic testing](/core-concepts/agentic-testing) first for the basics, then
come back here for patterns.

## The mental model

A typical Momentic test is written as explicit `click` and `type` steps and
spells out *how* to reach an outcome. Click this, type that. Instead, with AI
action you tell Momentic *what* you want, and then AI figures out the how.
Planning with AI is slow, so the agent caches the steps it resolves on the first
successful run and replays them deterministically afterward, paying the model
cost once instead of on every run.

This changes what you maintain. Instead of a list of steps tied to the current
layout, you keep a short statement of intent. Because your goal names the
outcome rather than the exact path, the test holds up through UI churn, and the
agent re-resolves the steps whenever the UI moves. The rest of this guide
refines that approach: when to split a goal, how to guard it, and what to avoid.

## Rolling up a Google Flights search

This section uses a flight search on
[Google Flights](https://www.google.com/travel/flights) to show the
highest-leverage move in AI action. You take a series of low-level steps whose
only purpose is to reach some state, then collapse them into a single goal that
names that state.

First, here is the search written the imperative way. It spells out every click
and keystroke, so the actual intent of searching two cities for two dates gets
buried in the mechanics, and a reader has to reconstruct it from the steps.

```yaml flight-search.test.yaml theme={null}
fileType: momentic/test/v2
id: flight-search-imperative
url: https://www.google.com/travel/flights
steps:
  - click: The "Where from?" origin field
  - type:
      text: San Francisco
      into: The origin field
  - click: The "San Francisco" airport option in the suggestions
  - click: The "Where to?" destination field
  - type:
      text: New York
      into: The destination field
  - click: The "New York" option in the suggestions
  - click: The Departure date field
  - click: The August 10 date in the calendar
  - click: The Return date field
  - click: The August 17 date in the calendar
  - click: The Search button
```

Here is the same flow written as one declarative goal.

```yaml flight-search.test.yaml theme={null}
fileType: momentic/test/v2
id: flight-search-declarative
url: https://www.google.com/travel/flights
steps:
  - act: |-
      Search for round-trip flights from San Francisco to New York, departing
      August 10 and returning August 17.
```

The second version states the intent directly, so the specific clicks become an
implementation detail the agent works out rather than something you maintain.
When Google restyles the date picker or moves a field, the imperative version
breaks and needs a rewrite. The declarative goal adapts, because the intent has
not changed and the agent simply re-resolves the new UI.

### Assert the end state with a postcondition

Before the agent calls a goal done, it always checks a postcondition and derives
one from the goal if you do not supply your own. You write an explicit
`postcondition` when you need to be precise about the end state, either because
the derived check is too loose or because the goal alone does not tell the agent
what "done" should look like. For a flight search you might require that results
actually rendered, not just that the form was submitted. Either way the
postcondition is a protected check that runs against the live page once the
agent finishes, and the agent cannot skip or edit it.

```yaml flight-search.test.yaml theme={null}
fileType: momentic/test/v2
id: flight-search-guarded
url: https://www.google.com/travel/flights
steps:
  - act:
      goal: |-
        Search for round-trip flights from San Francisco to New York, departing
        August 10 and returning August 17.
      postcondition: |-
        The results page lists round-trip flights from San Francisco to New
        York for the August 10 to August 17 dates, and each result shows a
        price and a departure time.
```

Think of the postcondition as the contract for an `act`, since it spells out
what "done" means for that step. That makes each step a clean checkpoint when
you chain them together, so one `act` hands off to the next at a known state
instead of wherever the goal happened to stop. The
[section on guards](#guarding-a-flow-with-pre--and-post-conditions) below goes
deeper and covers preconditions too.

## Group goals into semantic phases

The rollup above has a counterpart, which is that you should not collapse an
*entire* test into one giant goal either. The sweet spot is a few AI actions
that each map to a phase a reader would recognize. Each `act` becomes a labeled
checkpoint in the trace, so when something fails you know which phase broke.

```yaml browse-products.test.yaml theme={null}
fileType: momentic/test/v2
id: browse-products-by-phase
url: https://www.saucedemo.com/
steps:
  - act: Log in with username 'standard_user' and password 'secret_sauce'.
  - act: Sort the products by price from low to high.
  - act: Open the detail page for the first product in the list.
```

Each phase is small enough that the agent plans it reliably and the cache stays
stable. Aim for goals that a teammate could read as a list of what the test
proves, like log in, sort, then open a product. Avoid one 200-word goal that
buries five distinct outcomes.

## Guarding a flow with pre- and post-conditions

The rollup introduced `postcondition`. Its counterpart is `precondition`, which
guards the *start* of a flow. Both are protected assertions the agent cannot
edit or skip, so they are the honest definition of "did this work". A
`postcondition` is more than a pass/fail gate, because if it is not met when the
agent finishes, the agent treats it as a target and keeps working toward it,
failing the step only if it cannot get there. A flow that "completed" in the
wrong place is therefore either corrected or fails loudly.

```yaml login-guarded.test.yaml theme={null}
url: https://www.saucedemo.com/
steps:
  - act:
      precondition: A login form with Username and Password fields is visible.
      goal: Log in with username 'standard_user' and password 'secret_sauce'.
      postcondition: The product inventory list is shown.
```

Use a `precondition` to fail fast and clearly when setup is wrong (wrong page,
not logged out) instead of letting the agent flail. Add an explicit
`postcondition` whenever the auto-generated one is too loose for a
state-changing goal.

<Note>
  Pre- and post-conditions follow the same rules as [AI
  check](/core-concepts/writing-assertions) steps. Assert structural,
  qualitative facts ("a confirmation is shown"), and never exact counts, prices,
  timestamps, or IDs that change between runs.
</Note>

## Feeding the agent context it cannot infer

The agent only knows what is on screen. Anything external (an invite code, a
specific record to act on, credentials) must come in through
[variables](/core-concepts/variables). Reference them with `{{ env.NAME }}` in
the goal.

```yaml login.test.yaml theme={null}
url: https://www.saucedemo.com/
steps:
  - act:
      goal: |-
        Log in with username '{{ env.QA_USERNAME }}' and password
        '{{ env.QA_PASSWORD }}'.
      postcondition: The product inventory list is shown.
```

## Limitations of AI action

An AI action is a bounded planning agent, not a general program. Designing
within these limits keeps tests fast and deterministic.

### No unbounded or infinite loops

The agent will not "keep clicking Next until the list is empty" forever. It runs
a bounded plan with a retry budget and then stops, and it will not repeat an
ineffective interaction more than a couple of times. If your intent is genuinely
to repeat until some condition, express the bound explicitly and keep it finite.

* Avoid `Keep loading more results until every order is on screen.`
* Prefer `Click "Load more" up to 3 times, then stop.`

### Conditionals only work on the first run

The LLM can evaluate conditional UI - cookie banners, "what's new" modals, A/B
interstitials - on the first run, because it inspects the page in real time. But
once the step is cached, the resolved steps replay deterministically without the
LLM, so the condition is baked in. If the conditional UI appears differently on
a later run, the cache busts and the step heals, re-running the agent to
re-resolve the goal.

## Related

* [Agentic testing](/core-concepts/agentic-testing)
* [act command reference](/reference/commands/act)
* [Writing assertions](/core-concepts/writing-assertions)
* [Variables](/core-concepts/variables)
* [Step cache](/reliability/step-cache) and [Auto-heal](/reliability/auto-heal)
* [Writing maintainable tests](/best-practices/maintainable-tests)
