Verification
CLI v3 uses two deliberately different verification boundaries: complete product journeys against an exact isolated development revision, followed by a narrow read-only confirmation of the exact production release. Do not create opencloud.verify.yaml or another verification DSL.
Verify the candidate
Exercise the exact isolated development revision before promotion:
opencloud app dev request "$APP_DIR" /
opencloud app dev data "$APP_DIR" items create \
--values '{"title":"Synthetic item"}'
opencloud app dev invoke "$APP_DIR" summarize --body '{"example":true}'
opencloud app dev requests "$APP_DIR"
opencloud app dev verify "$APP_DIR"Use synthetic development data only. Exercise every declared Function through its intended path: direct invocation or browser action for ordinary Functions, enqueue for queue consumers, and synthetic injection for inbound email. Inspect the correlated result. Development verification is bound to the active source revision; any later source or migration change requires another sync and verification receipt.
Verify the active release
Run the authoritative durable server gate:
opencloud app verify "$APP_ID"The production operation checks only:
- that the verification's recorded deployment is still the active release;
- that its immutable artifact still has the recorded digest and manifest;
- that the deployment SDK pin matches that exact manifest; and
- that the canonical app route answers a
HEADrequest (or, for a private app, redirects thatHEADrequest to the canonical Auth origin).
It does not load the production document, run Chromium or the external E2E specification, create verifier identities, or read or mutate application data. It writes only the durable control-plane operation and verification result. The development receipt remains the authoritative product-behaviour evidence.
CLI v3 intentionally exposes no local smoke, browser, session, or verification-file substitute for this gate.
External browser specifications
Put product-level browser tests in tests/opencloud.e2e.js. This file is included in the immutable artifact but is outside frontend.directory, so it is never served to app visitors and cannot install a production mutation hook.
import { test, expect } from "@opencloud/test";
test("REQ-001 creates and reloads an item", async ({ page, uniqueValue }) => {
const title = uniqueValue("Browser item", 60);
await page.getByRole("button", { name: "Add item" }).click();
const form = page.getByRole("form", { name: "Add item" });
await form.getByLabel("Title", { exact: true }).fill(title);
await form.getByRole("button", { name: "Save", exact: true }).click();
await expect(page.getByText(title, { exact: true })).toBeVisible();
await page.reload();
await expect(page.getByText(title, { exact: true })).toBeVisible();
});Every title begins with a stable REQ-### identifier. A test must perform at least one trusted UI action and one trusted assertion. test.skip, test.only, raw network clients, backend routes, page.evaluate, route interception, direct navigation, and script injection are rejected during bundling.
The specification runs in a blank, network-isolated Chromium controller. It receives frozen proxy objects; only validated commands cross into the real app pages. Test code cannot read app globals, cookies, SDK clients, bearer tokens, or response bodies and cannot mock the application under test.
Available fixtures are:
page/ownerPage: the primary authenticated user;memberPage: a distinct admitted user intended for collaborator journeys;secondOwnerTab: a second page in the owner's browser context;unrelatedPage: a third signed-in development identity that can load the test app but has no implicit app-authored ownership or team membership;marker: a unique string for the current test. DifferentREQ-###tests in the same verification run receive different markers;uniqueValue(prefix, maximumLength): returns a bounded value that preserves the current test's unique suffix. Use it for fields with UI or database length limits instead of manually truncatingmarker;clickIfVisible(locator): clicks an optional cleanup control only when it is visible and returns whether it clicked. Scope the locator first when labels repeat;files.tinyPng,files.tinyPdf, andfiles.tinyText: bounded fixtures. The PDF contains the textTOTAL 12.00; the text file containsopencloud deterministic verification. Ground content assertions in these actual fixture bytes, not their filenames or an assumed sample sentence; andexpectResponseStatus(page, path, status, action): a bounded status check around a visible action.
This identity topology exists only in isolated development. Establish any app-authored team membership through visible UI, then use unrelatedPage to challenge owner- or team-scoped RLS. Do not expect it to be denied by an equal-member using (true) business policy because that policy intentionally shares records with every admitted app user.
Each development test starts with a fresh migration-replayed schema, isolated managed Files, Function and background-job namespaces, synthetic users, and browser contexts. secondOwnerTab shares only the current test's owner context. No database row, object, Function revision, page, dialog, or browser state leaks into another test. OpenCloud removes the sandbox after the test even if UI cleanup fails. Visible cleanup remains good test discipline, but it is not protecting production: these tests never run against production data.
Development runs up to five tests concurrently by default. Set parallelism to an integer from 1 through 10 in the verify_dev_session request to override one run; operators can set OPENCLOUD_E2E_PARALLELISM to change the default. Parallel execution never means shared test data.
Locators support role, label, text, test ID, placeholder, and CSS selection, including the same selectors nested below another locator, plus first, last, nth, and filter({ hasText }). Accessible names, label/text/test-ID/ placeholder queries, and hasText accept a bounded string or RegExp. Read-only queries cover count, visibility, enabled/checked state, value, text, and attributes. Actions support click, double-click, fill, check, select, key press, focus, and fixture upload. Assertions cover visibility, enabled/checked/focused state, count, text, value, attribute, URL, and rendered toHaveMinimumSize({ width, height }). The size assertion is intended for dynamic phone controls that appear only after an interaction, such as generated checkboxes. Bounded scalar toBe, toEqual, toContain, toBeTruthy, and toBeFalsy assertions are available for values returned by read-only queries, but are supplemental: every test still needs a locator or page assertion. Explicit waits are capped at one second; use state assertions rather than sleeps.
selectOption accepts a stable raw option value and the bounded Playwright forms { value }, { label }, and { index } (or a bounded array for a multi-select). Prefer a stable value; use { label } when the visible label is the product contract.
Scope repeated labels through a dialog, form, or card locator. Cleanup runs in finally; close open overlays, reload or wait for the cleanup page to converge after another page changes state, and only then click cleanup controls. Use clickIfVisible only when the cleanup control is genuinely optional; required cleanup still needs a normal click and assertion. If cleanup also fails, OpenCloud reports the first primary command failure and appends the cleanup failure instead of hiding the cause. Browser failures include bounded console, request, response, and visible alert/status context.
verify_dev_session stores the exact source hash with the materialized dev revision. The receipt binds the application artifact, migration digest, and test hash. Promotion rejects a changed or unverified revision. Production verification reopens the recorded deployment artifact to confirm its digest and manifest, then performs only the SDK and canonical HEAD checks. It refuses to continue if another deployment became active meanwhile. If draft validation or apply did not make the latest revision active, verification returns DEV_REVISION_STALE before launching Chromium and does not consume a browser attempt.
Every success receipt and structured failure includes an attempt report with the input revision/artifact/test hashes, requested parallelism, outcome, failure classification, and phase timings. External E2E evidence also reports per-test sandbox setup, browser execution, cleanup, and total time. Compare the input hashes between attempts to distinguish an application change from a test-only change. An ambiguous failed assertion or locator is classified as application_or_test rather than guessed.
Only one verification may mutate a development session at a time. A second request returns DEV_VERIFICATION_IN_PROGRESS without deleting or interrupting the active attempt's sandboxes. This also makes retries safe when an MCP client loses a response while the server-side verification continues.
For private apps, development verification creates three bounded synthetic users, runs the external spec, proves A and B can enter while unrelated C has no implicit app membership, and removes every session, grant, and identity. Production verification checks only that an anonymous canonical HEAD redirects to Auth.
The platform still independently fails console/page errors, unexpected same-origin failures, primary-flow HTTP 404/405, server errors, horizontal overflow at 390 by 844, unnamed visible controls, and primary controls smaller than 44 by 44 pixels. These checks are platform evidence, not test-authored claims.
