@revyl/verify 0.5.0 → 0.6.1

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/docs/computers.md CHANGED
@@ -1,6 +1,6 @@
1
- # Mac computers (early access)
1
+ # Mac computers
2
2
 
3
- Verify opens a Revyl cloud macOS computer for commands, files, processes, and screenshots. It does not open an assigned physical computer. Mac computers are **early access**. Ask [support@revyl.ai](mailto:support@revyl.ai) about access.
3
+ Verify opens a Revyl cloud macOS computer for commands, files, processes, screenshots, and clicks and keys on its screen. It does not open an assigned physical computer. Mac computers are available to organizations on a current paid Revyl plan. If yours can't start one, ask [support@revyl.ai](mailto:support@revyl.ai).
4
4
 
5
5
  ```bash
6
6
  export REVYL_API_KEY=...
@@ -47,6 +47,75 @@ Paths are relative to the computer's workspace: no leading slash, empty componen
47
47
 
48
48
  Operation `timeout` is milliseconds, defaults to 30,000, and must be an integer from 1 to 300,000. It is also bounded by the run's remaining lifetime. A computer has a hard lifetime **from create** (two hours today; `expiresAt` is authoritative), not from readiness or last activity; `idleTimeoutSeconds` and the agent `stepTimeout` do not configure computer lifetime or commands. `expiresAt` is a `Date` when the API returns `expires_at`, and otherwise undefined (including on devices and older APIs). Verify refuses new operations after that deadline and bounds operation/screenshot observation by it; expiry does not prevent explicit close or recording retrieval. Revyl allows at most 256 operations per run.
49
49
 
50
+ ## Screen
51
+
52
+ **Early access**, since TypeScript 0.6.0, Python 0.4.0 and Go v0.5.0. A script can click, type and press keys on the computer's screen, at points it reads off `screenshot()` or at elements it describes:
53
+
54
+ ```ts
55
+ import { open } from '@revyl/verify';
56
+
57
+ const computer = await open({ platform: 'mac', startTimeout: 600_000 });
58
+ try {
59
+ await computer.writeFile('notes.txt', 'Notes\n');
60
+ await computer.exec(['open', '-a', 'TextEdit', 'notes.txt']); // exec runs in the workspace
61
+ await new Promise((resolve) => setTimeout(resolve, 3_000)); // let the window open
62
+ await computer.screenshot({ path: '.verify/mac.png' }); // open it and find the text
63
+ await computer.tap({ x: 400, y: 300 }); // the point as you read it
64
+ await computer.typeFocused('First line\nSecond line', { clear: false });
65
+ await computer.pressKey('a', { modifiers: ['meta'] }); // Command+A selects all
66
+ await computer.typeFocused('Replacement', { clear: false }); // typing replaces the selection
67
+ await computer.screenshot({ path: '.verify/mac-after.png' });
68
+ } finally {
69
+ await computer.close();
70
+ }
71
+ ```
72
+
73
+ | Call | What it does |
74
+ | --- | --- |
75
+ | `screenshot({ path? })` | A PNG of the whole screen. Its pixels are the coordinates the other calls take |
76
+ | `screenSize()` | The screenshot's `{ width, height }`: x is 0 to width - 1, y is 0 to height - 1 |
77
+ | `tap({ x, y })` | Clicks the point with the left button and resolves with it. Whole numbers from the top-left corner of the screenshot |
78
+ | `tap({ target })` | Revyl finds the described element on a fresh screenshot, clicks it, and resolves with the point it clicked, in screenshot pixels. The description is 1 to 500 characters. Takes several seconds |
79
+ | `typeFocused(text, { clear: false })` | Types the text where the cursor is, in the app that has focus. `{ clear: false }` is required, because Verify doesn't clear anything first: to replace text, select it with `pressKey('a', { modifiers: ['meta'] })` and type over it. Up to 16,384 characters, including line breaks (`\n`) and tabs (`\t`); other control characters are refused before anything is typed. Long text goes in pieces of up to 500 characters, in order |
80
+ | `pressKey(key, { modifiers })` | Presses a key: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` or `'pagedown'`, with up to four `modifiers`: `'meta'` (Command), `'ctrl'`, `'alt'` (Option) and `'shift'`. With modifiers a lowercase letter or digit works too, so Command+A is `pressKey('a', { modifiers: ['meta'] })`. On Web the shortcut key is `'ctrl'` instead |
81
+
82
+ `locate()` isn't available on computers, because Revyl can't find a point there without clicking it; `tap({ target })` returns the point it clicked instead. `doubleTap`, `longPress`, `type(location, text)`, `swipe`, `drag`, `back`, `goHome`, `openUrl` and `launchApp` aren't available on computers either, and a description works only in `tap`. They throw `NOT_AVAILABLE_ON_PLATFORM` before anything is sent; open apps and files with `exec()`, such as `exec(['open', '-a', 'TextEdit', 'notes.txt'])`. For TextEdit, open a file rather than the bare app: opened without a file, it may show a file picker instead of a document.
83
+
84
+ ### Described clicks
85
+
86
+ ```ts
87
+ const point = await computer.tap({ target: 'the Help menu in the menu bar' }); // several seconds
88
+ await computer.screenshot({ path: '.verify/help.png' }); // check what it clicked
89
+ ```
90
+
91
+ A described click takes several seconds, because Revyl takes a screenshot and finds the element first. It can pick a look-alike, and when the target isn't on the screen it can still click something else instead of failing. So check the point it returns or take a screenshot afterwards, and for anything that's hard to undo, read the point off `screenshot()` and use `tap({ x, y })`.
92
+
93
+ ### Behaviour and errors
94
+
95
+ Each call returns when Revyl has accepted the action, not when the app has finished reacting, so take a screenshot before you rely on what changed. Verify never repeats a screen action, with one exception: when Revyl answers `screen_starting`, nothing happened, so Verify waits as long as Revyl asks and tries again, for up to 30 seconds. Window positions and the desktop size aren't fixed, so for a script that runs unattended, run it once up to the screenshot, read the points off that image, and then write them into the script.
96
+
97
+ A script that controls the screen keeps control for 120 seconds after its last action. During that time a teammate can't take control of the screen in the Revyl viewer. The other way round, while someone controls the screen in the viewer, the script's actions throw `SESSION_BUSY`.
98
+
99
+ | What happened | Error | What to do |
100
+ | --- | --- | --- |
101
+ | A point past the screen's edge (`screen_point_outside`), or a negative or fractional one | `INVALID_ARGUMENT`, and nothing was clicked | The message names the valid range |
102
+ | `typeFocused` without `{ clear: false }`, with control characters other than `\n` and `\t`, or with more than 16,384 characters | `INVALID_ARGUMENT` before anything is sent | The message says how to fix the call |
103
+ | The computer can't type some of the characters (`screen_text_unsupported`) | `INVALID_ARGUMENT`, and nothing was typed | Type those characters another way, such as writing a file with `writeFile` |
104
+ | Someone controls the screen in the viewer (`screen_control_held`) | `SESSION_BUSY`, and nothing happened | Try again after they release it; control frees itself after 2 minutes without input |
105
+ | Control of the screen changed hands before the action was sent (`screen_control_lost`) | `SESSION_BUSY`, and nothing happened | Try again; if someone controls the screen in the viewer, wait until they release it |
106
+ | The computer refused a click at a point (`invalid_request` on `tap({ x, y })`), as when the point is no longer on its screen after its display size changed | `INVALID_ARGUMENT`, and nothing was clicked | Take a new screenshot and pick the point from it |
107
+ | The computer refused typing or a key (`invalid_request`) | `INVALID_ARGUMENT`; part of it may have run | Take a `screenshot()` before you try again |
108
+ | The computer can't press that key (`screen_key_unsupported`) | `INVALID_ARGUMENT`, and nothing was pressed | Use a key `pressKey` lists, or `typeFocused` for a printable character |
109
+ | The screen is still starting (`screen_starting`) for more than 30 seconds | `SESSION_BUSY`, and nothing happened | Try again after the number of seconds the message names |
110
+ | Revyl didn't find the described target (`screen_target_not_found`) | `ACTION_FAILED`, and nothing was clicked | Take a screenshot and pass a point to `tap({ x, y })`, or describe the element differently |
111
+ | Revyl couldn't look the target up just then (`screen_target_unavailable`) | `API_ERROR`, and nothing was clicked | It is safe to try again |
112
+ | The backend doesn't have described clicks yet (`invalid_request` on a described tap) | `NOT_AVAILABLE_ON_PLATFORM`, and nothing was clicked | Pass a point to `tap({ x, y })` |
113
+ | Revyl can't confirm the action (`screen_input_uncertain`, `screen_unavailable`, `screen_input_unavailable` or `owner_unavailable`, any other HTTP 5xx, or no answer at all) | `ACTION_UNCERTAIN` | It may have happened. Take a `screenshot()` before you do it again |
114
+ | The API key may not control the screen (HTTP 403) | `AUTH_REJECTED` | The key's owner needs the Member role or above. CI runner keys can't control the screen yet |
115
+ | Revyl couldn't check the key (HTTP 503 `authentication_unavailable`) | `API_ERROR`, and nothing happened | It is safe to try again |
116
+
117
+ When long text fails part way, the error says how many characters were certainly typed and whether the piece that failed may have been typed, counting characters as Unicode code points, so an emoji is one. Every other refusal names its reason and what to do next, such as starting a new computer after `run_closed`.
118
+
50
119
  ## Recording metadata
