Testing miniapps
Use small typed ports for unit tests. Use the Miniapp Test Lab when a test needs the real miniapp frame, lifecycle, permissions, or platform services.
Unit-test platform code
Importing the live sdk object is safe, but reading a property before the host
installs a capability session throws. Keep host access at the surface boundary
and pass a narrow typed port into business logic.
Pass sdk.channels at the surface boundary and a plain typed double in the
unit test. Apply the same pattern to storage, projects, chat, or another SDK
capability.
Add a host E2E suite
Pin the SDK and install the runner versions accepted by The AI Platform:
Scaffold the descriptor, deterministic Rstest configuration, dedicated TypeScript configuration, fixtures, and provenance smoke test:
The generated schema-v2 tap.test.json has one profile and positive matrix row
for each declared surface and target. New suites use tests/e2e/**/*.tap.ts,
which keeps host-driven cases out of an application's ordinary unit-test
discovery. The generated Rstest configuration receives that exact effective
testMatch; changing descriptor discovery no longer requires a second,
independent glob edit. In Surface mode, the generated smoke test resets the
fixture before mounting, which restores one-shot HTTP scripts and mutable
fixture state. It then requires a visible, non-empty #tap-root with no
lifecycle error. Add an app-specific loaded-state assertion as well: structural
checks cannot recognize an app-owned error shell. Positive scaffold profiles
capture screenshots with always; denied and error profiles default to
failure-only.
If a surface-target cell has more than one allowed row, suffix exactly one
matrix entry ID with -positive. This distinguishes the loaded capability
journey from scripted HTTP and transport-error recovery rows without denying
the host actions those recovery cases must reach.
Profiles accept default, all-denied, read-only, http-denied, or
deny:<action-id> as permissionScenario. Keep deniedActions as the exact
corresponding value: [], ["*"], ["do:*"], ["network.request"], or the
one named action. synthetic:* values are host-internal diagnostics and are
rejected by doctor before Test Lab.
Add these scripts so the same checks are easy to find locally and in CI:
tap-miniapp-test doctor requires direct compatible runner dependencies and
an exact SDK pin. It warns when these scripts are absent.
Import the host adapter in tests that need The AI Platform:
The adapter keeps Rstest's assertions, hooks, filtering, and reporting. It replaces the Playwright browser fixtures with these host-owned fixtures:
The adapter requires a session created by the Test Lab. Keep ordinary unit and browser-mode suites in normal CI; run host E2E tests from The AI Platform.
Test Lab handles an authorized sdk.notifications.show call with a
deterministic fake. It returns { disposition: 'shown' } without dispatching
an operating-system notification. Denied permission scenarios still reject at
the ordinary host authorization boundary, so cover both the allowed result and
your denied-state UI.
Descriptor fixture files and tap.fixture.seed() or
tap.fixture.http.script() payloads can use the reserved authoring tokens
tap-fixture-workspace-v1 and tap-fixture-channel-v1 wherever a string value
must refer to the selected Test Lab scope. Before the fixture operation is
applied, the host replaces every occurrence, including occurrences inside
storage values, presence state, VFS paths, HTTP URLs, headers, and body
text, with the run's actual workspace or channel ID. The bound payload is
validated again, and reset restores the same bound base state. These tokens are
scope references, so do not use either literal as ordinary fixture content or
as a JSON object key.
For channel-role behavior, seed the exact access fixture before exercising the surface:
When access is omitted, Surface fixture mode defaults to a participating
channel with read and write. Access is included in runtime fixture
snapshots and their digest, so tap.fixture.reset() restores the declared role
exactly. The session-level tap.fixtureDigest instead attests the immutable
descriptor fixture inputs selected for the run. Compare mutation, snapshot,
and reset receipt digests with other runtime realm digests, not directly with
that descriptor-input digest.
To verify retry and error UI without relying on a live endpoint, script a
bounded transport failure before the miniapp makes its exact sdk.http
request:
An HTTP fixture supplies exactly one response or transportError. A
transport error represents failure before a response exists, preserves its
bounded code as MiniAppHostActionError.code, and consumes the exact script
once.
Before connecting the project, validate the declaration, list cases, and inspect the expanded capability matrix:
Matrix capabilities are preflight declarations and coverage requirements, not proof that behavior passed. Only a completed Test Lab run proves the selected surface, profile, and cases executed. Live TAP rows additionally require an authenticated, dedicated host session and should stay separate from fixture-backed surface rows.
Connect the project
- Open the channel that owns the miniapp source.
- On desktop, open Apps > Marketplace > Installed. On mobile, open Workspace Settings > Miniapps > Installed. Select the installed package.
- Open Tests.
- Enter the source root and emitted package root relative to this channel's virtual file system.
- Select Connect test project.
The emitted root must be the exact artifact installed for the selected package. The platform hashes the source and test bundle when it creates the binding, then checks both hashes again before discovery and execution. If the source changes, reconnect to create a new provenance snapshot.
Choose an execution mode
Surface fixture
Surface mode is the fast, fixture-backed default. It mounts the emitted package in the production frame host with versioned workspace, channel, storage, presence, and permission fixtures. Use it for most SDK, lifecycle, and permission tests.
sdk.http.request is the exception: when the run lists the request's exact
origin, TAP sends a real bounded HTTP(S) request through the native transport.
Surface mode never reads the host credential vault. A profile may declare
httpCredentialFixtures for its existing credentialSlots; at launch, Test
Lab binds each declared slot to the selected run alias and exposes only the
declared type, display name, and bounded non-secret metadata through
sdk.credentials.listHttp. Omitted declarations remain invisible. Fixture
metadata uses the vault's exact public shape: bearer {}, Basic
{ username }, header auth { header_name }, or API key
{ placement, parameter_name }. Secret and extra keys are rejected. Request
use still requires credentials.use, the credentials effect, an exact
run-owned alias, and an explicit fixture declaration for that alias. The
http-denied scenario blocks sdk.http.request even when an origin was
entered.
The SDK HTTP origin list is not a browser-wide network sandbox. The Rstest
adapter observes HTTP and WebSocket traffic and emits sanitized diagnostics for
unexpected origins, but it does not intercept browser requests. Host-mediated
sdk.http.request still enforces its declared capability policy. Run every Test
Lab suite only from source and dependencies you trust. The visible Debug action
opens the same fixture-backed surface in a dedicated test window.
Live TAP
Live mode starts a separate automation-enabled The AI Platform process and uses real services in the selected channel. Use it only when deterministic fixtures cannot represent the journey.
The profile descriptor spells this mode "live". After the SDK attaches to the
dedicated process, the Rstest fixture projects the canonical runtime value
"live-tap":
Live test code is trusted local code running as you. It controls the complete dedicated test window and may inspect or change real channel state. Effect grants constrain miniapp host actions; they do not sandbox Playwright code or arbitrary browser networking.
Package-declared native services require a Live TAP profile and a separate,
session-only opt-in. Follow Local services
for the exact nativeServices, action, and effect declarations.
Before a live run:
- review each exact platform action in Real effect grants;
- list only the exact origins required by host-mediated
sdk.http.request; - list only credential aliases required by Live TAP SDK requests; and
- confirm that the selected channel can tolerate the side effects.
The confirmation is single-use and bound to the development binding, channel, actor, and exact effect set. The dedicated browser profile and authenticated CDP broker are destroyed when the run finishes or is canceled. The everyday app process never exposes its debugging port.
Run and debug
- Run all runs the discovered suite headlessly.
- Run selected runs the checked case IDs.
- Run affected passes the listed source paths through Rstest's related-test selection.
- Debug selected runs one selected case in a visible dedicated window.
The Test Lab shows ordered state, normalized counts, infrastructure failures,
and channel-backed history. An assertion failure is failed; a runner, host,
browser, or evidence failure is infrastructure failed.
Evidence and specialist handoff
Every terminal run writes one canonical manifest under the creating channel's virtual file system:
run.json records identity, source and bundle digests, versions, policy,
selection, result counts, artifact hashes, and relative paths. The runtime
writes it last as the canonical index; do not infer run evidence by scanning
filenames. Trace and screenshot files are present only when the selected
artifact policy captures them.
Choose Ask specialist to place a bounded diagnostic request in the same
channel's draft. The request points to report.md,
cases/<safe-task-id>/artifact-summary.json, and the corresponding debug.md.
It deliberately excludes trace.zip from model context.
To curate evidence for a team, enter an authorized Knowledge Garden plot ID and choose Publish evidence. The default copy includes the run manifest, sanitized Markdown report, trace summaries, and debug notes. The channel VFS run remains the source of truth.
Package acceptance
Before pinning a production release, also verify that package CI:
- installs dependencies from the public npm registry in a clean checkout;
- typechecks and runs the complete unit suite;
- builds every declared target;
- assembles and scans the complete package;
- validates the emitted descriptor against
config-schema.json; and - imports every declared expose from its emitted graph.
A passing local Test Lab run is developer evidence. It is not signed Marketplace verification.