@revyl/verify 0.5.0 → 0.6.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 +29 -3
- package/README.md +20 -18
- package/dist/computer-session.d.ts +18 -4
- package/dist/computer-session.js +167 -16
- package/dist/session.d.ts +5 -4
- package/dist/session.js +1 -1
- package/docs/README.md +1 -1
- package/docs/agents.md +4 -4
- package/docs/api.md +15 -13
- package/docs/ci.md +1 -1
- package/docs/computers.md +71 -2
- package/docs/errors.md +28 -5
- package/docs/llms.txt +4 -4
- package/docs/mobile.md +8 -6
- package/docs/own-model.md +2 -2
- package/docs/web.md +9 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.6.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
- Early access: a Mac computer's screen. `tap({ x, y })` clicks a point in screenshot pixels and resolves with it, `screenSize()` returns the screenshot's size, `typeFocused(text, { clear: false })` types where the cursor is, and `pressKey(key, { modifiers })` takes the Web keys and modifiers, where `'meta'` is Command and `'alt'` is Option, as in `pressKey('a', { modifiers: ['meta'] })` to select all.
|
|
6
|
+
- `typeFocused` on a computer requires `{ clear: false }` and throws `INVALID_ARGUMENT` without it, naming the fix. It takes up to 16,384 characters, with line breaks and tabs; other control characters are refused before anything is sent. Text over 500 characters goes in pieces, in order, and a failure part way says how many characters were certainly typed and whether the failed piece may have been.
|
|
7
|
+
- A point past the screen's edge throws `INVALID_ARGUMENT` naming the valid range, and nothing is clicked; negative and fractional points are refused before anything is sent. There is no extra request per click.
|
|
8
|
+
- Screen errors: someone controlling the screen in the Revyl viewer (`screen_control_held`) throws `SESSION_BUSY` and nothing happened; an action Revyl can't confirm (`screen_input_uncertain`, `screen_unavailable`, `screen_input_unavailable`) throws `ACTION_UNCERTAIN`; a 403 throws `AUTH_REJECTED` saying the key's owner needs the Member role or above and CI runner keys can't control the screen yet; a 503 `authentication_unavailable` throws `API_ERROR` and is safe to retry. These are read from the answer's `detail`, so `screen_unavailable` is uncertain as a 409 or a 503. Verify never repeats a screen action, except `screen_starting` (409, nothing happened): it waits as `Retry-After` asks and tries again for up to 30 seconds, then throws `SESSION_BUSY` naming when to try again.
|
|
9
|
+
- `tap({ target })` on a Mac computer: Revyl finds the described element on a fresh screenshot, clicks it, and `tap` resolves with the point it clicked. It takes several seconds and can click a look-alike, or something else when the target isn't on the screen, so the docs say to check the point or a screenshot afterwards. `screen_target_not_found` throws `ACTION_FAILED` and `screen_target_unavailable` throws `API_ERROR`, both with nothing clicked; a backend without described clicks (422 `invalid_request`) throws `NOT_AVAILABLE_ON_PLATFORM` saying to pass a point.
|
|
10
|
+
- `locate()` and the other exact actions aren't available on computers. They throw `NOT_AVAILABLE_ON_PLATFORM` before anything is sent, naming `tap({ x, y })`, `tap({ target })`, `typeFocused`, `pressKey` and `exec()`.
|
|
11
|
+
- Every other call sends the same requests as 0.5.0.
|
|
12
|
+
- Error messages on a computer say what happened and what to do next. `screen_control_lost` on a screen action throws `SESSION_BUSY` saying nothing was sent and to try again. `owner_unavailable`, a 5xx or no answer on a screen action throws `ACTION_UNCERTAIN` saying to take a screenshot before repeating it. `invalid_request` on `tap({ x, y })` says nothing was clicked and to pick the point from a new screenshot, and `screen_key_unsupported` says nothing was pressed. Partial typing counts characters as Unicode code points, the same in TypeScript, Python and Go, and a failure in the first piece says nothing was certainly typed. Every other computer refusal adds a next step, and the `screen_starting` message says "1 second" rather than "1 seconds".
|
|
13
|
+
- Docs: a Screen section in `computers.md`, the platform table, `api.md`, `errors.md`, `agents.md` and `llms.txt`. The Web docs say `'meta'` is not the shortcut key there; use `'ctrl'`.
|
|
14
|
+
|
|
15
|
+
## Go 0.5.0 (unreleased)
|
|
16
|
+
|
|
17
|
+
Published as the `go/v0.5.0` tag.
|
|
18
|
+
|
|
19
|
+
- Early access: a Mac computer's screen, as in TypeScript 0.6.0. `Tap(ctx, verify.Point{X, Y})`, `Tap(ctx, verify.Element("description"))` returning the point it clicked, `ScreenSize(ctx)`, `TypeFocused(ctx, text, TypeOptions{Clear})` with `Clear` pointing to `false`, and `PressKey(ctx, key, KeyOptions{Modifiers})`, where `ModifierMeta` is Command. Leaving `Clear` unset returns `ErrInvalidArgument` naming the fix.
|
|
20
|
+
- The same limits, errors and refusals as TypeScript 0.6.0: `ErrSessionBusy` for `screen_control_held`, `ErrActionUncertain` for an action Revyl can't confirm, `ErrAuthRejected` for a 403, `ErrActionFailed` for `screen_target_not_found`, a wait of up to 30 seconds on `screen_starting`, and `ErrNotAvailableOnPlatform` before any request for `Locate` and the other exact actions. Every other call sends the same requests as v0.4.0.
|
|
21
|
+
- The same error messages as TypeScript 0.6.0, each with a next step, with partial typing counted in Unicode code points.
|
|
22
|
+
|
|
23
|
+
## Python 0.4.0 (unreleased)
|
|
24
|
+
|
|
25
|
+
- Early access: a Mac computer's screen, as in TypeScript 0.6.0. `tap(Point(x, y))`, `tap("description")` returning the point it clicked, `screen_size()`, `type_focused(text, clear=False)` and `press_key(key, modifiers=[...])`, where `"meta"` is Command. Leaving `clear` out raises `INVALID_ARGUMENT` naming the fix. `AsyncSession` has the same methods.
|
|
26
|
+
- The same limits, errors and refusals as TypeScript 0.6.0, and every other call sends the same requests as 0.3.0.
|
|
27
|
+
- The same error messages as TypeScript 0.6.0, each with a next step, with partial typing counted in Unicode code points.
|
|
28
|
+
|
|
29
|
+
## 0.5.0 (2026-11-12)
|
|
4
30
|
|
|
5
31
|
- Early access: more exact actions on Web, iOS and Android. `doubleTap(at)` double-taps (double-clicks on Web), `longPress(at, { duration })` presses and holds for `duration` milliseconds (1 to 5000, 1500 by default), and `drag(from, to)` presses at one location, moves to the other and releases. They take a description or screenshot pixels like `tap`, and `doubleTap` and `longPress` resolve with the point.
|
|
6
32
|
- Web: `typeFocused(text, { clear })` types into the field that already has focus, without clicking, and replaces its text unless `clear` is `false`, the same default as `type()`. `pressKey` also takes `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` and `'pagedown'`, and `{ modifiers }`: up to four of `'ctrl'`, `'shift'`, `'alt'` and `'meta'`, with which a lowercase letter or digit works too, as in `pressKey('a', { modifiers: ['ctrl'] })`. The `Key` and `KeyModifier` types are exported.
|
|
@@ -10,7 +36,7 @@
|
|
|
10
36
|
- Every existing call sends the same requests as 0.4.1. Your own model choosing `open_url` with a deep link on Web gets the `openUrl()` wording.
|
|
11
37
|
- Docs: the platform table, `api.md`, the web and mobile guides with a script for the new calls, `agents.md` and `llms.txt`.
|
|
12
38
|
|
|
13
|
-
## Go 0.4.0 (
|
|
39
|
+
## Go 0.4.0 (2026-11-12)
|
|
14
40
|
|
|
15
41
|
Published as the `go/v0.4.0` tag.
|
|
16
42
|
|
|
@@ -20,7 +46,7 @@ Published as the `go/v0.4.0` tag.
|
|
|
20
46
|
- A call the platform lacks returns `ErrNotAvailableOnPlatform` before anything is sent, naming the alternative. Your own model can choose the new actions with both groundings, so it sees more tools; the tools it had send the same requests as v0.3.1, as does every existing call.
|
|
21
47
|
- `ErrInstructionFailed` names the step and the reason, as TypeScript does: `Instruction failed: "<text>". <reason> See <link>`. The uncertain-action message says "The step deadline passed". Your own model choosing `open_url` with a deep link on Web gets the `OpenURL` wording.
|
|
22
48
|
|
|
23
|
-
## Python 0.3.0 (
|
|
49
|
+
## Python 0.3.0 (2026-11-12)
|
|
24
50
|
|
|
25
51
|
- Early access: more exact actions on Web, iOS and Android, as in TypeScript 0.5.0. `double_tap(at)` and `long_press(at, duration=None)` return the point; `duration` is seconds, 0.001 to 5 in whole milliseconds, 1.5 by default. `drag(start, end)` presses, moves and releases.
|
|
26
52
|
- Web: `type_focused(text, clear=True)` types into the field that already has focus and replaces its text unless `clear` is `False`. `press_key(key, modifiers=())` adds `"tab"`, `"escape"`, `"delete"`, `"space"`, the arrows, `"home"`, `"end"`, `"pageup"` and `"pagedown"`, and modifiers (`"ctrl"`, `"shift"`, `"alt"`, `"meta"`), with which a lowercase letter or digit works too. `Key` and `KeyModifier` are exported.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Verify
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
|
-
Verify runs end-to-end checks in a cloud browser, iOS simulator, or Android emulator on Revyl. You write what the agent should do and what must be true afterward, in plain sentences, and call it from a script, a coding agent, CI, or the test runner you already use. When you know exactly what to press, you can tap, type and swipe yourself instead. Verify also opens Mac computers for commands, files, and
|
|
5
|
+
Verify runs end-to-end checks in a cloud browser, iOS simulator, or Android emulator on Revyl. You write what the agent should do and what must be true afterward, in plain sentences, and call it from a script, a coding agent, CI, or the test runner you already use. When you know exactly what to press, you can tap, type and swipe yourself instead. Verify also opens Mac computers for commands, files, screenshots, and clicks and keys on the screen.
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import { open } from '@revyl/verify';
|
|
@@ -41,9 +41,9 @@ Or set it up yourself:
|
|
|
41
41
|
npx @revyl/verify init
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`init` adds Verify to your project, asks what to open, writes `verify.config.ts` and an example script, signs you in through the browser on the first interactive run, and prints the command that runs the example. With no app id, the iOS and Android examples open the built-in Settings app, so they run on a new account before you upload anything. To skip the config, run `npm install @revyl/verify` and save the example above as `verify/check.mts`, then run `node verify/check.mts`. [Getting started](https://github.com/RevylAI/verify/blob/v0.
|
|
44
|
+
`init` adds Verify to your project, asks what to open, writes `verify.config.ts` and an example script, signs you in through the browser on the first interactive run, and prints the command that runs the example. With no app id, the iOS and Android examples open the built-in Settings app, so they run on a new account before you upload anything. To skip the config, run `npm install @revyl/verify` and save the example above as `verify/check.mts`, then run `node verify/check.mts`. [Getting started](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/getting-started.md) covers sign-in, your own app, and [CI](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/ci.md).
|
|
45
45
|
|
|
46
|
-
For finished projects, see [`examples/`](https://github.com/RevylAI/verify/tree/v0.
|
|
46
|
+
For finished projects, see [`examples/`](https://github.com/RevylAI/verify/tree/v0.6.0/examples/): a [web store](https://github.com/RevylAI/verify/tree/v0.6.0/examples/web-store/), [mobile Settings](https://github.com/RevylAI/verify/tree/v0.6.0/examples/mobile-settings/), a [Mac workspace](https://github.com/RevylAI/verify/tree/v0.6.0/examples/mac-workspace/), a small [agent platform](https://github.com/RevylAI/verify/tree/v0.6.0/examples/agent-platform/) that runs web and iOS side by side, and [your own model](https://github.com/RevylAI/verify/tree/v0.6.0/examples/byo-model/) (with [Python](https://github.com/RevylAI/verify/blob/v0.6.0/python/examples/byo_model.py) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/examples/byomodel/main.go) versions).
|
|
47
47
|
|
|
48
48
|
## Exact actions
|
|
49
49
|
|
|
@@ -60,7 +60,7 @@ await session.instruction('Accept the terms if they are shown'); // the age
|
|
|
60
60
|
await session.validation('The welcome screen is shown');
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
`doubleTap`, `typeFocused`, `back`, `goHome`, `openUrl`, `launchApp`, `locate` and `screenSize` round them out; [which call works where](#which-call-works-where) shows the platform limits. 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.
|
|
63
|
+
`doubleTap`, `typeFocused`, `back`, `goHome`, `openUrl`, `launchApp`, `locate` and `screenSize` round them out; [which call works where](#which-call-works-where) shows the platform limits. 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. On a Mac computer's screen, `tap` (by point or by description), `screenSize`, `typeFocused` and `pressKey` are new in TypeScript 0.6.0, Python 0.4.0 and Go v0.5.0. The [Python](https://github.com/RevylAI/verify/blob/v0.6.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/README.md#exact-actions) READMEs show them with each language's names. The [web](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/web.md#exact-actions) and [mobile](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/mobile.md#exact-actions) guides have runnable scripts, and [api.md](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/api.md#exact-actions) lists every call.
|
|
64
64
|
|
|
65
65
|
## Your own model
|
|
66
66
|
|
|
@@ -74,7 +74,7 @@ await session.instruction('Open Privacy', { model: 'openai/gpt-5.5' });
|
|
|
74
74
|
await open('ios', { model: { id: 'qwen-3.8-27b', baseURL: 'https://api.cerebras.ai/v1', apiKey: process.env.CEREBRAS_API_KEY } });
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
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.
|
|
77
|
+
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.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/README.md#your-own-model) READMEs show them with each language's names. Built in: `anthropic`, `openai`, `google`, `openrouter`, `groq`, `cerebras`, `xai` and `ollama`; anything OpenAI-compatible works through `baseURL`. Early access; [Use your own model](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/own-model.md) covers the providers, picking a model, what Revyl still sees, limits and troubleshooting.
|
|
78
78
|
|
|
79
79
|
## Packages
|
|
80
80
|
|
|
@@ -82,8 +82,8 @@ Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v
|
|
|
82
82
|
| --- | --- | --- |
|
|
83
83
|
| [`@revyl/verify`](https://www.npmjs.com/package/@revyl/verify) | `npm install @revyl/verify` | TypeScript SDK and the `verify` CLI (`init`, `login`, `logout`, `whoami`). Published on npm, Node.js 22.18 or later. |
|
|
84
84
|
| [`revyl-verify`](https://www.npmjs.com/package/revyl-verify) (npm) | `npm install revyl-verify` | Alias of `@revyl/verify` at the same version. |
|
|
85
|
-
| [Go module](https://github.com/RevylAI/verify/tree/go/v0.
|
|
86
|
-
| [`revyl-verify`](https://pypi.org/project/revyl-verify/) (PyPI) | `pip install revyl-verify` or `uv add revyl-verify` | Python SDK, published on PyPI. Python 3.10 or later, sync and asyncio APIs, no CLI; see the [Python guide](https://github.com/RevylAI/verify/blob/v0.
|
|
85
|
+
| [Go module](https://github.com/RevylAI/verify/tree/go/v0.5.0/go) | `go get github.com/RevylAI/verify/go@v0.5.0` | Published as the `go/v0.5.0` tag, Go 1.26.9 or later. Calls return errors instead of throwing. |
|
|
86
|
+
| [`revyl-verify`](https://pypi.org/project/revyl-verify/) (PyPI) | `pip install revyl-verify` or `uv add revyl-verify` | Python SDK, published on PyPI. Python 3.10 or later, sync and asyncio APIs, no CLI; see the [Python guide](https://github.com/RevylAI/verify/blob/v0.6.0/python/README.md). |
|
|
87
87
|
|
|
88
88
|
## Platforms
|
|
89
89
|
|
|
@@ -92,38 +92,40 @@ Since TypeScript (`@revyl/verify`) 0.4.0, Python (`revyl-verify`) 0.2.0 and Go v
|
|
|
92
92
|
| Web | Yes | Yes | — | Yes |
|
|
93
93
|
| iOS simulator | Yes | Yes | — | Yes |
|
|
94
94
|
| Android emulator | Yes | Yes | — | Yes |
|
|
95
|
-
| Mac computer | — |
|
|
95
|
+
| Mac computer | — | Screen: `tap`, `typeFocused`, `pressKey` | Yes | Yes |
|
|
96
96
|
|
|
97
97
|
### Which call works where
|
|
98
98
|
|
|
99
99
|
| Call | Web | iOS | Android | Mac computer |
|
|
100
100
|
| --- | --- | --- | --- | --- |
|
|
101
|
-
| `tap
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
101
|
+
| `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
|
|
102
|
+
| `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
|
|
103
|
+
| `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
|
|
104
|
+
| `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 |
|
|
105
|
+
| `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
|
|
104
106
|
| `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
|
|
105
107
|
| `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
|
|
106
108
|
| `openUrl(url)` | Yes, loads an http(s) URL in the tab | Yes, a URL or deep link | Yes, a URL or deep link | No |
|
|
107
109
|
| `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
|
|
108
110
|
| `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
|
|
109
|
-
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
|
|
111
|
+
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
|
|
110
112
|
| `screenshot()` | Yes | Yes | Yes | Yes |
|
|
111
113
|
| `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
|
|
112
114
|
|
|
113
|
-
**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.
|
|
115
|
+
**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.
|
|
114
116
|
|
|
115
117
|
A call a platform doesn't support throws `NOT_AVAILABLE_ON_PLATFORM`, and its message names the alternative.
|
|
116
118
|
|
|
117
|
-
Web is available to every organization. Mac computers are early access and have a server-bounded lifetime; see [computers](https://github.com/RevylAI/verify/blob/v0.
|
|
119
|
+
Web is available to every organization. Mac computers are early access and have a server-bounded lifetime; see [computers](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/computers.md).
|
|
118
120
|
|
|
119
121
|
## Documentation
|
|
120
122
|
|
|
121
|
-
The [documentation](https://github.com/RevylAI/verify/tree/v0.
|
|
123
|
+
The [documentation](https://github.com/RevylAI/verify/tree/v0.6.0/packages/verify/docs/) covers the [API](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/api.md), [configuration](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/configuration.md), [web](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/web.md), [mobile](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/mobile.md), [computers](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/computers.md), [CI](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/ci.md), [your own model](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/own-model.md), [errors and troubleshooting](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/errors.md), [coding agents](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/agents.md), and [live end-to-end results](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/end-to-end.md). The npm package ships these pages in `node_modules/@revyl/verify/docs/`, with an [llms.txt](https://github.com/RevylAI/verify/blob/v0.6.0/packages/verify/docs/llms.txt) index, so a coding agent can read the docs for the version you installed. The Revyl docs site has an overview at [docs.revyl.com/verify](https://docs.revyl.com/verify).
|
|
122
124
|
|
|
123
125
|
## Status
|
|
124
126
|
|
|
125
|
-
Verify is at 0.x, so APIs can still change between minor releases; the [changelog](https://github.com/RevylAI/verify/blob/v0.
|
|
127
|
+
Verify is at 0.x, so APIs can still change between minor releases; the [changelog](https://github.com/RevylAI/verify/blob/v0.6.0/CHANGELOG.md) records each release. Mac computers are early access. See [Contributing](https://github.com/RevylAI/verify/blob/v0.6.0/CONTRIBUTING.md) and [Releasing](https://github.com/RevylAI/verify/blob/v0.6.0/RELEASING.md) to work on Verify.
|
|
126
128
|
|
|
127
129
|
## License
|
|
128
130
|
|
|
129
|
-
[Apache 2.0](https://github.com/RevylAI/verify/blob/v0.
|
|
131
|
+
[Apache 2.0](https://github.com/RevylAI/verify/blob/v0.6.0/LICENSE).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RevylClient } from './api/client.js';
|
|
2
2
|
import type { ComputerPlatform } from './api/types.js';
|
|
3
3
|
import { type HostedTargetConfig, type ResolvedComputerTarget } from './config.js';
|
|
4
|
+
import { type Key, type KeyModifier, type Location, type Point } from './agent/loop.js';
|
|
4
5
|
import { type ComputerProcess, type ComputerRecording, type ExecOptions, type ExecResult, type FileStat, type OpenedSession, type OperationOptions, type Session, type SessionOptions, type StepResult } from './session.js';
|
|
5
6
|
export declare class ComputerSession implements Session {
|
|
6
7
|
private readonly client;
|
|
@@ -27,20 +28,32 @@ export declare class ComputerSession implements Session {
|
|
|
27
28
|
open(target: HostedTargetConfig): Promise<OpenedSession>;
|
|
28
29
|
report(): Promise<Record<string, unknown>>;
|
|
29
30
|
navigate(): Promise<never>;
|
|
30
|
-
|
|
31
|
+
/** Clicks a point in screenshot pixels, or an element Revyl finds from a description, with the left button. Returns the point clicked. */
|
|
32
|
+
tap(at: Location): Promise<Point>;
|
|
33
|
+
private tapTarget;
|
|
31
34
|
type(): Promise<never>;
|
|
32
|
-
|
|
35
|
+
/** Types into whatever has keyboard focus. Computers can't clear a field, so pass { clear: false }. */
|
|
36
|
+
typeFocused(text: string, options?: {
|
|
37
|
+
clear?: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
33
39
|
doubleTap(): Promise<never>;
|
|
34
40
|
longPress(): Promise<never>;
|
|
35
41
|
swipe(): Promise<never>;
|
|
36
42
|
drag(): Promise<never>;
|
|
37
|
-
|
|
43
|
+
/** Presses a key with modifiers held. 'meta' is the Command key on a Mac computer, and 'alt' is Option. */
|
|
44
|
+
pressKey(key: Key | string, options?: {
|
|
45
|
+
modifiers?: KeyModifier[];
|
|
46
|
+
}): Promise<void>;
|
|
38
47
|
back(): Promise<never>;
|
|
39
48
|
goHome(): Promise<never>;
|
|
40
49
|
openUrl(): Promise<never>;
|
|
41
50
|
launchApp(): Promise<never>;
|
|
42
51
|
locate(): Promise<never>;
|
|
43
|
-
|
|
52
|
+
/** The screenshot's size in pixels, the space tap() uses. */
|
|
53
|
+
screenSize(): Promise<{
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
}>;
|
|
44
57
|
get expiresAt(): Date | undefined;
|
|
45
58
|
recording(): Promise<ComputerRecording | null>;
|
|
46
59
|
instruction(): Promise<never>;
|
|
@@ -58,6 +71,7 @@ export declare class ComputerSession implements Session {
|
|
|
58
71
|
[Symbol.asyncDispose](): Promise<void>;
|
|
59
72
|
private operation;
|
|
60
73
|
private request;
|
|
74
|
+
private act;
|
|
61
75
|
private parseReport;
|
|
62
76
|
private assertOpen;
|
|
63
77
|
private observationSignal;
|
package/dist/computer-session.js
CHANGED
|
@@ -6,13 +6,26 @@ import { edgeBlockedError, responseDetail, responseErrorCode, responseRefusalRea
|
|
|
6
6
|
import { trackSession, untrackSession } from './cleanup.js';
|
|
7
7
|
import { validateHostedTarget } from './config.js';
|
|
8
8
|
import { VerifyError, isVerifyError } from './errors.js';
|
|
9
|
-
import {
|
|
9
|
+
import { WEB_KEYS, checkKey } from './agent/loop.js';
|
|
10
|
+
import { DeviceSession, checkLocation, pngSize } from './session.js';
|
|
10
11
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
12
|
const STATES = new Set(['starting', 'active', 'closing', 'stopped', 'failed', 'owner_lost']);
|
|
12
13
|
const MAX_OUTPUT_BYTES = 1_048_576;
|
|
13
14
|
const COMPUTER_AVAILABILITY = 'Computers are early access. Ask support@revyl.ai about access.';
|
|
14
15
|
const COMPUTER_CAPACITY = 'Computer admission was refused because of queued work (including iOS) or the concurrency limit. Verify will not retry the start.';
|
|
15
|
-
const ERROR_CODES = new Set(['computer_not_enabled', 'run_closed', 'inaccessible', 'invalid_handle', 'operation_not_found', 'owner_unavailable', 'invalid_request', 'unauthenticated', 'forbidden', 'runtime_unavailable', 'start_failed', 'computer_limit', 'request_conflict', 'run_conflict', 'evidence_conflict', 'evidence_unavailable', 'authentication_unavailable', 'operation_limit', 'screen_starting', 'screen_unavailable']);
|
|
16
|
+
const ERROR_CODES = new Set(['computer_not_enabled', 'run_closed', 'inaccessible', 'invalid_handle', 'operation_not_found', 'owner_unavailable', 'invalid_request', 'unauthenticated', 'forbidden', 'runtime_unavailable', 'start_failed', 'computer_limit', 'request_conflict', 'run_conflict', 'evidence_conflict', 'evidence_unavailable', 'authentication_unavailable', 'operation_limit', 'screen_starting', 'screen_unavailable', 'screen_control_held', 'screen_control_lost', 'screen_input_unavailable', 'screen_input_uncertain', 'screen_key_unsupported', 'screen_text_unsupported', 'screen_target_not_found', 'screen_target_unavailable']);
|
|
17
|
+
const COMPUTER_KEYS = { enter: 'Return', backspace: 'BackSpace', tab: 'Tab', escape: 'Escape', delete: 'Delete', space: 'space', arrowup: 'Up', arrowdown: 'Down', arrowleft: 'Left', arrowright: 'Right', home: 'Home', end: 'End', pageup: 'Page_Up', pagedown: 'Page_Down' };
|
|
18
|
+
const REFUSAL_NEXT_STEPS = {
|
|
19
|
+
AUTH_REJECTED: "Check that REVYL_API_KEY is a current key and that its owner has the Member role or above in this computer's organization.",
|
|
20
|
+
SESSION_ENDED: 'This computer has stopped; start a new computer to continue.',
|
|
21
|
+
NOT_FOUND: 'Check that the id is right and that REVYL_API_KEY belongs to the organization that started this computer.',
|
|
22
|
+
ACTION_UNCERTAIN: "The request may have taken effect. Verify will not repeat it; check the computer's state before you do.",
|
|
23
|
+
INVALID_ARGUMENT: "Check the call's arguments against the docs; if they match, update Verify to the latest version.",
|
|
24
|
+
SESSION_START_FAILED: 'Start a new computer.',
|
|
25
|
+
SESSION_BUSY: 'The computer is busy with other work; try again once it finishes.',
|
|
26
|
+
API_ERROR: 'Try again; if it keeps failing, send this message to support@revyl.ai.',
|
|
27
|
+
};
|
|
28
|
+
const SCREEN_CALLS = 'For the screen, use screenshot(), tap({ x, y }) or tap({ target }), typeFocused(text, { clear: false }) and pressKey(); for commands, use exec().';
|
|
16
29
|
export class ComputerSession {
|
|
17
30
|
client;
|
|
18
31
|
options;
|
|
@@ -123,14 +136,89 @@ export class ComputerSession {
|
|
|
123
136
|
navigate() {
|
|
124
137
|
return this.unavailable('navigate');
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
/** Clicks a point in screenshot pixels, or an element Revyl finds from a description, with the left button. Returns the point clicked. */
|
|
140
|
+
async tap(at) {
|
|
141
|
+
if (at && typeof at === 'object' && 'target' in at && ('x' in at || 'y' in at))
|
|
142
|
+
throw new VerifyError('INVALID_ARGUMENT', 'tap() takes a point { x, y } or a description { target }, not both.');
|
|
143
|
+
const location = checkLocation(at);
|
|
144
|
+
if ('target' in location)
|
|
145
|
+
return this.tapTarget(location.target);
|
|
146
|
+
const { x, y } = location;
|
|
147
|
+
let response;
|
|
148
|
+
try {
|
|
149
|
+
response = await this.act({ action: 'click', x, y });
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (isVerifyError(error) && error.code === 'INVALID_ARGUMENT' && error.httpStatus === 422)
|
|
153
|
+
throw new VerifyError('INVALID_ARGUMENT', `Nothing was clicked: the computer refused a click at (${x}, ${y}). That happens when the point is no longer on its screen, for example after its display size changed. Take a new screenshot() and pick the point from it. See ${this.viewerUrl}`, { cause: error, reason: error.reason, httpStatus: 422 });
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
if (response.status === 422) {
|
|
157
|
+
const size = await this.screenSize().catch(() => undefined);
|
|
158
|
+
const range = size ? `x is 0 to ${size.width - 1} and y is 0 to ${size.height - 1}, in pixels of the image screenshot() returns.` : 'Use a point inside the image screenshot() returns.';
|
|
159
|
+
throw new VerifyError('INVALID_ARGUMENT', `Nothing was clicked: (${x}, ${y}) is outside the screen. ${range}`);
|
|
160
|
+
}
|
|
161
|
+
return { x, y };
|
|
162
|
+
}
|
|
163
|
+
async tapTarget(target) {
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
response = await this.act({ action: 'click', target }, 90_000);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (isVerifyError(error) && error.code === 'INVALID_ARGUMENT' && error.httpStatus === 422)
|
|
170
|
+
throw new VerifyError('NOT_AVAILABLE_ON_PLATFORM', `Nothing was clicked: this Revyl backend can't click a described target on a computer yet (invalid_request). Read the point off screenshot() and pass it to tap({ x, y }). See ${this.viewerUrl}`, { cause: error, httpStatus: 422 });
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
let point;
|
|
174
|
+
try {
|
|
175
|
+
point = JSON.parse(new TextDecoder().decode(response.body))?.point;
|
|
176
|
+
}
|
|
177
|
+
catch { }
|
|
178
|
+
const { x, y } = point ?? {};
|
|
179
|
+
if (!Number.isInteger(x) || !Number.isInteger(y) || x < 0 || y < 0)
|
|
180
|
+
throw new VerifyError('ACTION_UNCERTAIN', `Revyl answered the described click without the point it clicked, so where it landed is unknown. Take a screenshot() to check. See ${this.viewerUrl}`);
|
|
181
|
+
return { x: x, y: y };
|
|
128
182
|
}
|
|
129
183
|
type() {
|
|
130
184
|
return this.unavailable('type');
|
|
131
185
|
}
|
|
132
|
-
|
|
133
|
-
|
|
186
|
+
/** Types into whatever has keyboard focus. Computers can't clear a field, so pass { clear: false }. */
|
|
187
|
+
async typeFocused(text, options = {}) {
|
|
188
|
+
if (options.clear !== false)
|
|
189
|
+
throw new VerifyError('INVALID_ARGUMENT', "On a computer, typeFocused() can't clear the field first, so pass { clear: false } to type at the cursor: typeFocused(text, { clear: false }). To replace text, select it first, for example with pressKey('a', { modifiers: ['meta'] }) (Command+A on a Mac computer).");
|
|
190
|
+
if (typeof text !== 'string' || text.length > 16_384)
|
|
191
|
+
throw new VerifyError('INVALID_ARGUMENT', 'typeFocused() takes text of at most 16384 characters.');
|
|
192
|
+
const control = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.exec(text);
|
|
193
|
+
if (control)
|
|
194
|
+
throw new VerifyError('INVALID_ARGUMENT', `Nothing was typed: typeFocused() on a computer takes newline and tab but no other control characters, and the text has one at index ${control.index}. Use pressKey() for keys such as 'escape'.`);
|
|
195
|
+
const chunks = [];
|
|
196
|
+
let chunk = '';
|
|
197
|
+
for (const character of text) {
|
|
198
|
+
if ([...chunk].length === 500 || Buffer.byteLength(chunk + character) > 1_500) {
|
|
199
|
+
chunks.push(chunk);
|
|
200
|
+
chunk = '';
|
|
201
|
+
}
|
|
202
|
+
chunk += character;
|
|
203
|
+
}
|
|
204
|
+
if (chunk)
|
|
205
|
+
chunks.push(chunk);
|
|
206
|
+
const total = [...text].length;
|
|
207
|
+
let typed = 0;
|
|
208
|
+
for (const part of chunks) {
|
|
209
|
+
const length = [...part].length;
|
|
210
|
+
try {
|
|
211
|
+
await this.act({ action: 'type', text: part });
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (chunks.length === 1 || !isVerifyError(error))
|
|
215
|
+
throw error;
|
|
216
|
+
const uncertain = error.code === 'ACTION_UNCERTAIN';
|
|
217
|
+
const done = typed === 0 ? `typed nothing${uncertain ? ' for certain' : ''}: the first` : `typed the first ${typed} of ${total} characters; the next`;
|
|
218
|
+
throw new VerifyError(error.code, `typeFocused() ${done} ${length}${typed === 0 ? ` of ${total}` : ''} characters ${uncertain ? 'may have been typed' : 'were not typed'}, and nothing after them was. ${error.message}`, { cause: error, reason: error.reason, httpStatus: error.httpStatus });
|
|
219
|
+
}
|
|
220
|
+
typed += length;
|
|
221
|
+
}
|
|
134
222
|
}
|
|
135
223
|
doubleTap() {
|
|
136
224
|
return this.unavailable('doubleTap');
|
|
@@ -144,8 +232,12 @@ export class ComputerSession {
|
|
|
144
232
|
drag() {
|
|
145
233
|
return this.unavailable('drag');
|
|
146
234
|
}
|
|
147
|
-
|
|
148
|
-
|
|
235
|
+
/** Presses a key with modifiers held. 'meta' is the Command key on a Mac computer, and 'alt' is Option. */
|
|
236
|
+
async pressKey(key, options = {}) {
|
|
237
|
+
const checked = checkKey('web', key, options.modifiers ?? []);
|
|
238
|
+
const name = checked.key in WEB_KEYS ? COMPUTER_KEYS[checked.key] : checked.key;
|
|
239
|
+
const modifiers = [...new Set(checked.modifiers)].map((modifier) => (modifier === 'meta' ? 'cmd' : modifier));
|
|
240
|
+
await this.act({ action: 'key', keys: [...modifiers, name].join('+') });
|
|
149
241
|
}
|
|
150
242
|
back() {
|
|
151
243
|
return this.unavailable('back');
|
|
@@ -160,10 +252,12 @@ export class ComputerSession {
|
|
|
160
252
|
return this.unavailable('launchApp');
|
|
161
253
|
}
|
|
162
254
|
locate() {
|
|
163
|
-
return
|
|
255
|
+
return Promise.reject(new VerifyError('NOT_AVAILABLE_ON_PLATFORM', "locate() isn't available on computers, because Revyl can't find a point there without clicking it. tap({ target }) clicks a description and returns the point it clicked; to choose the point yourself, read it off screenshot() and pass it to tap({ x, y })."));
|
|
164
256
|
}
|
|
165
|
-
|
|
166
|
-
|
|
257
|
+
/** The screenshot's size in pixels, the space tap() uses. */
|
|
258
|
+
async screenSize() {
|
|
259
|
+
const { width, height } = pngSize(await this.screenshot());
|
|
260
|
+
return { width, height };
|
|
167
261
|
}
|
|
168
262
|
get expiresAt() {
|
|
169
263
|
return this.expiresAtMs === undefined ? undefined : new Date(this.expiresAtMs);
|
|
@@ -406,13 +500,15 @@ export class ComputerSession {
|
|
|
406
500
|
throw error;
|
|
407
501
|
}
|
|
408
502
|
}
|
|
409
|
-
async request(method, suffix, body, signal) {
|
|
503
|
+
async request(method, suffix, body, signal, timeoutMs) {
|
|
410
504
|
const path = `/api/v1/computer-runs${suffix === '' ? '' : `/${this.id}${suffix}`}`;
|
|
411
505
|
let response;
|
|
412
506
|
try {
|
|
413
|
-
response = await this.client.send(method, path, { body, signal, timeoutMs: suffix === '/stop' || suffix.startsWith('/operations') ? 310_000 : 30_000, retry: method === 'GET' && suffix !== '/screen/screenshot', headers: suffix === '/operations' ? { Prefer: 'respond-async' } : undefined });
|
|
507
|
+
response = await this.client.send(method, path, { body, signal, timeoutMs: timeoutMs ?? (suffix === '/stop' || suffix.startsWith('/operations') ? 310_000 : 30_000), retry: method === 'GET' && suffix !== '/screen/screenshot', headers: suffix === '/operations' ? { Prefer: 'respond-async' } : undefined });
|
|
414
508
|
}
|
|
415
509
|
catch (error) {
|
|
510
|
+
if (suffix === '/screen/actions')
|
|
511
|
+
throw new VerifyError('ACTION_UNCERTAIN', `The screen action may have happened: Revyl's answer never arrived. Verify will not repeat it; take a screenshot() to check before you do. See ${this.viewerUrl}`);
|
|
416
512
|
if (method === 'POST' || suffix.startsWith('/operations/'))
|
|
417
513
|
throw new VerifyError('ACTION_UNCERTAIN', `Computer request outcome is unconfirmed. Verify will not repeat it. See ${this.viewerUrl}`);
|
|
418
514
|
if (isVerifyError(error))
|
|
@@ -428,6 +524,7 @@ export class ComputerSession {
|
|
|
428
524
|
return response;
|
|
429
525
|
const detail = responseRefusalReason(response) ?? responseDetail(response);
|
|
430
526
|
const refusal = { reason: responseRefusalReason(response), httpStatus: response.status };
|
|
527
|
+
const reason = detail && ERROR_CODES.has(detail) ? detail : `HTTP ${response.status}`;
|
|
431
528
|
if (suffix === '/screen/screenshot') {
|
|
432
529
|
let raw = null;
|
|
433
530
|
try {
|
|
@@ -443,6 +540,30 @@ export class ComputerSession {
|
|
|
443
540
|
}
|
|
444
541
|
if (response.status === 429 && detail === 'computer_limit')
|
|
445
542
|
throw new VerifyError('COMPUTER_LIMIT', `${COMPUTER_CAPACITY} See ${this.viewerUrl}`, refusal);
|
|
543
|
+
if (suffix === '/screen/actions') {
|
|
544
|
+
if (response.status === 422 && detail === 'screen_point_outside')
|
|
545
|
+
return response;
|
|
546
|
+
if (response.status === 409 && detail === 'screen_starting')
|
|
547
|
+
return response;
|
|
548
|
+
if (detail === 'screen_target_not_found')
|
|
549
|
+
throw new VerifyError('ACTION_FAILED', `Nothing was clicked: Revyl could not find the described target on the screen (screen_target_not_found). Take a screenshot() and pass a point to tap({ x, y }). See ${this.viewerUrl}`, refusal);
|
|
550
|
+
if (detail === 'screen_target_unavailable')
|
|
551
|
+
throw new VerifyError('API_ERROR', `Nothing was clicked: Revyl could not look up the described target just now (screen_target_unavailable). It is safe to try again. See ${this.viewerUrl}`, refusal);
|
|
552
|
+
if (detail === 'screen_control_held')
|
|
553
|
+
throw new VerifyError('SESSION_BUSY', `Nothing happened: someone else is controlling this computer's screen in the viewer (screen_control_held). Try again after they release it; control frees itself after 2 minutes without input. See ${this.viewerUrl}`, refusal);
|
|
554
|
+
if (detail === 'screen_input_uncertain' || detail === 'screen_unavailable' || detail === 'screen_input_unavailable')
|
|
555
|
+
throw new VerifyError('ACTION_UNCERTAIN', `The screen action may have happened (${detail}). Verify will not repeat it; take a screenshot() to check before you do. See ${this.viewerUrl}`, refusal);
|
|
556
|
+
if (response.status === 403)
|
|
557
|
+
throw new VerifyError('AUTH_REJECTED', `Revyl refused the screen action (forbidden). The API key's owner needs the Member role or above in the organization, and CI runner keys can't control the screen yet. See ${this.viewerUrl}`, refusal);
|
|
558
|
+
if (response.status === 503 && detail === 'authentication_unavailable')
|
|
559
|
+
throw new VerifyError('API_ERROR', `Nothing happened: Revyl could not confirm your access just now (authentication_unavailable). It is safe to try again. See ${this.viewerUrl}`, refusal);
|
|
560
|
+
if (detail === 'screen_text_unsupported')
|
|
561
|
+
throw new VerifyError('INVALID_ARGUMENT', `Nothing was typed: this computer can't type some of these characters (screen_text_unsupported). See ${this.viewerUrl}`, refusal);
|
|
562
|
+
if (detail === 'screen_control_lost')
|
|
563
|
+
throw new VerifyError('SESSION_BUSY', `Nothing happened: control of this computer's screen changed hands before the action was sent (screen_control_lost). Try again; if someone is controlling the screen in the viewer, wait until they release it, which happens on its own after 2 minutes without input. See ${this.viewerUrl}`, refusal);
|
|
564
|
+
if (detail === 'screen_key_unsupported')
|
|
565
|
+
throw new VerifyError('INVALID_ARGUMENT', `Nothing was pressed: this computer can't press that key (screen_key_unsupported). Use a key pressKey() lists in the docs, or typeFocused() for a printable character. See ${this.viewerUrl}`, refusal);
|
|
566
|
+
}
|
|
446
567
|
let code = 'API_ERROR';
|
|
447
568
|
if (response.status === 401 || response.status === 403)
|
|
448
569
|
code = 'AUTH_REJECTED';
|
|
@@ -462,8 +583,38 @@ export class ComputerSession {
|
|
|
462
583
|
code = 'ACTION_UNCERTAIN';
|
|
463
584
|
else if (method === 'POST' && (response.status >= 500 || response.status < 400))
|
|
464
585
|
code = 'ACTION_UNCERTAIN';
|
|
465
|
-
|
|
466
|
-
|
|
586
|
+
let next = REFUSAL_NEXT_STEPS[code];
|
|
587
|
+
if (suffix === '/screen/actions' && code === 'ACTION_UNCERTAIN')
|
|
588
|
+
next = 'The screen action may have happened. Verify will not repeat it; take a screenshot() to check before you do.';
|
|
589
|
+
else if (suffix === '/screen/actions' && code === 'INVALID_ARGUMENT')
|
|
590
|
+
next = 'Part of the screen action may have run, so take a screenshot() to check the screen before you try again.';
|
|
591
|
+
else if (code === 'ACTION_UNCERTAIN' && method === 'GET' && !suffix.startsWith('/operations/'))
|
|
592
|
+
next = 'Nothing on the computer changed; it is safe to try again.';
|
|
593
|
+
throw new VerifyError(code, `Revyl refused the computer request (${reason}). ${next} See ${this.viewerUrl}`, refusal);
|
|
594
|
+
}
|
|
595
|
+
async act(action, timeoutMs = 30_000) {
|
|
596
|
+
this.assertOpen();
|
|
597
|
+
const signal = this.observationSignal(timeoutMs);
|
|
598
|
+
const deadlineMs = Date.now() + 30_000;
|
|
599
|
+
for (;;) {
|
|
600
|
+
const response = await this.request('POST', '/screen/actions', { action, screenshot: false }, signal, timeoutMs);
|
|
601
|
+
if (response.status !== 409)
|
|
602
|
+
return response;
|
|
603
|
+
const retryAfter = Number(response.headers.get('retry-after'));
|
|
604
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 60) * 1_000 : 1_000;
|
|
605
|
+
const seconds = Math.ceil(waitMs / 1_000);
|
|
606
|
+
const starting = new VerifyError('SESSION_BUSY', `Nothing happened: the computer's screen is still starting (screen_starting). Try again in ${seconds} second${seconds === 1 ? '' : 's'}. See ${this.viewerUrl}`, { httpStatus: 409 });
|
|
607
|
+
if (Date.now() + waitMs >= deadlineMs)
|
|
608
|
+
throw starting;
|
|
609
|
+
try {
|
|
610
|
+
await delay(waitMs, undefined, { signal });
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
this.assertOpen();
|
|
614
|
+
throw starting;
|
|
615
|
+
}
|
|
616
|
+
this.assertOpen();
|
|
617
|
+
}
|
|
467
618
|
}
|
|
468
619
|
parseReport(response) {
|
|
469
620
|
const report = parseComputerJson(response);
|
|
@@ -482,7 +633,7 @@ export class ComputerSession {
|
|
|
482
633
|
return AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(Math.max(0, remainingMs))]);
|
|
483
634
|
}
|
|
484
635
|
unavailable(method) {
|
|
485
|
-
return Promise.reject(new VerifyError('NOT_AVAILABLE_ON_PLATFORM', `${method}() is not available on computers
|
|
636
|
+
return Promise.reject(new VerifyError('NOT_AVAILABLE_ON_PLATFORM', `${method}() is not available on computers. ${SCREEN_CALLS} See ${this.viewerUrl}`));
|
|
486
637
|
}
|
|
487
638
|
log(line) {
|
|
488
639
|
this.options.log?.(`${this.target}: ${line}`);
|
package/dist/session.d.ts
CHANGED
|
@@ -103,13 +103,13 @@ export interface OpenedSession extends Session {
|
|
|
103
103
|
report(): Promise<Record<string, unknown>>;
|
|
104
104
|
navigate(url: string): Promise<void>;
|
|
105
105
|
open(target: HostedTargetConfig): Promise<OpenedSession>;
|
|
106
|
-
/** Taps a point in screenshot pixels, or an element Revyl locates from a description. Returns the point tapped, in screenshot pixels.
|
|
106
|
+
/** Taps a point in screenshot pixels, or an element Revyl locates from a description. Returns the point tapped, in screenshot pixels. */
|
|
107
107
|
tap(at: Location): Promise<Point>;
|
|
108
108
|
/** Taps a text field and types into it, clearing it first unless clear is false. Returns the point tapped. Browsers and devices only. */
|
|
109
109
|
type(at: Location, text: string, options?: {
|
|
110
110
|
clear?: boolean;
|
|
111
111
|
}): Promise<Point>;
|
|
112
|
-
/** Types into the field that already has focus, without tapping, replacing its content unless clear is false. Web
|
|
112
|
+
/** Types into the field that already has focus, without tapping, replacing its content unless clear is false. Web and Mac computers, where clear must be false; on iOS and Android, use type(location, text). */
|
|
113
113
|
typeFocused(text: string, options?: {
|
|
114
114
|
clear?: boolean;
|
|
115
115
|
}): Promise<void>;
|
|
@@ -125,7 +125,7 @@ export interface OpenedSession extends Session {
|
|
|
125
125
|
}): Promise<void>;
|
|
126
126
|
/** Presses at one point or element, moves to another and releases. Browsers and devices only. */
|
|
127
127
|
drag(from: Location, to: Location): Promise<void>;
|
|
128
|
-
/** Presses a key on the focused element. iOS and Android take 'enter' and 'backspace'; Web also
|
|
128
|
+
/** Presses a key on the focused element. iOS and Android take 'enter' and 'backspace'; Web and Mac computers also take the other Key names, and modifiers, with which a lowercase letter or digit also works. On a Mac computer 'meta' is Command. */
|
|
129
129
|
pressKey(key: Key | string, options?: {
|
|
130
130
|
modifiers?: KeyModifier[];
|
|
131
131
|
}): Promise<void>;
|
|
@@ -139,7 +139,7 @@ export interface OpenedSession extends Session {
|
|
|
139
139
|
launchApp(bundleId: string): Promise<void>;
|
|
140
140
|
/** Asks Revyl's grounding model where an element is on the current screen, in screenshot pixels. Browsers and devices only. */
|
|
141
141
|
locate(target: string): Promise<Point>;
|
|
142
|
-
/** The screenshot's size in pixels, the space every coordinate uses.
|
|
142
|
+
/** The screenshot's size in pixels, the space every coordinate uses. */
|
|
143
143
|
screenSize(): Promise<{
|
|
144
144
|
width: number;
|
|
145
145
|
height: number;
|
|
@@ -277,3 +277,4 @@ export declare function pngSize(bytes: Uint8Array): {
|
|
|
277
277
|
width: number;
|
|
278
278
|
height: number;
|
|
279
279
|
} | undefined;
|
|
280
|
+
export declare function checkLocation(at: Location): Location;
|
package/dist/session.js
CHANGED
|
@@ -879,7 +879,7 @@ export function pngSize(bytes) {
|
|
|
879
879
|
const height = view.getUint32(20);
|
|
880
880
|
return width > 0 && height > 0 ? { width, height } : undefined;
|
|
881
881
|
}
|
|
882
|
-
function checkLocation(at) {
|
|
882
|
+
export function checkLocation(at) {
|
|
883
883
|
if (at && typeof at === 'object' && 'target' in at) {
|
|
884
884
|
if (typeof at.target !== 'string' || at.target.trim() === '' || at.target.length > 500)
|
|
885
885
|
throw new VerifyError('INVALID_ARGUMENT', 'A target is a description of 1 to 500 characters.');
|
package/docs/README.md
CHANGED
|
@@ -10,7 +10,7 @@ These files ship inside the npm package at `node_modules/@revyl/verify/docs/`, s
|
|
|
10
10
|
| [configuration.md](configuration.md) | Look up every option in `verify.config.ts`, the environment variables, and the CLI |
|
|
11
11
|
| [web.md](web.md) | Open a web app in a Revyl cloud browser, and act on it with exact actions |
|
|
12
12
|
| [mobile.md](mobile.md) | Open an iOS or Android app on a Revyl cloud device, and act on it with exact actions |
|
|
13
|
-
| [computers.md](computers.md) | Open a Mac computer for commands, files, processes, screenshots, and optional recording metadata |
|
|
13
|
+
| [computers.md](computers.md) | Open a Mac computer for commands, files, processes, screenshots, clicks and keys on its screen, and optional recording metadata |
|
|
14
14
|
| [ci.md](ci.md) | Call Verify from CI or from a test runner you already use |
|
|
15
15
|
| [own-model.md](own-model.md) | Run instructions and validations on your own model, such as Claude, GPT or a local model |
|
|
16
16
|
| [errors.md](errors.md) | Understand an error code and fix it, or troubleshoot a common problem |
|
package/docs/agents.md
CHANGED
|
@@ -24,9 +24,9 @@ This page is for AI coding agents (Claude Code, Cursor, Codex, and others) that
|
|
|
24
24
|
|
|
25
25
|
## Exact actions and coordinates
|
|
26
26
|
|
|
27
|
-
Use `instruction()` when the step needs judgement, and exact actions (`tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `pressKey`, `locate`, `screenSize`) when you know exactly what to press. They work on Web, iOS and Android,
|
|
27
|
+
Use `instruction()` when the step needs judgement, and exact actions (`tap`, `doubleTap`, `longPress`, `type`, `swipe`, `drag`, `pressKey`, `locate`, `screenSize`) when you know exactly what to press. They work on Web, iOS and Android. On a Mac computer's screen, `tap({ x, y })`, `tap({ target })`, `screenSize()`, `typeFocused(text, { clear: false })` and `pressKey` work; see [computers.md](computers.md#screen). On Web and Mac computers, `typeFocused(text)` types into the field that has focus, and `pressKey` also takes keys such as `'escape'`, `'tab'` and arrows, with `modifiers`. Select all with `pressKey('a', { modifiers: ['meta'] })` on a Mac computer, where `'meta'` is Command, and with `pressKey('a', { modifiers: ['ctrl'] })` on Web, where `'meta'` is not the shortcut key. On iOS and Android, `goHome()` goes to the home screen, `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, and the message names what to use instead.
|
|
28
28
|
|
|
29
|
-
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.
|
|
29
|
+
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.
|
|
30
30
|
|
|
31
31
|
`swipe('up')` moves the finger up, so content scrolls down, on every platform. Actions return once Revyl accepts them, so after a swipe, drag, `goHome`, `openUrl` or `launchApp`, wait for the screen to settle before you `locate` or tap by coordinates.
|
|
32
32
|
|
|
@@ -53,13 +53,13 @@ try {
|
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
The [Python](https://github.com/RevylAI/verify/blob/v0.
|
|
56
|
+
The [Python](https://github.com/RevylAI/verify/blob/v0.6.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/README.md#exact-actions) READMEs state the same rule with each language's names. [api.md](api.md#exact-actions) lists every call and its errors.
|
|
57
57
|
|
|
58
58
|
## Rules
|
|
59
59
|
|
|
60
60
|
Mac computers are early access. Ask support@revyl.ai about access before starting a computer; do not repeat an uncertain start/stop to probe availability.
|
|
61
61
|
|
|
62
|
-
For computer work, use structured `exec(['program', 'arg'])`, `readFile`, `writeFile`, `stat`, and run-owned handles from `processes()`/`terminate(id)`. Check command output with ordinary assertions and close in `finally`; screenshots are supported, but instruction/validation are not. Do not fall back to agent steps on computers or shell/file methods on mobile/web. See [computers.md](computers.md) for limits. Starting a computer is a paid external action and needs the same approval as starting a device.
|
|
62
|
+
For computer work, use structured `exec(['program', 'arg'])`, `readFile`, `writeFile`, `stat`, and run-owned handles from `processes()`/`terminate(id)`. Check command output with ordinary assertions and close in `finally`; screenshots are supported, but instruction/validation are not. To use the screen, read a point off `screenshot()` and `tap({ x, y })` it, or describe it with `tap({ target })`, and type with `typeFocused(text, { clear: false })`. 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 })`. After `ACTION_UNCERTAIN`, take a screenshot before you act again. Do not fall back to agent steps on computers or shell/file methods on mobile/web. See [computers.md](computers.md) for limits. Starting a computer is a paid external action and needs the same approval as starting a device.
|
|
63
63
|
|
|
64
64
|
Computers have a hard lifetime from create (two hours today), not from readiness or activity; `expiresAt` is the authoritative deadline. `expiresAt` is optional on older APIs; expiry does not prevent close. After terminal state and confirmed release, `recording()` can return private signed metadata or null, including after close. Never log or share its signed URL/query. Do not automatically wait for recordings or retry `COMPUTER_LIMIT` starts: queued work (including iOS) or the concurrency limit may be responsible.
|
|
65
65
|
|
package/docs/api.md
CHANGED
|
@@ -84,19 +84,21 @@ Web only, hosted or normal. Sends one deterministic `open_url` action and checks
|
|
|
84
84
|
|
|
85
85
|
## Exact actions
|
|
86
86
|
|
|
87
|
-
**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.
|
|
87
|
+
**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. On a Mac computer's screen, `tap` (by point or by description), `screenSize`, `typeFocused` and `pressKey` are new in TypeScript 0.6.0, Python 0.4.0 and Go v0.5.0. The [Python](https://github.com/RevylAI/verify/blob/v0.6.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/README.md#exact-actions) READMEs show them with each language's names. Web, iOS and Android sessions, and a Mac computer's screen, take device actions that need no agent.
|
|
88
88
|
|
|
89
89
|
| Call | Web | iOS | Android | Mac computer |
|
|
90
90
|
| --- | --- | --- | --- | --- |
|
|
91
|
-
| `tap
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
91
|
+
| `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
|
|
92
|
+
| `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
|
|
93
|
+
| `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
|
|
94
|
+
| `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 |
|
|
95
|
+
| `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
|
|
94
96
|
| `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
|
|
95
97
|
| `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
|
|
96
98
|
| `openUrl(url)` | Yes, loads an http(s) URL in the tab | Yes, a URL or deep link | Yes, a URL or deep link | No |
|
|
97
99
|
| `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
|
|
98
100
|
| `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
|
|
99
|
-
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
|
|
101
|
+
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
|
|
100
102
|
| `screenshot()` | Yes | Yes | Yes | Yes |
|
|
101
103
|
| `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
|
|
102
104
|
|
|
@@ -106,14 +108,14 @@ A location is `{ x, y }` in screenshot pixels (see [Coordinates](#coordinates))
|
|
|
106
108
|
|
|
107
109
|
| Call | What it does |
|
|
108
110
|
| --- | --- |
|
|
109
|
-
| `tap(at)` | Taps and resolves with the point tapped |
|
|
111
|
+
| `tap(at)` | Taps (clicks on Web and Mac computers) and resolves with the point tapped. On a Mac computer a described tap takes several seconds, can click a look-alike, and can click something else when the target isn't on the screen, so check the point or a screenshot afterwards; see [computers.md](computers.md#screen) |
|
|
110
112
|
| `doubleTap(at)` | Double-taps (double-clicks on Web) and resolves with the point |
|
|
111
113
|
| `longPress(at, { duration })` | Presses and holds for `duration` milliseconds, 1 to 5000 (default 1500), and resolves with the point |
|
|
112
114
|
| `type(at, text, { clear })` | Taps a text field and types into it, clearing it first unless `clear` is `false` |
|
|
113
|
-
| `typeFocused(text, { clear })` | Web
|
|
115
|
+
| `typeFocused(text, { clear })` | Web and Mac computers: types into the field that already has focus, without clicking. Focus the field first, for example with `tap` or `pressKey('tab')`. With no field focused nothing is typed and no error is thrown, so check the result. On Web it replaces the field's text unless `clear` is `false`. On a Mac computer pass `{ clear: false }`: it types where the cursor is, and leaving `clear` out throws `INVALID_ARGUMENT`. To replace text there, select it first with `pressKey('a', { modifiers: ['meta'] })`. On iOS and Android it throws `NOT_AVAILABLE_ON_PLATFORM`; use `type(location, text)` |
|
|
114
116
|
| `swipe(direction, { from })` | Swipes from a location or the screen's center; `'up'` moves the finger up, which scrolls content down |
|
|
115
117
|
| `drag(from, to)` | Presses at one location, moves to the other and releases, for example to reorder a list, move a slider or select text |
|
|
116
|
-
| `pressKey(key, { modifiers })` | Presses a key on the focused element. Every platform takes `'enter'` and `'backspace'`. Web also
|
|
118
|
+
| `pressKey(key, { modifiers })` | Presses a key on the focused element. Every platform takes `'enter'` and `'backspace'`. Web and Mac computers also take `'tab'`, `'escape'`, `'delete'`, `'space'`, `'arrowup'`, `'arrowdown'`, `'arrowleft'`, `'arrowright'`, `'home'`, `'end'`, `'pageup'` and `'pagedown'`, and `modifiers`: up to four of `'ctrl'`, `'shift'`, `'alt'` and `'meta'`, held while the key is pressed. With modifiers a lowercase letter or digit works too. The shortcut key differs: select all with `pressKey('a', { modifiers: ['meta'] })` on a Mac computer, where `'meta'` is Command and `'alt'` is Option, and with `pressKey('a', { modifiers: ['ctrl'] })` on Web, where `'meta'` is not the shortcut key. Other keys and any modifiers throw `NOT_AVAILABLE_ON_PLATFORM` on iOS and Android |
|
|
117
119
|
| `back()` | Browser history on Web, the back button on Android; iOS throws `NOT_AVAILABLE_ON_PLATFORM` |
|
|
118
120
|
| `goHome()` | Goes to the home screen on iOS and Android; Web throws `NOT_AVAILABLE_ON_PLATFORM` |
|
|
119
121
|
| `openUrl(url)` | Opens a URL or deep link on a device, or loads a URL in a browser |
|
|
@@ -121,13 +123,13 @@ A location is `{ x, y }` in screenshot pixels (see [Coordinates](#coordinates))
|
|
|
121
123
|
| `locate(target)` | Resolves with Revyl's best match on the current screen, without acting |
|
|
122
124
|
| `screenSize()` | Resolves with the screenshot's `{ width, height }` in pixels |
|
|
123
125
|
|
|
124
|
-
A failed action throws `ACTION_FAILED`, including when Revyl cannot find a target or nothing at a point can take the action. 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.
|
|
126
|
+
A failed action throws `ACTION_FAILED`, including when Revyl cannot find a target or nothing at a point can take the action. 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. A Mac computer's screen has a few errors of its own, listed in [computers.md](computers.md#screen).
|
|
125
127
|
|
|
126
128
|
Exact actions return when Revyl has accepted them, not when the screen has finished changing. After `swipe`, `drag`, `goHome`, `openUrl` or `launchApp`, let the screen settle or check it before you `locate` or tap coordinates. `locate` returns Revyl's best match on the current screen and can return a point for an element that is off screen, so bring the element on screen first. A point outside `screenSize()` is off screen, and an action at it is refused.
|
|
127
129
|
|
|
128
130
|
### Coordinates
|
|
129
131
|
|
|
130
|
-
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.
|
|
132
|
+
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.
|
|
131
133
|
|
|
132
134
|
```ts
|
|
133
135
|
await session.screenshot({ path: '.verify/screen.png' }); // open it and find the element
|
|
@@ -180,7 +182,7 @@ When inspecting `revyl device report --session-id <id>`, read validation verdict
|
|
|
180
182
|
|
|
181
183
|
## Your own model
|
|
182
184
|
|
|
183
|
-
**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.
|
|
185
|
+
**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.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/go/README.md#your-own-model) READMEs show them with each language's names. Runs instructions and validations on a model you choose, in your process, instead of Revyl's agent. Revyl still provides the browser or device. Leave `model` out and Revyl's agent runs the step, as before.
|
|
184
186
|
|
|
185
187
|
```ts
|
|
186
188
|
const session = await open('ios', { model: 'anthropic/claude-sonnet-5-5' }); // every step on your model
|
|
@@ -231,7 +233,7 @@ For owning computers, cleanup must be confirmed; a terminal state alone does not
|
|
|
231
233
|
|
|
232
234
|
## Computer commands, files, and processes
|
|
233
235
|
|
|
234
|
-
Mac sessions provide `exec(argv: string[], options?)`, `readFile(path, options?)` returning `Buffer`, `writeFile(path, string | Uint8Array, options?)`, `stat(path, options?)` returning `{ kind, size }`, `processes(options?)` returning `{ id, state, exitCode? }[]`, and `terminate(id, options?)`. They throw `NOT_AVAILABLE_ON_PLATFORM` on web/iOS/Android. See [computers.md](computers.md) for complete signatures, limits, timeouts, and examples. Both support `screenshot()
|
|
236
|
+
Mac sessions provide `exec(argv: string[], options?)`, `readFile(path, options?)` returning `Buffer`, `writeFile(path, string | Uint8Array, options?)`, `stat(path, options?)` returning `{ kind, size }`, `processes(options?)` returning `{ id, state, exitCode? }[]`, and `terminate(id, options?)`. They throw `NOT_AVAILABLE_ON_PLATFORM` on web/iOS/Android. See [computers.md](computers.md) for complete signatures, limits, timeouts, and examples. Both support `screenshot()`, and computers take `tap({ x, y })`, `screenSize()`, `typeFocused(text, { clear: false })` and `pressKey` on the screen (see [computers.md](computers.md#screen)); instruction/validation do not run on computers.
|
|
235
237
|
|
|
236
238
|
### `session.recording(): Promise<ComputerRecording | null>`
|
|
237
239
|
|
|
@@ -265,7 +267,7 @@ While a session is open, Verify attempts best-effort cleanup with a 15-second bu
|
|
|
265
267
|
- on an uncaught error, such as a failed validation that nothing catches: it prints the error, attempts cleanup, then exits with 1;
|
|
266
268
|
- when the script finishes without calling `close()`: it attempts cleanup and prints a reminder.
|
|
267
269
|
|
|
268
|
-
Verify skips installing a signal or error handler when that event already has listeners, so test runners and apps that manage their own shutdown keep control of it. Always close in `try/finally` or an `after` hook; automatic cleanup cannot guarantee success. A process that is killed outright, or calls `process.exit()` with sessions open, leaves browser/mobile sessions running until their idle timeout (10 minutes by default). Computers have a
|
|
270
|
+
Verify skips installing a signal or error handler when that event already has listeners, so test runners and apps that manage their own shutdown keep control of it. Always close in `try/finally` or an `after` hook; automatic cleanup cannot guarantee success. A process that is killed outright, or calls `process.exit()` with sessions open, leaves browser/mobile sessions running until their idle timeout (10 minutes by default). Computers have a hard lifetime from create instead (two hours today; `expiresAt` is authoritative); no idle timeout is configurable.
|
|
269
271
|
|
|
270
272
|
## defineConfig(config)
|
|
271
273
|
|
package/docs/ci.md
CHANGED
|
@@ -71,4 +71,4 @@ A thrown `VerifyError` fails the test with its code, Revyl's reason, and the ses
|
|
|
71
71
|
|
|
72
72
|
## Cost and parallelism
|
|
73
73
|
|
|
74
|
-
Each `open()` holds one Revyl session until `close()`. Sessions run in parallel when you open several at once, so close each one as soon as you are done with it. A browser/mobile session that loses its process stops itself after `idleTimeoutSeconds` (10 minutes by default). Computers instead have a
|
|
74
|
+
Each `open()` holds one Revyl session until `close()`. Sessions run in parallel when you open several at once, so close each one as soon as you are done with it. A browser/mobile session that loses its process stops itself after `idleTimeoutSeconds` (10 minutes by default). Computers instead have a hard lifetime from create (two hours today; `expiresAt` is authoritative) and no configurable idle timeout; close in `finally` and do not treat a timed-out CI job as proof of physical release.
|
package/docs/computers.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mac computers (early access)
|
|
2
2
|
|
|
3
|
-
Verify opens a Revyl cloud macOS computer for commands, files, processes, and
|
|
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 **early access**. Ask [support@revyl.ai](mailto:support@revyl.ai) about access.
|
|
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.
|
|
@@ -59,7 +128,7 @@ Mac computers are early access. A `503 runtime_unavailable` or typed `computer_n
|
|
|
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.
|
|
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
|
|
package/docs/errors.md
CHANGED
|
@@ -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 }` | The message names the argument |
|
|
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
|
|
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).
|
package/docs/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
|
@@ -11,7 +11,7 @@ Mac computers are early access; ask support@revyl.ai about access. See computers
|
|
|
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 early access, 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
|
@@ -50,7 +50,7 @@ For Android Settings, instruct the agent to scroll or navigate until the OS vers
|
|
|
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.
|
|
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.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.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:
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
import { open } from '@revyl/verify';
|
|
@@ -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
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
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.
|
|
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.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.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.
|
|
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.
|
|
55
|
+
The [Python](https://github.com/RevylAI/verify/blob/v0.6.0/python/README.md#your-own-model) and [Go](https://github.com/RevylAI/verify/blob/v0.6.0/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
|
|
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.
|
|
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.0/python/README.md#exact-actions) and [Go](https://github.com/RevylAI/verify/blob/v0.6.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()`:
|
|
57
57
|
|
|
58
58
|
```ts
|
|
59
59
|
import { open } from '@revyl/verify';
|
|
@@ -103,23 +103,25 @@ try {
|
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
105
|
|
|
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.
|
|
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. 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
107
|
|
|
108
108
|
| Call | Web | iOS | Android | Mac computer |
|
|
109
109
|
| --- | --- | --- | --- | --- |
|
|
110
|
-
| `tap
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
110
|
+
| `tap({ x, y })`, `tap({ target })`, `screenSize` | Yes | Yes | Yes | Yes |
|
|
111
|
+
| `locate` | Yes | Yes | Yes | No, use `tap({ target })`, which returns the point it clicked, or read the point off `screenshot()` |
|
|
112
|
+
| `doubleTap`, `longPress`, `type`, `swipe`, `drag` | Yes | Yes | Yes | No |
|
|
113
|
+
| `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 |
|
|
114
|
+
| `typeFocused(text, { clear })` | Yes, types into the focused field | No, use `type(location, text)` | No, use `type(location, text)` | Yes, with `{ clear: false }` |
|
|
113
115
|
| `back()` | Yes, browser history | No, tap the on-screen back control | Yes, the system back button | No |
|
|
114
116
|
| `goHome()` | No, use `openUrl` or `navigate` | Yes, the home screen | Yes, the home screen | No |
|
|
115
117
|
| `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
118
|
| `launchApp(bundleId)` | No, use `openUrl` or `navigate` | Yes | Yes | No |
|
|
117
119
|
| `navigate(url)` | Yes | No, use `openUrl` | No, use `openUrl` | No |
|
|
118
|
-
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` |
|
|
120
|
+
| `instruction`, `validation`, your own model | Yes | Yes | Yes | No, use `exec()` and the screen calls above |
|
|
119
121
|
| `screenshot()` | Yes | Yes | Yes | Yes |
|
|
120
122
|
| `exec`, files, processes | No, open a Mac computer | No, open a Mac computer | No, open a Mac computer | Yes |
|
|
121
123
|
|
|
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.
|
|
124
|
+
**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
125
|
|
|
124
126
|
A call a platform doesn't support throws `NOT_AVAILABLE_ON_PLATFORM`, and its message names the alternative.
|
|
125
127
|
|