mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-03 19:47:10 +00:00
docs(spec): cut the package readme to a description and doc links
This commit is contained in:
1 parent
4b22b45b68
commit
e8beddfd70
1 file changed
+4
-58
+4
-58
@@ -1,67 +1,13 @@
|
|||||||
# @sanderling/spec
|
# @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.
|
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.
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install --save-dev @sanderling/spec
|
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
|
The CLI bundles this package's TypeScript sources at run time, so keep the CLI and the package on the same release.
|
||||||
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<number>((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: "[email protected]" }), 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: "[email protected]" }), 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.
|
|
||||||
Reference in new issue
Block a user