51
120
 
52
121
  After terminal state **and** confirmed release, `await computer.recording()` returns `{ url, durationSeconds?, startedAt? }` or `null` if the recording is unavailable or still processing. `durationSeconds` is a finite nonnegative number; `startedAt` is a `Date`. Missing or nullable optional fields are omitted. Calling before terminal state and confirmed release throws `SESSION_BUSY`; Verify does not wait for the recording or download it. The method works after `close()` and checks the report before requesting the recording.
@@ -55,11 +124,11 @@ The HTTPS URL is signed and short-lived. Keep it private, including its query st
55
124
 
56
125
  ## Availability and errors
57
126
 
58
- Mac computers are early access. A `503 runtime_unavailable` or typed `computer_not_enabled` becomes `NOT_AVAILABLE_ON_PLATFORM` with this availability guidance. A `429 computer_limit` becomes `COMPUTER_LIMIT`: queued work (including iOS) or the concurrency limit can cause it, and the response does not identify which. Verify does not retry the create or send a stop after these definite admission refusals. `operation_limit` remains `SESSION_BUSY`. Availability is determined by the server.
127
+ Mac computers are available to organizations on a current paid Revyl plan. A `503 runtime_unavailable` or typed `computer_not_enabled` becomes `NOT_AVAILABLE_ON_PLATFORM` with this availability guidance. A `429 computer_limit` becomes `COMPUTER_LIMIT`: queued work (including iOS) or the concurrency limit can cause it, and the response does not identify which. Verify does not retry the create or send a stop after these definite admission refusals. `operation_limit` remains `SESSION_BUSY`. Availability is determined by the server.
59
128
 
60
129
  The default `startTimeout` remains 600000 ms; override it explicitly if needed. An unclaimed start can transition to `failed` with `failure_reason: 'no_host'` after 120 seconds from create. Verify stops readiness polling and throws `SESSION_START_FAILED` with that context, then attempts bounded cleanup. This is not a 120-second timeout for every start: claimed guests may take longer. `device_limit` also yields `SESSION_START_FAILED`, with queued-work/concurrency context. A start timeout or unconfirmed close does not prove resources were released. Inspect the session and contact support rather than blindly repeating a start or stop.
61
130
 
62
- `instruction()` and `validation()` throw `NOT_AVAILABLE_ON_PLATFORM` on computers without sending agent requests. Screenshots are supported, but screenshot readiness is separate from command readiness. Agent control of the screen is not part of this release. On web, iOS, and Android, shell/file/process methods throw `NOT_AVAILABLE_ON_PLATFORM`.
131
+ `instruction()` and `validation()` throw `NOT_AVAILABLE_ON_PLATFORM` on computers without sending agent requests. Screenshots are supported, but screenshot readiness is separate from command readiness. For the screen, use the calls in [Screen](#screen). On web, iOS, and Android, shell/file/process methods throw `NOT_AVAILABLE_ON_PLATFORM`.
63
132
 
64
133
  For the first 30 seconds after a computer becomes active, `screenshot()` retries exact structured `screen_unavailable` responses (HTTP 409 or 503) and `screen_starting` (HTTP 409). Backoff starts at 500 ms and doubles up to 2 seconds; the fixed window does not restart on each call. Cancellation, close, expiry, and the observation deadline still bound the wait. Exhausting that window yields `SESSION_BUSY` with a desktop-not-ready message, rather than requiring a caller sleep. Authentication failures, unrelated errors, malformed responses, and transport failures are not replayed. Commands and other mutations are never retried by this screenshot behavior.
65
134
 
@@ -8,8 +8,8 @@ import { defineConfig } from '@revyl/verify';
8
8
  export default defineConfig({
9
9
  targets: {
10
10
  web: { platform: 'web', app: { appId: '…', url: 'https://example.com' }, viewport: { width: 1280, height: 800 } },
11
- ios: { platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: '18.5' } },
12
- pixel: { platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 8' } },
11
+ ios: { platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: 'iOS 18.5' } },
12
+ pixel: { platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 7', osVersion: 'Android 14' } },
13
13
  mac: { platform: 'mac' },
14
14
  },
15
15
  stepTimeout: 240_000,
@@ -20,7 +20,7 @@ Unknown keys are rejected, so a typo fails loudly instead of being ignored.
20
20
 
21
21
  ## Top-level options
22
22
 
23
- Mac targets are early access. Ask support@revyl.ai about access. See [computer availability](computers.md#availability-and-errors).
23
+ Mac computers are available to organizations on a current paid Revyl plan. See [computer availability](computers.md#availability-and-errors).
24
24
 
25
25
  | Option | Default | Meaning |
26
26
  | --- | --- | --- |
@@ -50,7 +50,7 @@ Mac targets are early access. Ask support@revyl.ai about access. See [computer a
50
50
  | `app.buildId` | An exact Revyl build to install. |
51
51
  | `app.artifactUrl` | An HTTPS URL of an `.apk`, `.app.zip`, or `.ipa` to install. |
52
52
  | `app.bundleId` | An app already on the device to launch instead, such as a system app. |
53
- | `device` | `{ model?, osVersion? }`, for example `{ model: 'Pixel 8', osVersion: '15' }` |
53
+ | `device` | `{ model?, osVersion? }`, written as Revyl names them, for example `{ model: 'iPhone 16', osVersion: 'iOS 18.5' }` or `{ model: 'Pixel 7', osVersion: 'Android 14' }`. Revyl picks its default for a value you leave out. A model or OS version Revyl doesn't offer, or a pair that doesn't go together, throws `INVALID_ARGUMENT` listing the values it accepts, and no session starts |
54
54
  | `startTimeout` | Milliseconds to wait for the device. Default 600000. |
55
55
  | `idleTimeoutSeconds` | Default 600. |
56
56
 
@@ -1,5 +1,7 @@
1
1
  # Verify working end to end
2
2
 
3
+ **Historical record.** This page documents the 0.1.0 and 0.2.0 runs of October 2026 and is kept for its method. It does not describe the current release: the [changelog](https://github.com/RevylAI/verify/blob/main/CHANGELOG.md) and the guides do.
4
+
3
5
  These live runs were made on **2026-10-27**. The baseline uses source commit [`6029513369f13e73914b550d0cd26ece615b4e13`](https://github.com/RevylAI/verify/tree/6029513369f13e73914b550d0cd26ece615b4e13); the separate published-package check used `@revyl/verify@0.1.0`. The [re-verification below](#re-verification-at-061e5cfa) uses `061e5cfa31c5489aa304c2b640bbaf40e3d88561`, including an unpublished packed 0.2.0 tarball. Neither is a publication announcement.
4
6
 
5
7
  Only reviewed public scripts, sanitized transcripts, result projections, and two reviewed screenshots were used here. No private reports, device identifiers, session links, signed recording URLs, or recordings are published.
package/docs/errors.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  `web_runtime_unavailable` does not distinguish full capacity from an unavailable runtime. `computer_limit` does not distinguish queued work from concurrency exhaustion. Inspect these fields to classify refusal without parsing prose; do not infer a specific cause or automatically start a replacement session.
20
20
 
21
- Ordinary start errors retain recognized safe server diagnostic codes such as `web_app_required` in the message even when they are not typed refusal reasons. Arbitrary server messages and payloads are not copied into startup diagnostics.
21
+ Ordinary start errors retain recognized safe server diagnostic codes such as `web_app_required` in the message even when they are not typed refusal reasons. An iOS or Android start that Revyl refuses as invalid (HTTP 400 or 422), such as a device model or OS version it doesn't offer, throws `INVALID_ARGUMENT` with Revyl's explanation, which lists the values it accepts; nothing was started. Other server messages and payloads, including every Web and hosted start's, are not copied into startup diagnostics.
22
22
 
23
23
  Web navigation timeouts expose `reason: 'request_timeout'` with HTTP 504 and `code: 'ACTION_UNCERTAIN'`: the action budget expired before navigation was confirmed, but the request may still commit and the session remains open. A normalized `protocol_error` also preserves uncertainty and its HTTP status. Neither error causes Verify to replay navigation or close the session. Inspect the live page before deciding whether to navigate again.
24
24
 
@@ -37,23 +37,23 @@ try {
37
37
 
38
38
  | Code | What happened | What to do |
39
39
  | --- | --- | --- |
40
- | `INVALID_ARGUMENT` | A call got a bad value: an unknown target name, an empty step, two steps at once on one session, a point outside the screenshot | The message names the argument |
40
+ | `INVALID_ARGUMENT` | A call got a bad value: an unknown target name, an empty step, two steps at once on one session, a point outside the screenshot, `typeFocused` on a computer without `{ clear: false }`, or a device model or OS version Revyl doesn't offer | The message names the argument, and for a device, the values Revyl accepts |
41
41
  | `CONFIG_NOT_FOUND` | `open('name')` found no `verify.config.ts` at or above the current folder | Run `npx @revyl/verify init`, pass `options.config`, or pass a target object |
42
42
  | `CONFIG_INVALID` | The config failed validation or did not load | The message names the exact option and the rule |
43
43
  | `AUTH_MISSING` | No `REVYL_API_KEY` and no saved login | Run `npx @revyl/verify login` in a terminal, or set `REVYL_API_KEY` |
44
- | `AUTH_REJECTED` | Revyl rejected the credentials | Create a new API key, or log in again |
44
+ | `AUTH_REJECTED` | Revyl rejected the credentials, or refused a computer screen action with HTTP 403 | Create a new API key, or log in again. For a screen action, the key's owner needs the Member role or above, and CI runner keys can't control the screen yet |
45
45
  | `EDGE_BLOCKED` | An HTTP 403 has a `text/html` content type and nonempty `cf-ray`, without a valid JSON body | Check network access or contact support; Verify does not retry the request |
46
46
  | `API_ERROR` | Revyl returned an unexpected response | Try again; after an exact action, check the session first, since the action may have run. If it persists, report it with the session link |
47
47
  | `SESSION_START_FAILED` | The browser, device, or computer did not start | Computer `no_host` reports the 120-second unclaimed-start context; `device_limit` reports queued-work/concurrency context. Inspect the session and cleanup before starting again. |
48
48
  | `SESSION_ENDED` | The session was closed, stopped, or expired, or Revyl answered 404 for the session itself | Close explicitly; computer expiry does not prove release |
49
- | `SESSION_BUSY` | A step conflict, computer operation limit, or recording requested before terminal state and confirmed release | Inspect the state before another call; a terminal computer alone does not prove release |
49
+ | `SESSION_BUSY` | A step conflict, computer operation limit, someone controlling a computer's screen in the Revyl viewer, or recording requested before terminal state and confirmed release | Inspect the state before another call; a terminal computer alone does not prove release |
50
50
  | `COMPUTER_LIMIT` | Computer admission was refused due to queued work (including iOS) or the concurrency limit | Wait for capacity; Verify does not retry or stop a definitely refused start |
51
51
  | `NOT_AVAILABLE_ON_PLATFORM` | The call is unavailable: computer agent steps, device shell/file/recording methods, an exact action the platform lacks (such as `back()` on iOS, `goHome()` or `launchApp()` on Web, `typeFocused()` or keys other than Enter and Backspace on iOS and Android), computer access or runtime unavailable, or a backend without web steps | See [web.md](web.md) and [computers.md](computers.md#availability-and-errors) |
52
52
  | `COMMAND_FAILED` | A computer command exited nonzero | Read its reason; use `exec(argv, { check: false })` to inspect the exit code and output instead |
53
53
  | `NOT_FOUND` | A computer file or resource was not found | Check the workspace-relative path |
54
54
  | `ACCESS_DENIED` | The computer refused access | Check permissions; do not assume host/root access |
55
55
  | `ACTION_FAILED` | Revyl refused or failed a request on the session | Read the reason in the message |
56
- | `ACTION_UNCERTAIN` | The connection failed mid-request, so the step may or may not have run | Verify does not repeat it; check the session link before deciding what to do |
56
+ | `ACTION_UNCERTAIN` | The connection failed mid-request, or Revyl couldn't confirm a computer screen action, so the step may or may not have run | Verify does not repeat it; check the session link before deciding what to do |
57
57
  | `INSTRUCTION_FAILED` | Revyl's agent could not do what the instruction asked | Make the instruction more specific, or split it into smaller steps |
58
58
  | `VALIDATION_FAILED` | The validation did not hold | The message has Revyl's reason; watch the recording at the session link |
59
59
  | `STEP_FAILED` | Revyl could not run the step to an outcome: it errored, it was cancelled on Revyl, a validation came back without a verdict, or Revyl accepted the step but still answered "Live step not found" about 10 seconds later (`httpStatus` 404). This says nothing about your app, and `soft` does not cover it. Verify leaves the session open. | Watch the session at the link. After "Live step not found", the step may still be running, so wait for it to finish (another step meanwhile gets `SESSION_BUSY`), then run the step again |
@@ -65,9 +65,32 @@ try {
65
65
 
66
66
  Computer operations use `TIMEOUT` for bounded runtime with confirmed termination, and `ACTION_UNCERTAIN` when termination, execution, or cleanup is unconfirmed. An observed successful operation can return its result while logging pending/failed evidence. Timeout never proves rollback, and failed evidence does not prove the command never ran. Never automatically retry a mutation with a fresh request id; inspect the computer first. See [computers.md](computers.md).
67
67
 
68
+ ### Computer screen actions
69
+
70
+ Clicks, typing and keys on a Mac computer's screen map Revyl's answers to these codes, read from the answer's `detail`, never from its HTTP status alone. Verify never repeats a screen action, except after `screen_starting`, which it tries again for up to 30 seconds. See [computers.md](computers.md#screen).
71
+
72
+ | Revyl's answer | `code` | What it means |
73
+ | --- | --- | --- |
74
+ | 409 `screen_control_held` | `SESSION_BUSY` | Nothing happened: someone controls the screen in the viewer. Try again after they release it; control frees itself after 2 minutes without input |
75
+ | 409 `screen_control_lost` | `SESSION_BUSY` | Nothing happened: control of the screen changed hands before the action was sent. Try again; if someone controls the screen in the viewer, wait until they release it |
76
+ | 409 `screen_starting` for more than 30 seconds | `SESSION_BUSY` | Nothing happened: the screen is still starting. Try again after the number of seconds the message names |
77
+ | 422 `screen_target_not_found` | `ACTION_FAILED` | Nothing was clicked: Revyl didn't find the described target. A target that isn't there can also be clicked as a look-alike, so check the point `tap({ target })` returns |
78
+ | 503 `screen_target_unavailable` | `API_ERROR` | Nothing was clicked: Revyl couldn't look the target up just then. It is safe to try again |
79
+ | 422 `invalid_request` on a described tap | `NOT_AVAILABLE_ON_PLATFORM` | Nothing was clicked: this backend doesn't have described clicks yet. Pass a point |
80
+ | 422 `screen_point_outside` | `INVALID_ARGUMENT` | Nothing was clicked: the point is past the screen's edge. The message names the valid range |
81
+ | 422 `invalid_request` on a tap at a point | `INVALID_ARGUMENT` | Nothing was clicked: the computer refused the point, as when it is no longer on the screen after the display size changed. Take a new `screenshot()` and pick the point from it |
82
+ | 422 `invalid_request` on typing or a key | `INVALID_ARGUMENT` | Part of the action may have run. Take a `screenshot()` before you try again |
83
+ | 422 `screen_key_unsupported` | `INVALID_ARGUMENT` | Nothing was pressed: the computer can't press that key. Use a key `pressKey` lists, or `typeFocused` for a printable character |
84
+ | `screen_text_unsupported` | `INVALID_ARGUMENT` | Nothing was typed: the computer can't type some of the characters |
85
+ | `screen_input_uncertain`, `screen_unavailable`, `screen_input_unavailable` or `owner_unavailable` on an action (409 or 503), any other 5xx, or no answer at all | `ACTION_UNCERTAIN` | The action may have happened. Take a `screenshot()` before you do it again |
86
+ | 403 | `AUTH_REJECTED` | The key's owner needs the Member role or above; CI runner keys can't control the screen yet |
87
+ | 503 `authentication_unavailable` | `API_ERROR` | Nothing happened: Revyl couldn't check the key. It is safe to try again |
88
+
89
+ Any other refusal of a computer request names its reason and what to do next: `run_closed` (`SESSION_ENDED`) says to start a new computer, `inaccessible` (`NOT_FOUND`) says to check the id and the key's organization, a 401 or 403 (`AUTH_REJECTED`) says to check the key and its owner's role, and an answer Verify can't classify (`API_ERROR`) says to try again.
90
+
68
91
  ## Troubleshooting
69
92
 
70
- - **`NOT_AVAILABLE_ON_PLATFORM`:** the requested method is not available for that target. Computer targets do not run instruction or validation steps; Web and mobile targets do not expose computer commands or files.
93
+ - **`NOT_AVAILABLE_ON_PLATFORM`:** the requested method is not available for that target. Computer targets do not run instruction or validation steps or `locate()`; Web and mobile targets do not expose computer commands or files.
71
94
  - **`COMPUTER_LIMIT`:** computer admission was refused because of queued work or a concurrency limit. Verify does not retry creation or stop a definitely refused start.
72
95
  - **Blank first screen:** mobile startup uses a bounded, best-effort blank-frame observation, not a rendered-readiness guarantee. Begin with an instruction that reaches the screen your workflow needs. See [mobile.md](mobile.md#steps).
73
96
  - **A validation report looks successful but the assertion failed:** inspect each step's `effective_status` and `validation_result`, and compare `validations_passed` with `total_validations` rather than transport counters. See [reading the verdict](end-to-end.md#reading-the-verdict).
@@ -86,7 +86,7 @@ The generated iOS and Android targets open Settings. To test your own app, repla
86
86
 
87
87
  - **Web:** create a Web app in Revyl (see [web.md](web.md#create-the-web-app)) and set `app.url` to a public HTTPS URL. Revyl's cloud browser cannot reach `localhost`.
88
88
  - **iOS and Android:** upload a build to the app in Revyl, for example with `revyl build upload`; each session installs the newest one. See [mobile.md](mobile.md).
89
- - **Mac:** computers are early access; ask support@revyl.ai about access. Agent instruction/validation are unavailable. See [computers.md](computers.md#availability-and-errors).
89
+ - **Mac:** computers are available to organizations on a current paid Revyl plan. Agent instruction/validation are unavailable. See [computers.md](computers.md#availability-and-errors).
90
90
 
91
91
  ## Run the script
92
92
 
package/docs/llms.txt CHANGED
@@ -1,17 +1,17 @@
1
1
  # Verify
2
2
 
3
- > Verify (@revyl/verify) is an open-source TypeScript library for Revyl. It opens cloud browsers and iOS/Android devices for agent instruction/validation steps, or Mac computers for commands, files, processes, screenshots, and optional private recording metadata after release. Call it from a plain script, coding agent, CI, or any test runner. It has no test runner of its own.
3
+ > Verify (@revyl/verify) is an open-source TypeScript library for Revyl. It opens cloud browsers and iOS/Android devices for agent instruction/validation steps, or Mac computers for commands, files, processes, screenshots, clicks and keys on the screen, and optional private recording metadata after release. Call it from a plain script, coding agent, CI, or any test runner. It has no test runner of its own.
4
4
 
5
5
  ## Docs
6
6
 
7
- Mac computers are early access; ask support@revyl.ai about access. See computers.md#availability-and-errors.
7
+ Mac computers are available to organizations on a current paid Revyl plan. See computers.md#availability-and-errors.
8
8
 
9
9
  - [Getting started](getting-started.md): install, authenticate, init, run a script
10
10
  - [API](api.md): open(), attach(), hosted children, navigate(), report(), agent steps, screenshot(), exec(), file/process methods, close()
11
11
  - [Configuration](configuration.md): verify.config.ts targets, environment variables, the init and whoami commands
12
12
  - [Web](web.md): Revyl web sessions, URLs and builds, sign-in, steps, and exact actions
13
13
  - [Mobile](mobile.md): iOS and Android devices, installing apps, and exact actions
14
- - [Computers](computers.md#availability-and-errors): Mac early access, expiry, capacity errors, recording metadata, and confirmed release
14
+ - [Computers](computers.md#availability-and-errors): Mac availability, commands and files, the screen (clicks, typing and keys), expiry, capacity errors, recording metadata, and confirmed release
15
15
  - [Verify working end to end](end-to-end.md): live verification scope, observed outcomes, and limits of the evidence
16
16
  - [CI and test runners](ci.md): GitHub Actions, node:test, Vitest, Jest, Playwright Test
17
17
  - [Use your own model](own-model.md): run instructions and validations on Claude, GPT, Gemini, a local model or any OpenAI-compatible endpoint; providers, limits, privacy and troubleshooting
@@ -20,6 +20,6 @@ Mac computers are early access; ask support@revyl.ai about access. See computers
20
20
 
21
21
  ## Exact actions and coordinates
22
22
 
23
- Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points. See [For coding agents](agents.md#exact-actions-and-coordinates) for a runnable example.
23
+ Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points. On a Mac computer a point is in screenshot pixels too, and `tap({ target })` returns the point it clicked in the same pixels. Verify refuses a negative or fractional point before sending it, and Revyl refuses one past the screen's edge without clicking; both throw `INVALID_ARGUMENT` naming the valid range. See [For coding agents](agents.md#exact-actions-and-coordinates) for a runnable example.
24
24
 
25
- Exact actions are `tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `pressKey`, `locate` and `screenSize` on Web, iOS and Android. Web adds `typeFocused(text)`, which types into the field that has focus, and `pressKey` keys such as `'escape'`, `'tab'` and arrows with `modifiers`. iOS and Android add `goHome()`; their `pressKey` takes only `'enter'` and `'backspace'`, and you type with `type(location, text)`. A call the platform lacks throws `NOT_AVAILABLE_ON_PLATFORM` before anything is sent, naming what to use instead. See [api.md](api.md#exact-actions) for every call.
25
+ Exact actions are `tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `pressKey`, `locate` and `screenSize` on Web, iOS and Android. Web adds `typeFocused(text)`, which types into the field that has focus, and `pressKey` keys such as `'escape'`, `'tab'` and arrows with `modifiers`. iOS and Android add `goHome()`; their `pressKey` takes only `'enter'` and `'backspace'`, and you type with `type(location, text)`. A Mac computer's screen takes `tap({ x, y })`, `tap({ target })` (which returns the point it clicked; check it, because grounding can click a look-alike or something else when the target isn't there), `screenSize()`, `typeFocused(text, { clear: false })` and `pressKey` with the Web keys, where `'meta'` is Command: select all with `pressKey('a', { modifiers: ['meta'] })` there and `pressKey('a', { modifiers: ['ctrl'] })` on Web. See [computers.md](computers.md#screen). A call the platform lacks throws `NOT_AVAILABLE_ON_PLATFORM` before anything is sent, naming what to use instead. See [api.md](api.md#exact-actions) for every call.
package/docs/mobile.md CHANGED
@@ -3,8 +3,8 @@
3
3
  An `ios` or `android` target opens your app on a Revyl cloud simulator or emulator.
4
4
 
5
5
  ```ts
6
- { platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: '18.5' } }
7
- { platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 8' } }
6
+ { platform: 'ios', app: { appId: '…' }, device: { model: 'iPhone 16', osVersion: 'iOS 18.5' } }
7
+ { platform: 'android', app: { buildId: '…' }, device: { model: 'Pixel 7', osVersion: 'Android 14' } }
8
8
  { platform: 'android', app: { artifactUrl: 'https://ci.example.com/app-release.apk' } }
9
9
  { platform: 'ios', app: { bundleId: 'com.apple.Preferences' } } // an app already on the device
10
10
  ```
@@ -18,17 +18,17 @@ An `ios` or `android` target opens your app on a Revyl cloud simulator or emulat
18
18
 
19
19
  ## Devices
20
20
 
21
- `device.model` and `device.osVersion` pick the simulator or emulator. Without them, Revyl picks its default for the platform. To run the same script on several devices, give each its own target and open them one after another or in parallel:
21
+ `device.model` and `device.osVersion` pick the simulator or emulator. Write them as Revyl names them: `'iOS 18.5'` and `'Android 14'`, not `'18.5'` or `'14'`. Without them, Revyl picks its default for the platform, and with only a model, its default OS version, which that model must support. A value Revyl doesn't offer throws `INVALID_ARGUMENT` before any session starts, and the message lists the models or versions it accepts. To run the same script on several devices, give each its own target and open them one after another or in parallel:
22
22
 
23
23
  ```ts
24
24
  targets: {
25
- 'iphone-se': { platform: 'ios', app: { appId }, device: { model: 'iPhone SE (3rd generation)' } },
26
- 'iphone-16': { platform: 'ios', app: { appId }, device: { model: 'iPhone 16' } },
25
+ 'iphone-air': { platform: 'ios', app: { appId }, device: { model: 'iPhone Air' } },
26
+ 'iphone-16': { platform: 'ios', app: { appId }, device: { model: 'iPhone 16', osVersion: 'iOS 18.5' } },
27
27
  },
28
28
  ```
29
29
 
30
30
  ```ts
31
- for (const name of ['iphone-se', 'iphone-16']) {
31
+ for (const name of ['iphone-air', 'iphone-16']) {
32
32
  const session = await open(name);
33
33
  try {
34
34
  await session.validation('The onboarding screen fits without overlapping text');
@@ -46,11 +46,11 @@ After connection and any explicit bundle launch, `open()` makes a best-effort fi
46
46
 
47
47
  The generated mobile example starts with an instruction to wait for the first screen before validating it. For your own scripts, make the first instruction describe the screen or action you actually need; a screenshot with content can still show a loading screen or another app.
48
48
 
49
- For Android Settings, instruct the agent to scroll or navigate until the OS version is visible before validating it. Opening About alone can leave the version offscreen.
49
+ For Android Settings (`bundleId: 'com.android.settings'`), instruct the agent to scroll or navigate until the OS version is visible before validating it, as in `instruction('Open About phone, then scroll or navigate until the Android version is visible')`. Opening About alone can leave the version offscreen.
50
50
 
51
51
  ## Exact actions
52
52
 
53
- **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. The [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#exact-actions) READMEs show them with each language's names. When you know exactly what to press, skip the agent. Exact actions run on the device as given, with no model, and mix freely with `instruction()` and `validation()` in one script:
53
+ **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. The [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#exact-actions) READMEs show them with each language's names. When you know exactly what to press, skip the agent. Exact actions run on the device as given, with no model, and mix freely with `instruction()` and `validation()` in one script:
54
54
 
55
55
  ```ts
56
56
  import { open } from '@revyl/verify';
@@ -66,10 +66,10 @@ try {
66
66
  await session.swipe('down'); // the finger moves down, so the list scrolls to the top
67
67
  const search = await session.locate('Search field'); // a point in screenshot pixels
68
68
  await session.tap(search); // a tap by coordinates
69
- await session.type(search, 'Wallpaper');
69
+ await session.type(search, 'Accessibility');
70
70
  await session.pressKey('enter');
71
- await session.instruction('Open the Wallpaper result'); // hand the fuzzy part to the agent
72
- await session.validation('The Wallpaper settings are shown');
71
+ await session.instruction('Open the Accessibility result'); // hand the fuzzy part to the agent
72
+ await session.validation('The Accessibility settings are shown');
73
73
  } finally {
74
74
  await session.close();
75
75
  }
@@ -103,19 +103,21 @@ On Android, `back()` presses the system back button. On both platforms, `goHome(
103
103
 
104
104
  | Call | Web | iOS | Android | Mac computer |
105
105
  | --- | --- | --- | --- | --- |
106
- | `tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `locate`, `screenSize` | Yes | Yes | Yes | No, use `exec()` and files |
107
- | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | No |
108
- | `typeFocused(text)` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | No |
106
+ | `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
107
+ | `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
108
+ | `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
109
+ | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | Yes: the Web keys and modifiers, where `'meta'` is Command |
110
+ | `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
109
111
  | `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
110
112
  | `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
111
113
  | `openUrl(url)` | Yes, loads an http(s) URL in the tab | Yes, a URL or deep link | Yes, a URL or deep link | No |
112
114
  | `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
113
115
  | `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
114
- | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
116
+ | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
115
117
  | `screenshot()` | Yes | Yes | Yes | Yes |
116
118
  | `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
117
119
 
118
- **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points.
120
+ **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points. On a Mac computer a point is in screenshot pixels too, and `tap({ target })` returns the point it clicked in the same pixels. Verify refuses a negative or fractional point before sending it, and Revyl refuses one past the screen's edge without clicking; both throw `INVALID_ARGUMENT` naming the valid range.
119
121
 
120
122
  A call a platform doesn't support throws `NOT_AVAILABLE_ON_PLATFORM`, and its message names the alternative.
121
123
 
package/docs/own-model.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Use your own model
2
2
 
3
- **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0; the [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#your-own-model) READMEs show them with each language's names. Verify normally runs `instruction()` and `validation()` on Revyl's agent. Pass a `model` and the agent loop runs in your process instead, on a model you choose, with your own key. Revyl still provides the cloud browser or device and, by default, finds the elements your model names on screen.
3
+ **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0; the [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#your-own-model) READMEs show them with each language's names. Verify normally runs `instruction()` and `validation()` on Revyl's agent. Pass a `model` and the agent loop runs in your process instead, on a model you choose, with your own key. Revyl still provides the cloud browser or device and, by default, finds the elements your model names on screen.
4
4
 
5
5
  Use it when you want a specific model, your own prompts and key, or faster steps on a cheap model. Leave `model` out and nothing changes.
6
6
 
@@ -52,7 +52,7 @@ log.Println(step.Model.ID, len(step.Model.Turns), step.Model.InputTokens, step.M
52
52
  _, err = s.Validation(ctx, "The screen shows the iOS version")
53
53
  ```
54
54
 
55
- The [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#your-own-model) READMEs have the full reference for each.
55
+ The [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#your-own-model) READMEs have the full reference for each.
56
56
 
57
57
  Set `model` on `open()` to run every step on it, or on one step to override it:
58
58
 
@@ -67,7 +67,7 @@ The string is `'provider/model'`. Everything after the first slash is the provid
67
67
 
68
68
  | Prefix | Key variable | Endpoint | Status |
69
69
  | --- | --- | --- | --- |
70
- | `anthropic/` | `ANTHROPIC_API_KEY` | Anthropic Messages API | The one-line form run live against an Anthropic-compatible proxy with Claude Sonnet 5.5, real screenshots and tool schemas; the run on a Revyl device is pending |
70
+ | `anthropic/` | `ANTHROPIC_API_KEY` | Anthropic Messages API | Run live with Claude Sonnet 5.5 on Revyl iOS, Android and Web sessions in TypeScript, Python and Go, including `grounding: 'model'` on iOS |
71
71
  | `openai/` | `OPENAI_API_KEY` | `https://api.openai.com/v1` | Request shape checked; not yet run live |
72
72
  | `google/` | `GOOGLE_API_KEY`, or `GEMINI_API_KEY` when it is unset | `https://generativelanguage.googleapis.com/v1beta/openai/` | Request shape checked; not yet run live |
73
73
  | `openrouter/` | `OPENROUTER_API_KEY` | `https://openrouter.ai/api/v1` | Request shape checked; not yet run live |
@@ -97,7 +97,7 @@ Measured in production on Cerebras `qwen-3.8-27b`, a fast vision model (about 0.
97
97
  | | your model, `grounding: 'revyl'` | pass, 21.2 s | pass, 1.3 s | 4 + 1 | 7,854/355 + 1,380/136 |
98
98
  | | your model, `grounding: 'model'` | fail after 20 turns, 64.4 s | | 20 | 52,726 total |
99
99
 
100
- Each local turn costs one model call plus one device action: about 3.7 s per turn on iOS and 5.3 s on Web here, most of it the device action and the screen settling. These are two short flows on one model, so treat them as a direction, not a benchmark. Claude, GPT and Gemini have not been measured yet, and `grounding: 'model'` has not yet been shown to pass with any model.
100
+ Each local turn costs one model call plus one device action: about 3.7 s per turn on iOS and 5.3 s on Web here, most of it the device action and the screen settling. These are two short flows on one model, so treat them as a direction, not a benchmark. GPT and Gemini have not been measured yet. Claude Sonnet 5.5 passed with `grounding: 'model'` on iOS Settings in 3 turns; that is one run, so treat `grounding: 'model'` as dependent on the model.
101
101
 
102
102
  Start with the default grounding and the cheapest vision model your provider has. Move to a stronger model if steps run out of turns or pick the wrong elements.
103
103
 
package/docs/web.md CHANGED
@@ -53,7 +53,7 @@ Revyl runs one step at a time on a session. A step sent while another is still r
53
53
 
54
54
  ## Exact actions
55
55
 
56
- **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. The [Python](https://github.com/RevylAI/verify/blob/v0.5.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.5.0/go/README.md#exact-actions) READMEs show them with each language's names. When you know exactly what to click, skip the agent. Exact actions run in the browser as given, with no model, and mix freely with `instruction()` and `validation()`:
56
+ **Early access.** Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v0.3.0. `doubleTap`, `longPress`, `drag`, `typeFocused`, `goHome`, and Web keys and modifiers in `pressKey`, are new in TypeScript 0.5.0, Python 0.3.0 and Go v0.4.0. The [Python](https://github.com/RevylAI/verify/blob/v0.6.1/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.1/go/README.md#exact-actions) READMEs show them with each language's names. When you know exactly what to click, skip the agent. Exact actions run in the browser as given, with no model, and mix freely with `instruction()` and `validation()`:
57
57
 
58
58
  ```ts
59
59
  import { open } from '@revyl/verify';
@@ -66,6 +66,7 @@ try {
66
66
  await session.validation('A list of products is shown');
67
67
  await session.swipe('up'); // the finger moves up, so the page scrolls down
68
68
  await session.instruction('Add the Sauce Labs Onesie to the cart'); // hand the fuzzy part to the agent
69
+ await session.swipe('down'); // back to the top, so the cart icon is on screen
69
70
  const cart = await session.locate('Shopping cart icon'); // a point in screenshot pixels
70
71
  await session.tap(cart); // a tap by coordinates
71
72
  await session.validation('The cart lists the Sauce Labs Onesie');
@@ -103,23 +104,25 @@ try {
103
104
  }
104
105
  ```
105
106
 
106
- `typeFocused(text)` types where the focus already is, without clicking, and replaces the field's text unless you pass `{ clear: false }`. Focus the field first, with `tap()` or `pressKey('tab')`: with no field focused, nothing is typed and no error is thrown. `pressKey()` takes `'enter'`, `'backspace'`, `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` and `'pagedown'`, with up to four `modifiers` held (`'ctrl'`, `'shift'`, `'alt'`, `'meta'`); with modifiers a lowercase letter or digit works too. `longPress()` holds for 1500 ms unless you pass `duration`, from 1 to 5000. `drag(from, to)` presses at one location, moves to the other and releases. A failed action throws `ACTION_FAILED`. One whose outcome Revyl can't confirm throws `ACTION_UNCERTAIN` or `TIMEOUT`, so check the session before acting again. Actions are never repeated. If Verify can't reach Revyl, the action fails with `API_ERROR` and isn't resent either, so check the session before trying it again. [api.md](api.md#exact-actions) lists every call.
107
+ `typeFocused(text)` types where the focus already is, without clicking, and replaces the field's text unless you pass `{ clear: false }`. Focus the field first, with `tap()` or `pressKey('tab')`: with no field focused, nothing is typed and no error is thrown. `pressKey()` takes `'enter'`, `'backspace'`, `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` and `'pagedown'`, with up to four `modifiers` held (`'ctrl'`, `'shift'`, `'alt'`, `'meta'`); with modifiers a lowercase letter or digit works too. The shortcut key on Web is `'ctrl'`, so select all with `pressKey('a', { modifiers: ['ctrl'] })`: `'meta'` is not the shortcut key here, though it is Command on a [Mac computer](computers.md#screen). `longPress()` holds for 1500 ms unless you pass `duration`, from 1 to 5000. `drag(from, to)` presses at one location, moves to the other and releases. A failed action throws `ACTION_FAILED`. One whose outcome Revyl can't confirm throws `ACTION_UNCERTAIN` or `TIMEOUT`, so check the session before acting again. Actions are never repeated. If Verify can't reach Revyl, the action fails with `API_ERROR` and isn't resent either, so check the session before trying it again. [api.md](api.md#exact-actions) lists every call.
107
108
 
108
109
  | Call | Web | iOS | Android | Mac computer |
109
110
  | --- | --- | --- | --- | --- |
110
- | `tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `locate`, `screenSize` | Yes | Yes | Yes | No, use `exec()` and files |
111
- | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | No |
112
- | `typeFocused(text)` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | No |
111
+ | `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
112
+ | `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
113
+ | `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
114
+ | `pressKey(key, { modifiers })` | Yes: `'enter'`, `'backspace'`, `'tab'`, `'escape'`, arrows and more, with modifiers | `'enter'` and `'backspace'` only | `'enter'` and `'backspace'` only | Yes: the Web keys and modifiers, where `'meta'` is Command |
115
+ | `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
113
116
  | `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
114
117
  | `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
115
118
  | `openUrl(url)` | Yes, loads an http(s) URL in the tab | Yes, a URL or deep link | Yes, a URL or deep link | No |
116
119
  | `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
117
120
  | `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
118
- | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
121
+ | `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
119
122
  | `screenshot()` | Yes | Yes | Yes | Yes |
120
123
  | `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
121
124
 
122
- **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points.
125
+ **Coordinates.** Prefer a description, such as `tap({ target: 'Sign in button' })`: Revyl finds the element on the current screen, and you never handle coordinates. Otherwise a point is `{ x, y }` in screenshot pixels on every platform: whole numbers from the top-left corner of the image `screenshot()` returns. So a position you read off a screenshot goes to `tap()`, `type()`, `swipe()` and every other exact action unchanged. `screenSize()` returns the screenshot's width and height, and `locate()` and the points `tap()`, `doubleTap()`, `longPress()` and `type()` return use the same pixels. A point outside the screenshot is refused with `INVALID_ARGUMENT` before any action is sent, and the message names the valid range. On iOS, Verify converts the pixels to the screen's points for you. On Web they equal CSS pixels at the default device scale factor of 1, and you still pass screenshot pixels at any other. This holds from TypeScript 0.4.1, Python 0.2.1 and Go v0.3.1; earlier versions took iOS points. On a Mac computer a point is in screenshot pixels too, and `tap({ target })` returns the point it clicked in the same pixels. Verify refuses a negative or fractional point before sending it, and Revyl refuses one past the screen's edge without clicking; both throw `INVALID_ARGUMENT` naming the valid range.
123
126
 
124
127
  A call a platform doesn't support throws `NOT_AVAILABLE_ON_PLATFORM`, and its message names the alternative.
125
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revyl/verify",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Open Revyl cloud browsers and mobile devices for agent steps, or Mac computers for commands and files, from TypeScript.",
5
5
  "keywords": [
6
6
  "e2e",