---
title: Behaviour registry
description: Inspect Pest Flow nodes, identifiers, source locations, and runtime timing.
---

`Pest\Flow\FlowRegistry` exposes the behaviour declarations loaded in the current PHP process. Use it from PHP code when you need to inspect the feature tree or the steps recorded while tests run.

## Read the registry

~~~php
use Pest\Flow\FlowRegistry;

$features = FlowRegistry::features();
$rules = FlowRegistry::rules();
$scenarios = FlowRegistry::scenarios();
$steps = FlowRegistry::steps();
~~~

Each method returns a list in declaration order:

| Method | Returns | Contents |
| --- | --- | --- |
| `features()` | `list<FeatureNode>` | Top-level feature declarations. |
| `rules()` | `list<RuleNode>` | All rules, flattened in feature and declaration order. |
| `scenarios()` | `list<ScenarioNode>` | All scenarios, including standalone scenarios. |
| `steps()` | `list<StepNode>` | Step declarations recorded by scenarios that have started, including steps skipped after a failure. |

You can also walk the tree from a feature: `FeatureNode::rules()`, `RuleNode::scenarios()`, and `ScenarioNode::steps()` return each node's children.

## Node fields

The node objects expose their data as public properties. Feature, rule, and scenario tags are read
with `tags()`:

| Node | Properties | Child access |
| --- | --- | --- |
| `FeatureNode` | `id: string`, `name: string`, `source: SourceLocation`; `tags(): list<string>` | `rules(): list<RuleNode>` |
| `RuleNode` | `id: string`, `name: string`, `source: SourceLocation`, `feature: FeatureNode`; `tags(): list<string>` | `scenarios(): list<ScenarioNode>` |
| `ScenarioNode` | `id: string`, `name: string`, `source: SourceLocation`, `rule: ?RuleNode`, `status: ExecutionStatus`, `duration: ?float`, `exception: ?Throwable`; `tags(): list<string>` | `steps(): list<StepNode>` |
| `StepNode` | `id: string`, `type: StepType`, `description: string`, `source: SourceLocation`, `status: ExecutionStatus`, `duration: ?float`, `exception: ?Throwable` | None |

`SourceLocation` provides `file: string` and `line: int` for the DSL declaration. For a feature, rule, or scenario, that is where its helper was called. For a step, it is the line where `given()`, `when()`, or `then()` was called.

Feature, rule, and scenario tags are available from each node's `tags()` method. The method returns
tags declared directly on that node; parent tags are inherited by child scenarios for Pest group
filtering. See [tags and filtering](/pest-flow/tags).

`StepType` is a backed enum with `Given`, `When`, and `Then` cases. Its string values are `given`, `when`, and `then`.

`ExecutionStatus` is a backed enum with `Pending`, `Running`, `Passed`, `Failed`, and `Skipped` cases. Its string values are `pending`, `running`, `passed`, `failed`, and `skipped`. A scenario or step that has not run remains `Pending`. A failing step stores its thrown `Throwable` and duration; Pest Flow rethrows that same exception so Pest retains its normal failure behavior. Steps declared after the failure are present with `Skipped` status and no duration or exception. A Pest skip exception marks the active step and scenario as skipped.

Durations are floating-point seconds measured with a monotonic high-resolution clock when available. A duration is `null` until execution completes. The scenario duration covers step declaration collection and execution; each step duration covers its callback only.

## Runtime identifiers

Each node gets a path-like ID built from its name and its parent. For example:

~~~text
Feature:  Contractor activation
Rule:     Only compliant contractors may activate
Scenario: Activates a compliant contractor

contractor-activation
contractor-activation/only-compliant-contractors-may-activate
contractor-activation/only-compliant-contractors-may-activate/activates-a-compliant-contractor
~~~

The ID slug lowercases ASCII letters, replaces each run of characters other than `a-z` and `0-9` with a hyphen, then trims hyphens from the ends. A name with no ASCII letters or digits uses `node-` followed by the first 12 characters of its SHA-256 hash.

If sibling nodes produce the same ID, Pest Flow appends `-2`, `-3`, and so on to keep each sibling unique. For example, two rules named `Can activate` under one feature receive `can-activate` and `can-activate-2`. Suffixes depend on declaration order. The same names declared in the same order produce the same IDs between runs.

Standalone scenarios have no feature or rule parent, so their IDs are based on the scenario name at the root.

## When nodes are available

Feature, rule, and scenario nodes are registered as Pest loads the test definitions. Step nodes are created when their scenario runs, before any step callback executes. Pest Flow collects the declarations first so it can retain later steps as `Skipped` if an earlier callback fails.

~~~php
// During test collection, a declared scenario is already in the registry.
$scenario = FlowRegistry::scenarios()[0];
$scenario->steps(); // empty until this scenario executes

// Inside a Then step, all declared steps are in the registry.
FlowRegistry::steps();
~~~

`FlowRegistry::steps()` aggregates recorded steps by scenario declaration order, with each scenario's steps in declaration order. If a scenario runs more than once in the same process, its previous step list and execution metadata are cleared before the next run. If a step throws, its exception and duration remain visible, earlier steps remain `Passed`, and later declared steps are `Skipped` without being executed.

The registry is static and process-local. It does not aggregate data across separate PHP processes or provide a reset/reporting API. Parallel test workers therefore have separate registry state. A scenario that Pest never enters, including one skipped by a hook before its callback starts, remains `Pending` in the registry.

For a live example that inspects IDs and steps from a test, follow [the registry walkthrough](/pest-flow/walkthroughs/inspect-registry). See the [DSL reference](/pest-flow/api) for how declarations are created.
