---
title: "Test and debug a campaign"
description: "Run a test execution from the canvas, read the execution history to find which node failed and why, and see what editing a campaign does to runs in flight."
canonical: https://docs.afp.monster/guides/test-and-debug-campaigns
updated: 2026-08-19
pageType: guide
---

A test execution walks your saved graph without touching Facebook or Instagram: click **Test** on the canvas, optionally type the message the campaign should treat as incoming, and the canvas switches to the **Executions** view with the path the run took highlighted. Real runs land in the same list, so one view tells you which node ran, which node failed, and why the run stopped.

## What you need first

- A campaign saved on the canvas with at least one trigger node. A test execution runs the campaign's current saved version, so unsaved edits are not tested. See [Build a campaign on the canvas](/guides/build-a-campaign).
- No page setup beyond that. A test execution is not bound to a connected page and makes no calls to Facebook or Instagram.

## How do I run a test execution?

1. Open the campaign on the canvas and click **Save** if anything is unsaved.
2. Click **Test**.
3. Type the sample message the run should treat as the incoming comment or DM. Leave it empty and the run behaves as though the message text were the word `test`.
4. Click **Run test**.
5. The canvas switches to the **Executions** view and the new execution appears at the top of the list.
6. Click the execution to overlay its path on the canvas: nodes it passed through are marked as visited, the node it stopped on is marked as current, and nodes it never reached are dimmed.

![The test dialog on the campaign canvas, with a text box for the message the test execution should treat as the incoming comment](../../../assets/screenshots/guides/test-and-debug-campaigns/test-dialog.webp)

The whole traversal finishes before the canvas answers, so there is no animation of a run stepping through nodes — what you get is the finished state.

## How does a test execution differ from a real one?

A test execution uses the same engine and the same graph, and it stops for the same reasons. What it skips is every call that would reach a real person.

| Behaviour | Test execution | Real execution |
|---|---|---|
| What starts it | The **Test** button | A comment or DM matching the trigger |
| Recipient | A synthetic test recipient | The person who commented or messaged |
| Connected page | None | The page the campaign is bound to |
| DMs, comment replies, hides, deletes | Recorded as a description of what would have been done; nothing is sent | Sent through Facebook or Instagram |
| Tags | Not added, not removed | Applied to the contact |
| `delay` nodes | Recorded as skipped, and the run ends there | Scheduled, resumed when the wait elapses |
| A `condition` set to wait for a reply | Parks and stays parked — no reply can arrive | Resumes when the person replies |
| In the execution list | Yes, marked as a test | Yes |

Because a test execution ends at the first `delay` node, it tells you nothing about the nodes after that delay. Remove the delay temporarily, or verify that stretch with a real comment.

## What does the execution history tell me?

The **Executions** view lists the 20 most recent executions for the campaign, newest first, tests and real runs together. Each row carries whether it is a test, whether it is still active, the recipient, when it started, when it completed, the node it sits on, and an error message if it has one.

Selecting an execution loads the graph version that run started on and marks every node it visited. Each visited node records its type, when it ran, and either a description of what the node did or the error it raised — which is how you tell "the DM node sent" from "the DM node failed".

The list does not refresh itself. Use the refresh control on the panel to re-pull a run that is still active.

### What does the path record at each node?

| Entry against the node | What it means |
|---|---|
| Trigger fired | The run started here. Every execution has this as its first entry |
| A description of the action | An action node ran: a DM sent, a comment replied to or hidden, tags changed |
| Waiting for reply | The run parked on a `condition` set to wait for a reply and has not been woken |
| Scheduled delay, with the wait in seconds | A `delay` node scheduled the resume and stopped advancing |
| Took arm 2 | A `random_split` picked that arm, and the run continued down it |
| Flow stopped | A `stop` node ended the run deliberately |
| An error message | The node raised an error. The run does not continue past it |

