Using Claude Code, Cursor, or another coding agent? Paste this into your prompt to run cross-browser and real-device tests, debug sessions, and wire up CI on the TestMu AI cloud:
@import lets you extract a repeating flow into a helper file and reuse it from many tests. Helpers are first-class _test.md-style files that live alongside your tests. Editing one helper updates every test that imports it.
This page covers helper files, the @import syntax, the rules the resolver enforces, and how recordings work across imports. To learn the file format, see Writing test.md files. To learn how runs and replays work, see Running test.md files.
Why split a test
A few common cases where splitting pays off:- Login. Almost every test starts logged in. Put the login flow in
helpers/login.mdand@importit from every test. - Setup. Visiting a dashboard, switching tenants, accepting a cookie banner — pull these out so a single update fixes every test that relies on them.
- Long regression flows. A 30-step checkout test is hard to read and harder to debug. Split it into 4–5 helpers describing each phase (browse, add-to-cart, checkout, payment, confirmation).
- Negative test cases. Share a setup helper between the happy-path test and the negative-path test, so the only thing each test owns is the assertion that distinguishes them.
Helper files
A helper file is any.md file whose name does not end in _test.md. There is no kane-cli new-helper command — just write the file:
helpers/login.md. It can be referenced from any test:
kane-cli testmd run ./helpers/login.md is rejected because the filename does not end in _test.md. Helpers are only reachable through @import from a test.
@import syntax
@import is a step body. It replaces a prose objective in a step.
- The step body must contain
@importand nothing else. Mixing prose and@importin the same body is a parse error. <path>may be relative or absolute. Relative paths resolve against the directory of the importing file, not against your shell’s working directory.- The imported file must exist; missing paths are a parse error.
- The
yamlblock of an@importstep may only containoptional. Any other key is rejected.
How paths resolve
Path resolution is relative to the file that contains the@import, never to your shell:
checkout_test.md imports ../../helpers/login.md, the path is relative to tests/e2e/, so it resolves to helpers/login.md. When login.md imports ./submit-button.md, the path is relative to helpers/, so it resolves to helpers/submit-button.md.
You can also use absolute paths:
What @import does at run time
When the resolver hits an @import step, it inlines every step from the imported file into the run, in order, at that position. The imported file’s frontmatter — except for variables and context — is not merged into the run. The result is a flat list of steps from the root file’s perspective.
A test like this:
login.md containing two steps, runs as four steps in total: two from the helper, then “Open settings”. Result.md reports the import as one entry that summarises the helper’s outcome.
Rules the resolver enforces
The resolver catches structural problems at parse time, before any browser launches. The full list:
There is no built-in depth limit — helpers can import helpers can import helpers. In practice keep nesting shallow; deeply chained helpers are hard to read and hard to debug.
Optional imports
A root-level@import step can be marked optional in the same way a prose step can:
Result.md entry is suffixed with (optional).
Optional is intentionally not allowed on nested @import steps — only the root test decides which imports may fail. Helpers cannot decide on their own that they may be skipped.
What propagates through @import
Some settings travel with the import; others are run-wide and apply only at the root.
Propagate to imported steps:
variables— the root file’s variables (and any added by--variables-file/--variables) are visible inside helpers. A helper can reference{{tester_email}}if the root test defines it.global_contextandlocal_context— context is shared across the whole run.- Per-step settings on an objective inside a helper apply to that step.
- Chrome settings:
target,chrome_profile,cdp_endpoint,ws_endpoint,headless. mode(actionvstesting).on_lock_conflict.- Authentication.
Variables across imports
Variables are namespaced flat across the whole run — a single map merged at the root. A helper sees whatever variables the root configuration produces.{{tester_email}} and {{tester_password}} directly because the root test defined them. You can also define defaults in the helper’s frontmatter; the root test’s values override them.
Variables set on an individual step in the root test are visible only on that step — they do not bleed into the helper that follows. If you need a value visible inside a helper, put it in the root frontmatter, not in a per-step yaml block.
Helper outputs
A helper imported at multiple call sites in the same root test records each call site independently. The same helper imported by step 2 and step 4 produces two separate recordings — one per call site — because the browser state on entry is different. The recording for each call site lives next to the helper file:- The helper file’s stem (
login) - The root test’s stem (
checkout) - The index of the importing step in the root file (
2,4).
login.md imported by checkout_test.md and dashboard_test.md produces helper-output-login-checkout-2/ and helper-output-login-dashboard-1/. They are independent recordings on disk and replay independently.
Result.md inside a helper-output-... directory has the same shape as a top-level Result.md. Open it in an editor or Markdown viewer to inspect what the helper did at that call site.
Like the test’s own output-<stem>/, helper-output-... directories are safe — and recommended — to commit to git.
Editing a helper
When you edit a step in a helper:- The cache for that step inside every call site invalidates.
- Subsequent steps in the same helper invocation also re-author (the same “rest of the file” rule from Running test.md files applies inside helpers).
- The root tests’ steps that come after the
@importalso re-author, because the helper changed what the browser looks like when control returns to the root test.
Sharing helpers across projects
There is no built-in command to share a helper across two checkouts. Sharing is a filesystem operation:@import paths resolve relative to the importing file, the same helper layout works in both projects without rewriting the imports. As long as common/login.md exists next to each project’s tests, @import ../common/login.md works.
Worked example
A small suite with a shared login helper and two tests that use it:helpers/login.md
tests/checkout_test.md
tests/dashboard_test.md
helpers/login.md re-authors the login steps and everything after them in both checkout_test.md and dashboard_test.md on the next run.
Next steps
- Writing test.md files — frontmatter, step syntax, variables.
- Running a test.md — the run command, flags, replay model, output.