.agents/skills/writing-tests/SKILL.md
The purpose of a test is confidence that a workflow or contract works for a Studio user, CLI user, package consumer, or renderer consumer.
Test the feature at the widest stable boundary that gives useful confidence. Prefer one complete integration workflow; use focused unit tests for genuinely complex logic and E2E tests for critical cross-process workflows.
Test count, assertion count, file count, and coverage percentage are not quality metrics. One rendered frame, final artifact, or persisted-source assertion can give more confidence than many narrow unit assertions.
A test that stays green when the feature is disconnected is not useful primary coverage. For a user-facing or cross-layer feature, a private helper unit test must not be the only coverage.
Answer these questions first:
Test type is determined by the boundary, not the directory or runner. A Bun test can be an integration test, while a Playwright test can still be coupled to implementation details.
Use these primary boundaries:
| Change | Preferred primary coverage |
|---|---|
| Studio interaction | Playwright action followed by a visible, source-file, persistence, URL, or rendered outcome |
| Studio Server or codemod | Realistic source fixture through the route or handler, followed by the resulting source or event |
| Core, Player, or Media | Mount or render the real component with real neighboring modules and representative media |
| Web renderer or visual effect | Browser render, image comparison, pixel probe, or produced frame |
| CLI | Execute the command and assert exit code, output, and filesystem effects |
| Renderer, SSR, templates, or packaging | Public API through packages/it-tests or a realistic temporary project |
| Lambda or cloud orchestration | Full orchestration with only the remote provider mocked or faked |
| Pure math, parser, serializer, protocol transformation, or state machine | Focused, preferably table-driven unit tests |
Do not force every change through Playwright. A route-level or filesystem integration is often the best stable boundary. Reserve full E2E tests for important risks that cross browser, server, filesystem, HMR, or process boundaries.
Prefer outcomes a user or integrator can observe:
Prefer a positive final outcome proving the path ran. does not throw, not visible, not present, or “error absent” is weak as the only proof. First assert the expected reorder, file mutation, request, rendered output, persisted state, or successful result; add the negative assertion only when it provides additional confidence.
When generated code is the user-visible artifact, source assertions are legitimate. Assert the meaningful output and avoid coupling to irrelevant formatting unless formatting itself is part of the contract.
Locate controls as users find them: getByRole, getByLabel, getByText, and accessible names. Prefer semantic state matchers such as toBeDisabled() and toBeFocused() over inspecting attributes.
Avoid using these as assertion targets when an external result exists:
If no semantic locator exists, improve accessibility or add a stable interaction hook. A CSS selector or test attribute may be used to perform an action against third-party or otherwise inaccessible internals, but the assertion should still target an observable result.
Do not apply a blanket ban to styles, geometry, DOM output, or pixels. In Remotion, visual output is often the product.
For renderers, effects, layout, media, and generated source, the following may be public contracts:
When practical, test the rendered result rather than only an intermediate style object. Focused geometry unit tests remain appropriate for edge-heavy layout algorithms, especially when paired with one browser or visual test that proves the result is wired into rendering.
Use real Remotion modules, routes, filesystem operations, servers, components, and browser rendering by default. Mock or fake the boundary Remotion does not own, such as AWS, an unavailable remote service, time, or a nondeterministic external dependency.
Do not mock internal production modules or neighboring owned packages merely to make setup easier. If an internal fake is unavoidable, explain why the real boundary is impractical and ensure another test covers the wiring that the fake bypasses.
A good integration test may use a temporary directory, real fixture project, real server route, real package entry point, or real media file while replacing only the external provider.
Model a test on a coherent manual workflow: one setup, followed by as many actions and assertions as that workflow needs.
beforeAll steps.In packages/example/e2e, starting Studio is expensive and the suite runs serially. Title, visibility, navigation, interaction, and final-result assertions that form one workflow should not each restart Studio. Combine them into one independently runnable test instead of sharing process state through beforeAll.
Before adding an assertion, ask whether an adjacent stronger assertion already proves it. One strong outcome is preferable to several redundant intermediate observations.
Unit tests are valuable for:
For a cross-layer feature, first add or identify the primary integration workflow. Add unit tests only for important edge cases that the broad test cannot cover economically.
Do not extract or export a trivial helper solely to make it testable. If the helper works but the feature entry point can stop calling it without failing the test, that test is not sufficient primary coverage.
It is acceptable to add no automated test when the only affordable test would assert implementation details and the regression risk is low. Report explicit manual verification rather than adding a misleading green test.
For a bug fix, verify when practical that the new test detects the regression:
Use this mutation heuristic:
If the tested helper still works but the feature entry point stops calling it, would this test fail?
If not, move the primary test to a wider boundary.
Report Red/green verified in the final response. If verification is impractical, state why rather than implying it was performed.
Weak primary coverage:
expect(getNewCompositionDefaults(selected)).toEqual(selected);
Stronger workflow:
Focused units may still cover unusual fallback inputs if they represent meaningful independent logic.
Weak primary coverage:
expect(getCreateVideoHelp()).toContain('--help');
Stronger integration:
create-video --help through the real CLI entry point.Weak primary coverage:
expect(getItem(items, 'rename').disabled).toBe(true);
Stronger workflow:
Weak coverage:
await expect(page.getByText(error)).toBeHidden();
Stronger coverage performs the operation and asserts its positive result, such as the new sequence order, source mutation, request, persisted value, or rendered output. Error absence may then be a secondary assertion.
Use these as models for test shape and boundary selection:
packages/example/e2e/error-overlay.test.mtspackages/example/e2e/suppress-rebuild.test.mtspackages/media/src/test/player-preview-frame-accuracy.test.tsxpackages/lambda/src/test/integration/deploy-site-from-bundle.test.tspackages/web-renderer/src/test/opaque-layer-over-fading-layer.test.tsxFollow specialized skills such as web-renderer-test for subsystem mechanics while retaining the principles in this skill.
Before finishing:
When reporting test changes, include:
Red/green verified, or why it was impracticalPrinciples adapted from: