A successful build proves that the source was accepted. It does not prove that a button works, a request was validated or an island adopted the server DOM. Test those boundaries directly. The learning examples use their actual exercise and solution files as browser test inputs.
Test user-visible behavior#
A Playwright check can express the counter's public contract without depending on Forma's internal signal implementation:
import { test, expect } from "@playwright/test";
test("counter responds to input", async ({ page }) => {
await page.goto("http://127.0.0.1:4174/lab/native/?count=7");
await expect(page.locator("#native-count")).toHaveText("7");
await page.getByRole("button", { name: "Add one", exact: true }).click();
await expect(page.locator("#native-count")).toHaveText("8");
await page.reload();
await expect(page.locator("#native-count")).toHaveText("7");
});This excerpt requires the native lab to be running. It checks both interaction and the state boundary: the URL still seeds 7 after a reload, because clicking does not save anything. Your own test should target your application's address and behavior. Prefer a test-owned server with an isolated port and state directory; accidentally reusing an old preview can test stale code.
Prove native rendering without JavaScript#
Request HTML using the test's HTTP client and assert that the expected value is
present. Also create a browser context with javaScriptEnabled: false and submit
ordinary forms. A screenshot after JavaScript runs cannot distinguish native HTML
from an empty shell filled by the browser.
For a no-JavaScript route, check for executable scripts and module preloads.
Forma's inert application/json state envelope is data, not executable code.
Do not write an assertion that rejects every script element regardless of type.
Check adoption by identity#
Delay the application's script response, capture an element from the server HTML, then release the script, exercise an island and compare the element object with the one still in the document. This detects a replacement that a text assertion alone would miss. The native lab performs this check for its counter.
For lists, type into an uncontrolled input, reorder rows and confirm that the same business item retains its input and DOM element. Indexed updates should change a single row's data without losing unrelated row state.
Test rejected input and persistence#
Unit-test request parsing at boundaries, then test actual HTTP behavior: invalid
numbers, extra or duplicate values, missing records, concurrent edits and server
errors. The browser's min, max and required attributes improve the interface;
they do not replace server validation.
Use temporary data directories in persistence tests. Write through the application, restart its process and read the result again. A value surviving a rerender is not evidence of durable storage. Define conflict behavior before testing it.
For Field Notes, type a draft and first assert that the confirmed count stays unchanged. Save, wait for server acceptance, then check the count. Verify that rejected writes preserve stored data and that Reload inventory fetches current data instead of retrying a stale POST. Only a fresh page after a process restart proves that the accepted value came back from disk.
Keep security response checks focused#
Check that separately rendered native responses carry different nonces and retain
Cache-Control: no-store, the enforced CSP and content-type protections. Keep
the nonce in the body paired with the response's policy. Test malformed input
through the public route, not by disabling the guard that rejects it.
The framework's release probes qualify its implementation. They do not audit your application's authentication, authorization, storage or business rules.
Run the same path in CI#
Install from committed npm and Cargo locks, compile browser/native assets together, run Rust unit tests, then test the application with the browser engines you support. Keep worker counts appropriate to the runner; this site uses at most two browser workers and one for adoption checks. Retain traces and failed assertions rather than replacing a failure with an unexplained retry.
Automated accessibility checks complement keyboard, zoom, screen-reader and visual review. See the deployment guide for the final artifact check.