From e8beddfd700410491a3f544071fdbaea298e890d Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 15 Aug 2026 13:32:23 +0530 Subject: [PATCH] docs(spec): cut the package readme to a description and doc links --- pkg/spec/README.md | 62 +++------------------------------------------- 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/pkg/spec/README.md b/pkg/spec/README.md index c651ddf..abfc57b 100644 --- a/pkg/spec/README.md +++ b/pkg/spec/README.md @@ -1,67 +1,13 @@ # @sanderling/spec -TypeScript spec API for [sanderling](https://github.com/priyanshujain/sanderling), a property-based UI fuzzer for mobile and web apps. +TypeScript spec API for [sanderling](https://github.com/priyanshujain/sanderling), a property-based UI fuzzer for Android, iOS and web apps. -Spec authors write properties (what the app must always or eventually do), extractors (structured state from the UI), and action generators (what sanderling is allowed to do). The `sanderling` CLI evaluates the spec in a loop against a running app. - -## Install +A spec exports properties (what the app must always or eventually do), extractors (structured state read off the UI), and action generators (what sanderling is allowed to do). The `sanderling` CLI evaluates the spec against a running app once per step. ```sh npm install --save-dev @sanderling/spec ``` -## Usage +[Getting started](https://priyanshujain.github.io/sanderling/manual/getting-started/) installs the CLI and runs a first spec. The [spec language reference](https://priyanshujain.github.io/sanderling/manual/spec-language/) lists every primitive, and the [case study](https://priyanshujain.github.io/sanderling/manual/case-study/) walks a complete spec end to end. -```ts -import { extract, always, eventually, actions, weighted, taps, swipes, InputText, Tap } from "@sanderling/spec"; - -const loggedIn = extract((s) => !!s.ax.find("id:home-tab-bar")); -const balance = extract((s) => (s.snapshots.balance as number) ?? 0); -const emailField = extract((s) => s.ax.find("id:email-field")); -const submitButton = extract((s) => s.ax.find("id:sign-in-button")); - -export const properties = { - balanceNeverNegative: always(() => balance.current >= 0), - loginSucceeds: eventually(() => loggedIn.current).within(30, "seconds"), -}; - -const doLogin = actions(() => { - if (loggedIn.current) return []; - const email = emailField.current; - const submit = submitButton.current; - if (!email || !submit) return []; - return [InputText({ into: email, text: "test@example.com" }), Tap({ on: submit })]; -}); - -export const actionsRoot = weighted( - [50, doLogin], - [10, taps], - [2, swipes], -); -``` - -## Setup actions - -Some action generators are not fuzz targets but preconditions: they drive the -app from a fresh state into the surface you actually want to fuzz (login, -onboarding, permission grants, seed data). Export them as `setup` instead of -mixing them into `actionsRoot`. The runner tries `setup` first; if it yields -no action, it falls through to `actionsRoot`. State regressing back across the -precondition (e.g. logout under fuzz) automatically re-engages setup. - -```ts -const login = actions(() => { - if (loggedIn.current) return []; - return [InputText({ into: emailField.current!, text: "demo@app.test" }), Tap({ on: submitButton.current! })]; -}); - -export const setup = login; -export const actionsRoot = weighted([60, browse], [40, edit]); - -(globalThis as { setup?: unknown }).setup = setup; -``` - -Setup is just an `ActionGenerator`; compose with `actions`, `weighted`, or -`whenRoute` exactly like the main pool. - -Works identically across Android, iOS, and web targets. +The CLI bundles this package's TypeScript sources at run time, so keep the CLI and the package on the same release.