```d2 title="What each kind of next node does to a run: continue, park, schedule, complete, or stop with an error" data-wide="true"
direction: right
starts: Execution starts at the trigger
next: Next node {shape: diamond}
visited: "Recorded as visited,\nrun continues"
parked: "Parked: still active,\ncurrent node is the condition"
scheduled: "Scheduled: still active,\nresumes when the wait elapses"
completed: Completed
cycle: Stopped with a cycle error
errored: "Still active, error recorded,\nrun does not continue"
starts -> next
next -> visited: "trigger, action\nor tag node"
visited -> next
next -> parked: "condition set to\nwait for a reply"
next -> scheduled: delay node
next -> completed: "stop node, or an output\nwith nothing connected"
next -> cycle: a node already visited
next -> errored: action raised an error
```

### How do I read the state of one execution?

| What you see | What it means | What to do |
|---|---|---|
| Not active, with a completion time | The run reached a `stop` node, or an output with nothing connected after it | Nothing. This is a normal finish |
| Still active, current node is a `condition` | The condition is set to wait for a reply and no reply has arrived | Wait for the reply. A test execution stays here, because no reply can reach it |
| Still active, current node is a `delay` | The wait is scheduled and has not elapsed yet | Wait for the delay to elapse |
| Still active, with an error message on the last visited node | An action failed. The run records the error and does not continue past that node | Fix the cause, then start a fresh run — this one does not resume by itself |
| Not active, error names a cycle at a node | The path returned to a node it had already visited. Every node runs at most once per execution | Rework the branch so the path never revisits a node |
| Not active, error says the scheduled step did not run | A `delay` was scheduled but its resume never happened | Test the branch again. If it repeats, raise it with support |
| Not active, error says the person did not reply within 7 days | The run sat on a `condition` waiting for a reply for a week | Nothing. Expiry is the expected end for conversations nobody continues |

Retries are narrow. A resume after a `delay` is attempted up to three times before the run is marked failed with the last error. An action that fails while the run is walking the graph is not retried at all, and no execution is ever re-run from the start.

## How long does an execution wait for a reply?

Seven days. A run parked on a `condition` that waits for a reply is closed as failed after seven days of silence, with a message saying the person did not reply, and test executions expire the same way.

A scheduled resume that never happens is caught separately: once it is more than an hour overdue, the run is closed as failed with a message saying the step did not run.

Both deadlines are checked by a periodic sweep rather than at the instant they pass, so a run can still show as active for a while after its time is up.

## What happens to a run in flight when I edit or delete the campaign?

Saving the campaign writes a new version of the graph. Runs already in flight stay pinned to the version they started on and finish against it, so editing a campaign never rewires a conversation that is already under way. New events start runs only on the current saved version, and a trigger node that survives only in an older version never fires again.

Older versions are kept for as long as runs still reference them, which is why an execution you open in the **Executions** view can show nodes that are no longer in the graph you are editing. Highlighting can also fail to line up on runs old enough that the version they used was never recorded; the panel falls back to the current graph for those.

Deleting the campaign removes every saved version of its graph and every execution recorded against it, active runs included.

:::caution
Deleting a campaign is not reversible and takes the execution history with it. A run that was waiting out a delay does not resume — nothing further is sent for it.
:::

## Why did nothing happen when I tested?

| Symptom | Cause | Fix |
|---|---|---|
| **Test** reports that the campaign has no trigger | The saved graph contains no trigger node | Add a trigger node, click **Save**, then test again |
| The run completes after one or two nodes | The output it needed has nothing connected to it, so the run ended there | Connect the branch and save |
| The `send_dm` node shows as visited but no message arrives | Test executions record actions instead of sending them | Confirm delivery with a real comment or DM on the connected page |
| Nothing arrives on a real comment either | The campaign is saved but not active, or the comment did not match the trigger and the condition | Check the **Active** toggle, then read [Match comments with keyword lists](/guides/keyword-lists) |
| A second comment from the same person produces nothing | One person cannot have two active runs of the same campaign at once, so the second matching event is skipped | Wait for the first run to finish, or test with a different account |
| The run stops on an action node with an error | The action failed against Facebook or Instagram, and a run never continues past a failed action | Fix the cause, then start a fresh run |

## Related

- [Execution states](/reference/execution-states) — every state a run can be in, and how it leaves each one.
- [Match comments with keyword lists](/guides/keyword-lists) — why a `condition` took **Match** or **No Match**.
- [Node types](/reference/node-types) — what each node does when the run reaches it.
- [Build a campaign on the canvas](/guides/build-a-campaign) — the graph you are testing.
