mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17: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
|
||||
|
||||
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<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.
|
||||
The CLI bundles this package's TypeScript sources at run time, so keep the CLI and the package on the same release.
|
||||
Reference in new issue
Block a user