split method

Future<void> split(
  1. Map<String, Future<void> Function()> branches
)

Forks the scenario: every branch runs, each in its own replay of the whole body — so each branch starts from the exact state this line was reached with, and the flow graph fans out here.

await s.split({
  'pay by card': () async {
    await s.tap('Pay');
    await s.screen('Receipt');
  },
  'payment fails': () async {
    await s.tap('Pay');
    await s.screen('Error dialog');
  },
});

Steps before the split are captured once and shared; anything after the call runs per branch, since by then the paths have genuinely diverged. Splits nest. Under bare flutter test the replays run too, so CI asserts every path.

What a replay does and does not reset. The widget tree is torn down and rebuilt from nothing, so each path starts from a fresh app. Anything outside the tree is not: a seeded repository, a registered singleton, a mock's recorded calls carry from one path into the next.

The body is what re-runs, so the body is where per-path setup belongs:

scenario('Around the shop', (s) async {
  repo.seed();                          // every path gets a fresh one
  await s.pumpWidget(const ShopApp());
  await s.split({ ... });
});

A setUp will not do it. setUp runs once per test and a scenario is one test however many paths it has — which is package:test's own rule, kept rather than bent: a setUp that fired three times for one test would be a surprise nothing in the file could explain, and its tearDown could not follow it (those run when the test ends).

Implementation

Future<void> split(Map<String, Future<void> Function()> branches) async {
  if (branches.isEmpty) return;
  var names = branches.keys.toList();
  var name = names[_state.plan.choose(names)];
  // A new segment: positions restart under the extended choice path, and
  // the branch's first capture wears the label.
  _ordinal = 0;
  _segment++;
  _pendingBranch = name;
  _branchTrail.add(name);
  try {
    await branches[name]!();
  } catch (error, stack) {
    // Annotated where the branch is, so a failure says which path reached
    // it. The original stack rides along, so the report still points at the
    // user's line; nested splits annotate innermost-first and the outer
    // ones leave the message alone.
    Error.throwWithStackTrace(_inContext(error), stack);
  }
}