swarmbox 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ Unreleased; the release names the version.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.20.0 (2026-11-14)
9
+
10
+ - `screen_act` `draw` signs with a finger on a phone or tablet screen: real touches through Chromium's input, so the
11
+ page's own gestures happen as on a device. A desktop still draws with the mouse. Before, it always used the
12
+ mouse, which never scrolls a page, so a signature pad that lets a finger scroll the page instead of drawing
13
+ looked fine. The action's answer now says which was used and, when it happened, how far the pad moved on screen
14
+ ("the touch scrolled the page"). `draw()` returns `{ by: 'finger' | 'mouse', scrolled }`.
15
+ - `draw` refuses a stroke with a point outside the element, or not a number, before pressing anything. Sent half
16
+ way through a stroke, such a point failed there and left the page mid-gesture, and the browser's mouse with no
17
+ position for whatever came next. A stroke that fails for another reason still lifts the finger or the button.
18
+
19
+ ## 0.19.0 (2026-11-13)
20
+
21
+ - A tool that fails gives the agent up to 40 lines (4000 characters) of the error, not just its first line, and
22
+ writes the whole of it to serve's output. A world whose seed script failed answered `run_start` with only
23
+ "world_seed.py failed (1):", and the traceback below it, which held the cause, reached neither.
24
+ - The README lists what the stand-in sandbox needs on Linux: bubblewrap, socat and ripgrep.
25
+ - A screen's downloads are kept: `screen_downloads` lists the files a screen saved (an export, a report, a
26
+ statement) and reads a text file's contents a page at a time, and an action that saved one says
27
+ "downloaded: #1 jobs.csv (2.0 KB, text)". A browser draws nothing for a download, so a tester could press
28
+ Export and never know what came out, or whether anything did. Each file is read through its stream (a remote
29
+ browser has no path), fingerprinted, and kept as text up to 500,000 characters. Its secrets are redacted:
30
+ token-shaped words anywhere in it, and in a CSV or TSV every cell under a secret-named header.
31
+ A download's text is the file as it is: redacted in place, never reparsed, so a JSON export's `424.10` and
32
+ its 20-digit ids read as written. A UTF-16 export (a spreadsheet's "Unicode text") is read by its byte-order
33
+ mark. A long run of key characters is redacted only when it mixes letters and digits and isn't a path. A page's
34
+ own blob: file shows its address whole; a data: one shows only its type and length; any address is cut at 300
35
+ characters. The browser's copy is deleted once read: it sat in the temp folder, which is memory on some
36
+ machines, until the screen closed.
37
+ - A blob: address in `screen_requests` shows whole, not cut to `:3110/<id>`.
38
+ - A secret kept as a number in JSON (`"api_key": 123456`) is redacted, in a download's text and in a request
39
+ body cut short. A tab-separated download is redacted by its column headers whatever its name, so a
40
+ spreadsheet's "Unicode text" export (.txt) is, as a .csv or .tsv is.
41
+ - A scenario file Swarmbox can't play says where and why, in words: "…/b.json isn't a scenario Swarmbox can
42
+ play: cast.0.part: Too big: expected string to have <=80 characters". It used to say only "failed: [", so an
43
+ agent couldn't tell what to fix. `scenario_list` lists every other scenario and names each file it can't play
44
+ with that reason; one bad file used to fail the whole list. `readScenarios` returns both.
45
+
8
46
  ## 0.18.0 (2026-11-11)
9
47
 
10
48
  - `serve` takes `standinSandbox`, and reads `SWARMBOX_NO_SANDBOX=1` as "off" when it isn't given. Before, only a
package/README.md CHANGED
@@ -45,6 +45,10 @@ Built for Swarmify's agents, and usable by any team whose product runs locally.
45
45
  start of what was sent and what came back, and what it keeps open (a live stream). An action's "refused:" line
46
46
  gives each refusal's number, with the server's reason (`error.message`, or FastAPI's `detail`). No headers are
47
47
  kept, and secrets in addresses and bodies are redacted.
48
+ - **A screen's downloads** (`screen_downloads`): the files it saved (an export, a report, a statement), with
49
+ name, size and fingerprint, and a text file's contents a page at a time. An action's "downloaded:" line names
50
+ each file it saved. Secrets in the text are redacted: token-shaped words anywhere, and in a CSV or TSV every
51
+ cell under a secret-named header.
48
52
  - **A daily budget** in browser-minutes and runs.
49
53
 
50
54
  ## Install
@@ -56,6 +60,12 @@ pnpm exec playwright-core install chromium
56
60
 
57
61
  `playwright-core` is a peer dependency, so your product and Swarmbox share one copy. Node 24 or later.
58
62
 
63
+ Stand-ins run in the operating-system sandbox. macOS has what it needs. On Linux, install **bubblewrap**,
64
+ **socat** and **ripgrep** (`apt-get install bubblewrap socat ripgrep`), and on Ubuntu 24.04 or later allow
65
+ unprivileged user namespaces (`kernel.apparmor_restrict_unprivileged_userns=0`), as `.github/workflows` does.
66
+ Without them `run_start` refuses and names what's missing. `SWARMBOX_NO_SANDBOX=1` runs stand-ins without the
67
+ sandbox: only on a throwaway machine of your own, never a shared one.
68
+
59
69
  ## Plugging a product in
60
70
 
61
71
  A product is one object (`Product` in `src/product.ts`): how to start a world, who is in it, how each of
@@ -0,0 +1,60 @@
1
+ import type { Download } from 'playwright-core';
2
+ export interface DownloadRecord {
3
+ /** Counts the screen's downloads from 1. */
4
+ seq: number;
5
+ at: string;
6
+ /** The name the page gave it. */
7
+ name: string;
8
+ /** Where it came from: a path on the screen's own origin, else the whole address. Secrets redacted. */
9
+ url: string;
10
+ state: 'saving' | 'saved' | 'failed';
11
+ bytes?: number;
12
+ sha256?: string;
13
+ /** A text file's contents (redacted), up to TEXT_KEPT characters. */
14
+ text?: string;
15
+ /** The text was longer than TEXT_KEPT, or the file longer than READ_MAX. */
16
+ cut?: boolean;
17
+ binary?: boolean;
18
+ failure?: string;
19
+ }
20
+ /** Kept per screen; the oldest go first. */
21
+ export declare const DOWNLOADS_KEPT = 50;
22
+ /** Of a file, read for its text; the fingerprint and size cover all of it. */
23
+ export declare const READ_MAX: number;
24
+ /** Of a text file, kept. */
25
+ export declare const TEXT_KEPT = 500000;
26
+ /** The longest a download is waited for after the action that started it. */
27
+ export declare const DOWNLOAD_WAIT_MS = 30000;
28
+ /**
29
+ * A file's text, or null when it isn't text: UTF-16 by its byte-order mark (a spreadsheet's "Unicode text"
30
+ * export holds NULs, so it's looked for first), else UTF-8 with no NUL. A character cut at the end of what's
31
+ * checked or kept is still text.
32
+ */
33
+ export declare function decodeText(name: string, bytes: Buffer): string | null;
34
+ /** Whether these bytes are a text file's. */
35
+ export declare function isText(name: string, bytes: Buffer): boolean;
36
+ export declare function redactWords(text: string): string;
37
+ /** A table with every cell under a secret-named header ("password", "api_key", "portal token"…) redacted. */
38
+ export declare function redactTable(text: string, sep: string): string;
39
+ /** A file's contents as kept: the text (redacted, bounded) or that it's binary. */
40
+ export declare function contents(name: string, bytes: Buffer, longer: boolean): Pick<DownloadRecord, 'text' | 'cut' | 'binary'>;
41
+ /**
42
+ * Where a download came from, for its line: a path on the screen's own origin, else the whole address, secrets
43
+ * redacted and bounded. A data: address is the file itself, so only its type and length are shown.
44
+ */
45
+ export declare function source(url: string, origin: string): string;
46
+ export interface DownloadLog {
47
+ records: DownloadRecord[];
48
+ /** The last record's number: an action's downloads are the ones after it. */
49
+ last(): number;
50
+ onDownload(d: Download): void;
51
+ /** Resolves when every download after `seq` has been saved or has failed, or after `ms`. */
52
+ settled(seq: number, ms?: number): Promise<DownloadRecord[]>;
53
+ }
54
+ /** A screen's log; `origin` is its own, whose addresses are shown as paths. */
55
+ export declare function downloadLog(origin: string): DownloadLog;
56
+ /** One line for a download: the readout after an action, and screen_downloads' list. */
57
+ export declare function downloadLine(r: DownloadRecord): string;
58
+ /** One download in full: its line, fingerprint and text from `offset`, at most `limit` characters. */
59
+ export declare function downloadText(r: DownloadRecord, offset?: number, limit?: number): string;
60
+ //# sourceMappingURL=downloads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloads.d.ts","sourceRoot":"","sources":["../src/downloads.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,4CAA4C;AAC5C,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,QAAmB,CAAC;AACzC,4BAA4B;AAC5B,eAAO,MAAM,SAAS,SAAU,CAAC;AACjC,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAOvC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAarE;AAED,6CAA6C;AAC7C,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAYD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAkBD,6GAA6G;AAC7G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAY7D;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GACd,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC,CAgBjD;AAKD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,IAAI,IAAI,MAAM,CAAC;IACf,UAAU,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC9B,4FAA4F;IAC5F,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CAC9D;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAuDvD;AAUD,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAQtD;AAED,sGAAsG;AACtG,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,SAAI,EAAE,KAAK,SAAS,GAAG,MAAM,CAYlF"}
@@ -0,0 +1,214 @@
1
+ // What a screen saved as a file: an export, a report, a statement. A browser drew nothing for it, so without
2
+ // this the agent could press "Download" and never know what came out. Each download is read as it finishes:
3
+ // its name, size, a fingerprint, and, for a text file (CSV, JSON, a calendar file…), the text itself, which
4
+ // screen_downloads shows. Secrets in the text are redacted as a response body's are (redact.ts).
5
+ //
6
+ // Read through the download's stream, not its path: the path isn't there when the browser is remote.
7
+ import { createHash } from 'node:crypto';
8
+ import { REDACTED, redactText, redactUrl, secretName } from "./redact.js";
9
+ /** Kept per screen; the oldest go first. */
10
+ export const DOWNLOADS_KEPT = 50;
11
+ /** Of a file, read for its text; the fingerprint and size cover all of it. */
12
+ export const READ_MAX = 10 * 1024 * 1024;
13
+ /** Of a text file, kept. */
14
+ export const TEXT_KEPT = 500_000;
15
+ /** The longest a download is waited for after the action that started it. */
16
+ export const DOWNLOAD_WAIT_MS = 30_000;
17
+ /** Files whose bytes are text, by name; anything else is text only if it reads as UTF-8. */
18
+ const TEXT_NAMES = /\.(csv|tsv|txt|json|xml|ics|vcf|html?|md|iif|ofx|qbo|qfx|log|ya?ml)$/i;
19
+ /** How far into a file without a text name its UTF-8 is checked. */
20
+ const SNIFF = 64 * 1024;
21
+ /**
22
+ * A file's text, or null when it isn't text: UTF-16 by its byte-order mark (a spreadsheet's "Unicode text"
23
+ * export holds NULs, so it's looked for first), else UTF-8 with no NUL. A character cut at the end of what's
24
+ * checked or kept is still text.
25
+ */
26
+ export function decodeText(name, bytes) {
27
+ if (bytes[0] === 0xff && bytes[1] === 0xfe)
28
+ return new TextDecoder('utf-16le').decode(bytes);
29
+ if (bytes[0] === 0xfe && bytes[1] === 0xff)
30
+ return new TextDecoder('utf-16be').decode(bytes);
31
+ if (bytes.includes(0))
32
+ return null;
33
+ if (!TEXT_NAMES.test(name)) {
34
+ try {
35
+ // stream: a multi-byte character cut at SNIFF is held back, not an error.
36
+ new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, SNIFF), { stream: true });
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ return new TextDecoder('utf-8').decode(bytes);
43
+ }
44
+ /** Whether these bytes are a text file's. */
45
+ export function isText(name, bytes) {
46
+ return decodeText(name, bytes) !== null;
47
+ }
48
+ /**
49
+ * A word that is a token by its shape wherever it sits in a file: a JWT or a provider's secret key, and a long
50
+ * run of key characters that mixes letters and digits and isn't a path. A body is redacted only when it is one
51
+ * token (redact.ts); a file is mostly not secret, so its words are looked at one by one. Best effort: a
52
+ * fingerprint or a slug with numbers in it reads as a token, and a short key (32 hex characters) doesn't.
53
+ */
54
+ const KNOWN_TOKEN = /\beyJ[\w-]+\.[\w-]+\.[\w-]*|\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}|\bwhsec_[A-Za-z0-9]{8,}/g;
55
+ const LONG_RUN = /(?<![\w+/=-])[A-Za-z0-9_+/=-]{40,}(?![\w+/=-])/g;
56
+ export function redactWords(text) {
57
+ return text
58
+ .replace(KNOWN_TOKEN, REDACTED)
59
+ .replace(LONG_RUN, (w) => (w.startsWith('/') || !/\d/.test(w) || !/[A-Za-z]/.test(w) ? w : REDACTED));
60
+ }
61
+ /** A row's cells, split on `sep` outside quotes (a quoted cell holding a line break is read as two rows). */
62
+ function cells(row, sep) {
63
+ const out = [];
64
+ let cell = '';
65
+ let quoted = false;
66
+ for (const ch of row) {
67
+ if (ch === '"')
68
+ quoted = !quoted;
69
+ if (ch === sep && !quoted) {
70
+ out.push(cell);
71
+ cell = '';
72
+ }
73
+ else
74
+ cell += ch;
75
+ }
76
+ out.push(cell);
77
+ return out;
78
+ }
79
+ /** A table with every cell under a secret-named header ("password", "api_key", "portal token"…) redacted. */
80
+ export function redactTable(text, sep) {
81
+ const rows = text.split('\n');
82
+ const secret = cells(rows[0] ?? '', sep).map((h) => secretName(h.replace(/^\s*"|"\s*$/g, '').trim()));
83
+ if (!secret.some(Boolean))
84
+ return text;
85
+ return [
86
+ rows[0],
87
+ ...rows.slice(1).map((r) => cells(r, sep)
88
+ .map((c, i) => (secret[i] && c.trim() ? REDACTED : c))
89
+ .join(sep)),
90
+ ].join('\n');
91
+ }
92
+ /** A file's contents as kept: the text (redacted, bounded) or that it's binary. */
93
+ export function contents(name, bytes, longer) {
94
+ const raw = decodeText(name, bytes);
95
+ if (raw === null)
96
+ return { binary: true };
97
+ const ext = /\.([a-z]+)$/i.exec(name)?.[1]?.toLowerCase() ?? '';
98
+ // Redacted in place, never reparsed: an export's 424.10 and its 20-digit ids are read as the file has them.
99
+ let text = redactText(raw);
100
+ // A table by its name, or tab-separated text by its first line (a spreadsheet's "Unicode text" is .txt).
101
+ const sep = ext === 'csv' ? ',' : ext === 'tsv' || /^[^\n]*\t/.test(text) ? '\t' : null;
102
+ if (sep)
103
+ text = redactTable(text, sep);
104
+ text = redactWords(text);
105
+ let cut = longer;
106
+ if (text.length > TEXT_KEPT) {
107
+ text = text.slice(0, TEXT_KEPT);
108
+ cut = true;
109
+ }
110
+ return { text, ...(cut ? { cut } : {}) };
111
+ }
112
+ /** The longest "from" address shown. */
113
+ const URL_SHOWN = 300;
114
+ /**
115
+ * Where a download came from, for its line: a path on the screen's own origin, else the whole address, secrets
116
+ * redacted and bounded. A data: address is the file itself, so only its type and length are shown.
117
+ */
118
+ export function source(url, origin) {
119
+ const data = /^data:([^,;]*)/i.exec(url);
120
+ if (data)
121
+ return `data:${data[1] || 'text/plain'} (${url.length} characters)`;
122
+ const shown = redactUrl(url, origin);
123
+ return shown.length > URL_SHOWN ? `${shown.slice(0, URL_SHOWN)}…` : shown;
124
+ }
125
+ /** A screen's log; `origin` is its own, whose addresses are shown as paths. */
126
+ export function downloadLog(origin) {
127
+ const records = [];
128
+ const pending = new Map();
129
+ let seq = 0;
130
+ const read = async (d, rec) => {
131
+ try {
132
+ const stream = await d.createReadStream();
133
+ const hash = createHash('sha256');
134
+ const kept = [];
135
+ let size = 0;
136
+ for await (const chunk of stream) {
137
+ hash.update(chunk);
138
+ if (size < READ_MAX)
139
+ kept.push(chunk.subarray(0, READ_MAX - size));
140
+ size += chunk.length;
141
+ }
142
+ Object.assign(rec, {
143
+ state: 'saved',
144
+ bytes: size,
145
+ sha256: hash.digest('hex'),
146
+ ...contents(rec.name, Buffer.concat(kept), size > READ_MAX),
147
+ });
148
+ }
149
+ catch (e) {
150
+ rec.state = 'failed';
151
+ rec.failure = (await d.failure().catch(() => null)) ?? e.message.split('\n')[0];
152
+ }
153
+ // Read, so the browser's copy goes: it sits in the temp folder, which is memory on some machines.
154
+ await d.delete().catch(() => { });
155
+ };
156
+ return {
157
+ records,
158
+ last: () => seq,
159
+ onDownload(d) {
160
+ const rec = {
161
+ seq: ++seq,
162
+ at: new Date().toISOString(),
163
+ name: d.suggestedFilename(),
164
+ url: source(d.url(), origin),
165
+ state: 'saving',
166
+ };
167
+ records.push(rec);
168
+ if (records.length > DOWNLOADS_KEPT)
169
+ records.shift();
170
+ const done = read(d, rec).finally(() => pending.delete(rec.seq));
171
+ pending.set(rec.seq, done);
172
+ },
173
+ async settled(after, ms = DOWNLOAD_WAIT_MS) {
174
+ const waiting = [...pending].filter(([n]) => n > after).map(([, p]) => p);
175
+ if (waiting.length) {
176
+ let timer;
177
+ await Promise.race([Promise.all(waiting), new Promise((r) => (timer = setTimeout(r, ms)))]).finally(() => clearTimeout(timer));
178
+ }
179
+ return records.filter((r) => r.seq > after);
180
+ },
181
+ };
182
+ }
183
+ function size(bytes) {
184
+ return bytes < 1024
185
+ ? `${bytes} B`
186
+ : bytes < 1024 * 1024
187
+ ? `${(bytes / 1024).toFixed(1)} KB`
188
+ : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
189
+ }
190
+ /** One line for a download: the readout after an action, and screen_downloads' list. */
191
+ export function downloadLine(r) {
192
+ const what = r.state === 'saving'
193
+ ? 'still saving'
194
+ : r.state === 'failed'
195
+ ? `failed: ${r.failure ?? 'unknown'}`
196
+ : `${size(r.bytes ?? 0)}, ${r.binary ? 'binary' : `text${r.cut ? ', cut' : ''}`}`;
197
+ return `#${r.seq} ${r.name} (${what}) from ${r.url}`;
198
+ }
199
+ /** One download in full: its line, fingerprint and text from `offset`, at most `limit` characters. */
200
+ export function downloadText(r, offset = 0, limit = 20_000) {
201
+ const head = `${downloadLine(r)}${r.sha256 ? `\nsha256 ${r.sha256}` : ''}`;
202
+ if (r.state !== 'saved')
203
+ return head;
204
+ if (r.binary)
205
+ return `${head}\nA binary file: its contents aren't shown.`;
206
+ const text = r.text ?? '';
207
+ const part = text.slice(offset, offset + limit);
208
+ const more = offset + limit < text.length
209
+ ? `\n… ${text.length - offset - limit} more characters: offset ${offset + limit}`
210
+ : '';
211
+ const cut = r.cut ? '\n(the file was longer than Swarmbox keeps; the rest is not shown)' : '';
212
+ return `${head}\n---\n${part}${more}${!more ? cut : ''}`;
213
+ }
214
+ //# sourceMappingURL=downloads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloads.js","sourceRoot":"","sources":["../src/downloads.ts"],"names":[],"mappings":"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"}
@@ -58,8 +58,19 @@ export declare function coveredByOwnLabel(el: Locator): Promise<boolean>;
58
58
  export type Stroke = [number, number][];
59
59
  /** A signature-like scribble, for a pad that only needs one. */
60
60
  export declare const SIGNATURE: Stroke[];
61
- /** Draws strokes on an element with the pointer, as a finger or a mouse would: down, moves, up. */
62
- export declare function draw(page: Page, el: Locator, strokes?: Stroke[]): Promise<void>;
61
+ /** How a drawing went: with a finger (a touch screen) or the mouse, and how far it moved the element on screen. */
62
+ export interface Drawn {
63
+ by: 'finger' | 'mouse';
64
+ /** CSS pixels the element moved while being drawn on: a finger that scrolled the page instead of drawing. */
65
+ scrolled: number;
66
+ }
67
+ /**
68
+ * Draws strokes on an element as the screen's person would: with a finger on a touch screen (a phone or tablet),
69
+ * the mouse otherwise; each stroke is down, moves, up. A finger's strokes are real touches, so a pad that lets
70
+ * touch scroll the page moves the page, as it does on a customer's phone; a mouse never scrolls a page and would
71
+ * hide that. Says how far the element moved meanwhile.
72
+ */
73
+ export declare function draw(page: Page, el: Locator, strokes?: Stroke[]): Promise<Drawn>;
63
74
  /** Where a drag goes: a point on another element (fractions of its box), or a distance in CSS pixels. */
64
75
  export type DragTo = {
65
76
  el: Locator;
@@ -1 +1 @@
1
- {"version":3,"file":"sight.d.ts","sourceRoot":"","sources":["../../src/fleet/sight.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sight.d.ts","sourceRoot":"","sources":["../../src/fleet/sight.ts"],"names":[],"mappings":"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"}
@@ -352,25 +352,86 @@ export const SIGNATURE = [
352
352
  [0.9, 0.45],
353
353
  ],
354
354
  ];
355
- /** Draws strokes on an element with the pointer, as a finger or a mouse would: down, moves, up. */
355
+ /** Points between two, so a stroke moves as a finger does, not in one jump. */
356
+ const between = (a, b, steps) => Array.from({ length: steps }, (_, i) => ({
357
+ x: a.x + ((b.x - a.x) * (i + 1)) / steps,
358
+ y: a.y + ((b.y - a.y) * (i + 1)) / steps,
359
+ }));
360
+ /**
361
+ * Draws strokes on an element as the screen's person would: with a finger on a touch screen (a phone or tablet),
362
+ * the mouse otherwise; each stroke is down, moves, up. A finger's strokes are real touches, so a pad that lets
363
+ * touch scroll the page moves the page, as it does on a customer's phone; a mouse never scrolls a page and would
364
+ * hide that. Says how far the element moved meanwhile.
365
+ */
356
366
  export async function draw(page, el, strokes = SIGNATURE) {
367
+ // A point off the element, or not a number, is refused before anything is pressed: sent half way through a
368
+ // stroke it fails there, and leaves the browser's mouse with no position for anything after it.
369
+ const bad = strokes.flat().find(([x, y]) => !(x >= 0 && x <= 1 && y >= 0 && y <= 1));
370
+ if (bad)
371
+ throw new Error(`a stroke's point [${bad.join(', ')}] is not inside the element (each is a fraction, 0 to 1)`);
357
372
  await el.scrollIntoViewIfNeeded({ timeout: 10_000 });
358
373
  const box = await el.boundingBox({ timeout: 10_000 });
359
374
  if (!box)
360
375
  throw new Error('it has no size on screen');
361
376
  const at = ([x, y]) => ({ x: box.x + x * box.width, y: box.y + y * box.height });
362
- for (const stroke of strokes) {
363
- if (!stroke.length)
364
- continue;
365
- const first = at(stroke[0]);
366
- await page.mouse.move(first.x, first.y);
367
- await page.mouse.down();
368
- for (const p of stroke.slice(1)) {
369
- const q = at(p);
370
- await page.mouse.move(q.x, q.y, { steps: 6 });
377
+ const touch = await page.evaluate(() => navigator.maxTouchPoints > 0).catch(() => false);
378
+ // Touches go in through Chromium's own input, the way a screen's touches do, so the page's gestures (a scroll,
379
+ // a pinch) happen as on a device. Another engine, or no protocol session, draws with the mouse.
380
+ const cdp = touch
381
+ ? await page
382
+ .context()
383
+ .newCDPSession(page)
384
+ .catch(() => null)
385
+ : null;
386
+ const touchAt = (type, p) => cdp.send('Input.dispatchTouchEvent', {
387
+ type,
388
+ touchPoints: p ? [{ x: Math.round(p.x), y: Math.round(p.y), id: 1 }] : [],
389
+ });
390
+ // Whether a finger or the button is down: a stroke that fails half way still lifts it, so the page isn't left
391
+ // mid-gesture (a pad still drawing, a page still being dragged) for whatever the screen does next.
392
+ let down = false;
393
+ try {
394
+ for (const stroke of strokes) {
395
+ if (!stroke.length)
396
+ continue;
397
+ const first = at(stroke[0]);
398
+ if (cdp) {
399
+ await touchAt('touchStart', first);
400
+ down = true;
401
+ let from = first;
402
+ for (const p of stroke.slice(1)) {
403
+ const q = at(p);
404
+ for (const r of between(from, q, 6))
405
+ await touchAt('touchMove', r);
406
+ from = q;
407
+ }
408
+ down = false;
409
+ await touchAt('touchEnd');
410
+ }
411
+ else {
412
+ await page.mouse.move(first.x, first.y);
413
+ await page.mouse.down();
414
+ down = true;
415
+ for (const p of stroke.slice(1)) {
416
+ const q = at(p);
417
+ await page.mouse.move(q.x, q.y, { steps: 6 });
418
+ }
419
+ down = false;
420
+ await page.mouse.up();
421
+ }
371
422
  }
372
- await page.mouse.up();
373
423
  }
424
+ finally {
425
+ if (down)
426
+ await (cdp ? touchAt('touchEnd') : page.mouse.up()).catch(() => undefined);
427
+ await cdp?.detach().catch(() => undefined);
428
+ }
429
+ // A scroll the gesture started may still be settling.
430
+ if (cdp)
431
+ await page.waitForTimeout(150);
432
+ const after = await el.boundingBox({ timeout: 10_000 }).catch(() => null);
433
+ const scrolled = after ? Math.round(Math.hypot(after.x - box.x, after.y - box.y)) : 0;
434
+ return { by: cdp ? 'finger' : 'mouse', scrolled };
374
435
  }
375
436
  /** How far one wheel turn scrolls while a drag is held, as a fraction of the screen's height. */
376
437
  const WHEEL_STEP = 0.6;