@leera.io/qa-runner 1.0.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/LICENSE +64 -0
- package/README.md +333 -0
- package/dist/src/appium/drivers.js +60 -0
- package/dist/src/appium/home.js +60 -0
- package/dist/src/appium/server.js +237 -0
- package/dist/src/builds/cache.js +133 -0
- package/dist/src/builds/cleanup.js +208 -0
- package/dist/src/builds/download.js +64 -0
- package/dist/src/builds/install.js +75 -0
- package/dist/src/builds/ios-app.js +99 -0
- package/dist/src/builds/unpack.js +347 -0
- package/dist/src/capabilities.js +93 -0
- package/dist/src/ci/builds.js +68 -0
- package/dist/src/ci/client.js +90 -0
- package/dist/src/ci/env.js +118 -0
- package/dist/src/ci/junit.js +79 -0
- package/dist/src/ci/run.js +320 -0
- package/dist/src/ci/summary.js +126 -0
- package/dist/src/cli/commands/builds.js +104 -0
- package/dist/src/cli/commands/ci.js +167 -0
- package/dist/src/cli/commands/config.js +83 -0
- package/dist/src/cli/commands/connect.js +70 -0
- package/dist/src/cli/commands/doctor.js +57 -0
- package/dist/src/cli/commands/service.js +165 -0
- package/dist/src/cli/commands/setup.js +193 -0
- package/dist/src/cli/commands/start.js +94 -0
- package/dist/src/cli/commands/update.js +189 -0
- package/dist/src/cli/commands/version.js +28 -0
- package/dist/src/cli/main.js +59 -0
- package/dist/src/cli/output.js +67 -0
- package/dist/src/cli.js +40 -0
- package/dist/src/client.js +146 -0
- package/dist/src/config.js +260 -0
- package/dist/src/debug.js +137 -0
- package/dist/src/devices/android/adb.js +251 -0
- package/dist/src/devices/android/avd.js +95 -0
- package/dist/src/devices/android/emulator.js +153 -0
- package/dist/src/devices/android/index.js +133 -0
- package/dist/src/devices/android/logcat.js +205 -0
- package/dist/src/devices/android/prepare.js +40 -0
- package/dist/src/devices/android/setup.js +162 -0
- package/dist/src/devices/android/system-dialog.js +78 -0
- package/dist/src/devices/desktop.js +145 -0
- package/dist/src/devices/ios/devicectl.js +87 -0
- package/dist/src/devices/ios/index.js +130 -0
- package/dist/src/devices/ios/prepare.js +39 -0
- package/dist/src/devices/ios/record.js +122 -0
- package/dist/src/devices/ios/settings.js +55 -0
- package/dist/src/devices/ios/setup.js +186 -0
- package/dist/src/devices/ios/simctl.js +201 -0
- package/dist/src/devices/ios/syslog.js +185 -0
- package/dist/src/devices/ios/wda.js +108 -0
- package/dist/src/devices/macos.js +248 -0
- package/dist/src/devices/manager.js +206 -0
- package/dist/src/devices/screen-record.js +95 -0
- package/dist/src/devices/tauri.js +189 -0
- package/dist/src/devices/types.js +6 -0
- package/dist/src/devices/windows.js +362 -0
- package/dist/src/doctor.js +394 -0
- package/dist/src/download.js +39 -0
- package/dist/src/drivers/android-appium.js +525 -0
- package/dist/src/drivers/common.js +64 -0
- package/dist/src/drivers/desktop-appium.js +282 -0
- package/dist/src/drivers/driver.js +15 -0
- package/dist/src/drivers/electron-playwright.js +566 -0
- package/dist/src/drivers/ios-appium.js +535 -0
- package/dist/src/drivers/mac2-appium.js +313 -0
- package/dist/src/drivers/registry.js +47 -0
- package/dist/src/drivers/snapshot/aria.js +351 -0
- package/dist/src/drivers/snapshot/format.js +106 -0
- package/dist/src/drivers/tauri-webdriver.js +678 -0
- package/dist/src/drivers/web-playwright.js +403 -0
- package/dist/src/drivers/webdriver/actions.js +328 -0
- package/dist/src/drivers/webdriver/desktop-keys.js +141 -0
- package/dist/src/drivers/webdriver/dom-locate.js +113 -0
- package/dist/src/drivers/webdriver/dom-snapshot.js +376 -0
- package/dist/src/drivers/webdriver/dom-tree.js +118 -0
- package/dist/src/drivers/webdriver/dom.js +213 -0
- package/dist/src/drivers/webdriver/ios-actions.js +299 -0
- package/dist/src/drivers/webdriver/ios-locate.js +121 -0
- package/dist/src/drivers/webdriver/locate.js +136 -0
- package/dist/src/drivers/webdriver/native-actions.js +175 -0
- package/dist/src/drivers/webdriver/native-locate.js +273 -0
- package/dist/src/drivers/webdriver/session.js +105 -0
- package/dist/src/drivers/webdriver/xml-tree-desktop.js +425 -0
- package/dist/src/drivers/webdriver/xml-tree-ios.js +314 -0
- package/dist/src/drivers/webdriver/xml-tree.js +393 -0
- package/dist/src/drivers/windows-appium.js +319 -0
- package/dist/src/errors.js +21 -0
- package/dist/src/executor.js +189 -0
- package/dist/src/home.js +76 -0
- package/dist/src/index.js +22 -0
- package/dist/src/log.js +69 -0
- package/dist/src/runner.js +498 -0
- package/dist/src/service/index.js +64 -0
- package/dist/src/service/launchd.js +85 -0
- package/dist/src/service/names.js +2 -0
- package/dist/src/service/schtasks.js +128 -0
- package/dist/src/service/systemd.js +65 -0
- package/dist/src/session/commands.js +165 -0
- package/dist/src/session/execute.js +232 -0
- package/dist/src/session/loop.js +148 -0
- package/dist/src/template.js +62 -0
- package/dist/src/types.js +7 -0
- package/dist/src/update/apply.js +223 -0
- package/dist/src/update/check.js +59 -0
- package/dist/src/update/manifest.js +93 -0
- package/dist/src/update/verify.js +65 -0
- package/dist/src/version.js +42 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Leera QA Runner License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leera AI Solutions LLP. All rights reserved.
|
|
4
|
+
|
|
5
|
+
The Leera QA Runner (the "Software") is proprietary software owned by Leera AI
|
|
6
|
+
Solutions LLP ("Leera"). It is licensed, not sold. By installing or running the
|
|
7
|
+
Software you agree to this license. If you do not agree, do not install or run
|
|
8
|
+
it.
|
|
9
|
+
|
|
10
|
+
1. Grant. Leera grants you a free, non-exclusive, non-transferable,
|
|
11
|
+
non-sublicensable, revocable right to install and run unmodified copies of
|
|
12
|
+
the Software, in the form Leera distributes it, solely to execute tests
|
|
13
|
+
against and report results to a Leera workspace that you are authorised to
|
|
14
|
+
use, whether hosted by Leera or on a self-hosted Leera instance you are
|
|
15
|
+
licensed to operate. This includes evaluation and trial use on the same
|
|
16
|
+
terms. You may run as many copies as that purpose requires, including on
|
|
17
|
+
your own machines and in your continuous-integration systems.
|
|
18
|
+
|
|
19
|
+
2. Restrictions. Except as this license expressly permits, you may not:
|
|
20
|
+
(a) copy, distribute, sell, rent, lease, sublicense, publish or otherwise
|
|
21
|
+
make the Software available to any third party, including as part of
|
|
22
|
+
another product or a hosted service (installing it from Leera's
|
|
23
|
+
official distribution channels on systems you control is permitted);
|
|
24
|
+
(b) modify, adapt, translate or create derivative works of the Software;
|
|
25
|
+
(c) reverse engineer, decompile or disassemble the Software, except to the
|
|
26
|
+
extent applicable law expressly permits this despite this restriction;
|
|
27
|
+
(d) use the Software with any service other than a Leera workspace, or to
|
|
28
|
+
build a competing product;
|
|
29
|
+
(e) remove or alter any copyright, trademark or other proprietary notices.
|
|
30
|
+
Third-party open-source components included with the Software are licensed
|
|
31
|
+
under their own terms, which govern those components.
|
|
32
|
+
|
|
33
|
+
3. Ownership. Leera and its licensors retain all right, title and interest in
|
|
34
|
+
the Software, including all intellectual property rights. No rights are
|
|
35
|
+
granted other than those expressly stated in this license.
|
|
36
|
+
|
|
37
|
+
4. No warranty. THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE", WITHOUT
|
|
38
|
+
WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
|
|
39
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
|
|
40
|
+
NON-INFRINGEMENT. YOU ARE RESPONSIBLE FOR THE SYSTEMS, APPLICATIONS AND DATA
|
|
41
|
+
YOU RUN THE SOFTWARE AGAINST.
|
|
42
|
+
|
|
43
|
+
5. Limitation of liability. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW,
|
|
44
|
+
IN NO EVENT WILL LEERA OR ITS LICENSORS BE LIABLE FOR ANY INDIRECT,
|
|
45
|
+
INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY OR PUNITIVE DAMAGES, OR FOR ANY
|
|
46
|
+
LOSS OF DATA, PROFITS, REVENUE OR BUSINESS, ARISING OUT OF OR IN CONNECTION
|
|
47
|
+
WITH THE SOFTWARE OR THIS LICENSE, HOWEVER CAUSED AND UNDER ANY THEORY OF
|
|
48
|
+
LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LEERA'S TOTAL
|
|
49
|
+
LIABILITY UNDER THIS LICENSE IS LIMITED AS SET OUT IN THE AGREEMENT UNDER
|
|
50
|
+
WHICH YOU USE LEERA; IF NO SUCH AGREEMENT APPLIES, IT WILL NOT EXCEED THE
|
|
51
|
+
AMOUNTS YOU PAID LEERA FOR THE RELEVANT WORKSPACE IN THE TWELVE MONTHS BEFORE
|
|
52
|
+
THE EVENT GIVING RISE TO THE CLAIM.
|
|
53
|
+
|
|
54
|
+
6. Termination. This license ends automatically if you breach it, and Leera
|
|
55
|
+
may end it on notice at any time. It also ends for a workspace when your
|
|
56
|
+
right to use that workspace ends. On termination you must stop using the
|
|
57
|
+
Software and delete all copies in your possession or control. Sections 2 to
|
|
58
|
+
7 survive termination.
|
|
59
|
+
|
|
60
|
+
7. General. If you have a separate written agreement with Leera covering the
|
|
61
|
+
Software, that agreement prevails where it conflicts with this license. If
|
|
62
|
+
any provision is held unenforceable, the rest remains in effect.
|
|
63
|
+
|
|
64
|
+
Contact: contact@leera.io
|
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# leera-qa-runner
|
|
2
|
+
|
|
3
|
+
The Leera QA runner runs your automated test cases on machines you own — a laptop, a
|
|
4
|
+
Mac mini under a desk, a Linux server or your CI — and reports step results, screenshots,
|
|
5
|
+
video and debug logs back to your Leera workspace.
|
|
6
|
+
|
|
7
|
+
It registers with your workspace using a **runner token** (`pm_run_…`), polls for queued
|
|
8
|
+
jobs, runs each recorded script and uploads the evidence. Nothing listens for incoming
|
|
9
|
+
connections: the runner only makes outgoing HTTPS requests to your server.
|
|
10
|
+
|
|
11
|
+
It runs **web** tests in Chromium, **Android** tests on emulators and USB phones, **iOS** tests
|
|
12
|
+
on simulators and iPhones (Mac only), and desktop apps built with **Electron**, **Tauri**, or
|
|
13
|
+
native **Windows** and **macOS** toolkits. Mobile and native desktop drivers use Appium, bundled
|
|
14
|
+
with the installers. See [test runners](https://github.com/leera-app/leera-qa-runner/blob/main/docs/test-runners.md)
|
|
15
|
+
for each platform's prerequisites and current limitations.
|
|
16
|
+
|
|
17
|
+
Full guides: [test runners](https://github.com/leera-app/leera-qa-runner/blob/main/docs/test-runners.md) and
|
|
18
|
+
[authoring automated test cases](https://github.com/leera-app/leera-qa-runner/blob/main/docs/qa-automation-authoring.md).
|
|
19
|
+
|
|
20
|
+
- [Install](#install)
|
|
21
|
+
- [Connect and run](#connect-and-run)
|
|
22
|
+
- [Run in the background](#run-in-the-background)
|
|
23
|
+
- [Check the machine: doctor](#check-the-machine-doctor)
|
|
24
|
+
- [Android](#android)
|
|
25
|
+
- [Configuration](#configuration)
|
|
26
|
+
- [Commands and exit codes](#commands-and-exit-codes)
|
|
27
|
+
- [Docker](#docker)
|
|
28
|
+
- [Uninstall](#uninstall)
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Every installer bundles its own Node.js; you do not need Node installed. Downloads are
|
|
33
|
+
listed on the [releases page](https://github.com/leera-app/leera-qa-runner/releases) with a
|
|
34
|
+
`SHA256SUMS` file, and the install scripts verify it.
|
|
35
|
+
|
|
36
|
+
### macOS (Apple silicon and Intel)
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
curl -fsSL https://raw.githubusercontent.com/leera-app/leera-qa-runner/main/install/install.sh | sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
or download `leera-qa-runner-macos-arm64.pkg` (Apple silicon) / `leera-qa-runner-macos-x64.pkg`
|
|
43
|
+
(Intel) and open it. The package installs to `/usr/local/lib/leera-qa-runner/<version>` and
|
|
44
|
+
links `/usr/local/bin/leera-qa-runner`.
|
|
45
|
+
|
|
46
|
+
### Linux (x64 and arm64)
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/leera-app/leera-qa-runner/main/install/install.sh | sh
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
No root needed: it installs under `~/.local/lib/leera-qa-runner/<version>` and links
|
|
53
|
+
`~/.local/bin/leera-qa-runner` (change with `--prefix DIR`). Make sure `~/.local/bin` is on
|
|
54
|
+
your `PATH`. Chromium needs some system libraries; if a job cannot launch the browser, run the
|
|
55
|
+
`sudo … install-deps chromium` command that `leera-qa-runner setup browsers` prints.
|
|
56
|
+
|
|
57
|
+
Manual install: download `leera-qa-runner-linux-<arch>.tar.gz`, extract it anywhere and run
|
|
58
|
+
`leera-qa-runner-linux-<arch>/bin/leera-qa-runner`.
|
|
59
|
+
|
|
60
|
+
### Windows (x64)
|
|
61
|
+
|
|
62
|
+
In PowerShell:
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
irm https://raw.githubusercontent.com/leera-app/leera-qa-runner/main/install/install.ps1 | iex
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
or download and run `leera-qa-runner-windows-x64.exe`. It installs for the current user
|
|
69
|
+
(no administrator rights) into `%LOCALAPPDATA%\Programs\leera-qa-runner` and adds it to your
|
|
70
|
+
`PATH`; open a new terminal afterwards. The installer is not code-signed yet, so Windows
|
|
71
|
+
SmartScreen may warn before it runs ("More info" → "Run anyway").
|
|
72
|
+
|
|
73
|
+
### npm
|
|
74
|
+
|
|
75
|
+
With Node.js 22.12 or newer:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
npm install -g @leera.io/qa-runner
|
|
79
|
+
leera-qa-runner setup browsers
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Appium and the UiAutomator2 driver are optional dependencies: installing with
|
|
83
|
+
`--omit=optional` gives a web-only runner.
|
|
84
|
+
|
|
85
|
+
### Install options
|
|
86
|
+
|
|
87
|
+
`install.sh` accepts `--version X`, `--prefix DIR` (Linux), `--url URL`, `--token TOKEN`,
|
|
88
|
+
`--name NAME`, `--service`, `--no-browsers` and `--base-url URL` (a mirror of the release
|
|
89
|
+
assets). `install.ps1` accepts `-Version`, `-Url`, `-Token`, `-Name`, `-Service`,
|
|
90
|
+
`-NoBrowsers`, `-Portable` (unpack the `.zip` instead of running the installer) and `-BaseUrl`. Prefer passing the token through the `RUNNER_TOKEN`
|
|
91
|
+
environment variable so it does not land in your shell history:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
curl -fsSL https://raw.githubusercontent.com/leera-app/leera-qa-runner/main/install/install.sh \
|
|
95
|
+
| RUNNER_TOKEN=pm_run_… sh -s -- --url https://app.example.com --service
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Connect and run
|
|
99
|
+
|
|
100
|
+
1. In your workspace, open **Settings → Test runners**, create a runner pool and copy a
|
|
101
|
+
runner token.
|
|
102
|
+
2. Install Chromium (the installers do this for you):
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
leera-qa-runner setup browsers
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
3. Connect. The token is asked for without echo:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
leera-qa-runner connect --url https://app.example.com --name lab-mac
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
In scripts, pipe it instead: `printf '%s' "$TOKEN" | leera-qa-runner connect --url … --token-stdin`.
|
|
115
|
+
`connect` stores the URL in `~/.leera-qa-runner/config.json`, the token in
|
|
116
|
+
`~/.leera-qa-runner/token` (readable only by you) and runs `doctor`.
|
|
117
|
+
|
|
118
|
+
4. Run it in this terminal:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
leera-qa-runner start
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
or [install the background service](#run-in-the-background). The runner appears on the
|
|
125
|
+
Test runners page with its OS, architecture and version; queue an automated run and
|
|
126
|
+
watch it pick up the job. Press Ctrl-C to stop after the current step.
|
|
127
|
+
|
|
128
|
+
`start` options: `--once` (exit after one job), `--headed` (show the browser),
|
|
129
|
+
`--labels a,b` (exact label list), `--concurrency N` (browser jobs at once) and
|
|
130
|
+
`--run-id N` (only take jobs from one test run).
|
|
131
|
+
|
|
132
|
+
## Run in the background
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
leera-qa-runner service install # install and start; starts again at login
|
|
136
|
+
leera-qa-runner service status
|
|
137
|
+
leera-qa-runner service logs -f
|
|
138
|
+
leera-qa-runner service restart
|
|
139
|
+
leera-qa-runner service stop
|
|
140
|
+
leera-qa-runner service start
|
|
141
|
+
leera-qa-runner service uninstall
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The service runs as **your user, in your login session**, so it can open browsers and
|
|
145
|
+
Android emulators (and, later, simulators and app windows):
|
|
146
|
+
|
|
147
|
+
| OS | What is installed |
|
|
148
|
+
|---|---|
|
|
149
|
+
| macOS | LaunchAgent `~/Library/LaunchAgents/io.leera.qa-runner.plist` (starts at login, restarted if it exits). Keep the Mac logged in; a locked screen is fine for browser jobs. |
|
|
150
|
+
| Windows | Scheduled task `leera-qa-runner`, at logon, in your interactive session, with limited rights; restarted on failure. |
|
|
151
|
+
| Linux | systemd user unit `~/.config/systemd/user/leera-qa-runner.service`. `loginctl enable-linger` keeps it running after you log out (may need an administrator). |
|
|
152
|
+
|
|
153
|
+
Services start without your shell profile, so `service install` saves `PATH` and a few other
|
|
154
|
+
variables (`JAVA_HOME`, `ANDROID_HOME`, `ANDROID_SDK_ROOT`, `PLAYWRIGHT_BROWSERS_PATH`,
|
|
155
|
+
proxy variables) into the configuration's `env`. Re-run `service install` after changing them.
|
|
156
|
+
|
|
157
|
+
Logs are JSON lines in `~/.leera-qa-runner/logs/runner.log`, rotated at 20 MB with five old
|
|
158
|
+
files kept.
|
|
159
|
+
|
|
160
|
+
## Check the machine: doctor
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
leera-qa-runner doctor # human-readable
|
|
164
|
+
leera-qa-runner doctor --json # for scripts
|
|
165
|
+
leera-qa-runner doctor --fix # fix permissions, install Chromium if missing
|
|
166
|
+
leera-qa-runner doctor --platform web
|
|
167
|
+
leera-qa-runner doctor --platform android
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
It checks Node.js, the runner home and its permissions, the server URL, the token (asking the
|
|
171
|
+
server whether it accepts it, without registering a runner), Chromium, and for Android: Appium,
|
|
172
|
+
the SDK, adb, the emulator, build-tools, Java, KVM on Linux and unauthorised phones. It exits `0` when
|
|
173
|
+
ready and `4` when something marked `[fail]` needs fixing, with the command that fixes it.
|
|
174
|
+
|
|
175
|
+
## Android
|
|
176
|
+
|
|
177
|
+
1. Install the Android SDK (Android Studio, or the command-line tools) and Java 17+, and set
|
|
178
|
+
`ANDROID_HOME` (or `"android": {"sdk_path": "…"}` in `config.json`). On Linux, emulators
|
|
179
|
+
need `/dev/kvm` and your user in the `kvm` group. For a phone, enable USB debugging and accept
|
|
180
|
+
the prompt on the phone.
|
|
181
|
+
2. Prepare the runner:
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
leera-qa-runner setup android # Appium home, SDK check, device list
|
|
185
|
+
leera-qa-runner setup android --install-sdk # also install SDK packages and create an emulator
|
|
186
|
+
leera-qa-runner doctor --platform android
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`--install-sdk` uses the SDK's own `sdkmanager`, so the SDK directory must already have
|
|
190
|
+
`platform-tools` and `cmdline-tools/latest`; otherwise it prints how to get them. Re-run
|
|
191
|
+
`service install` afterwards so the service sees `ANDROID_HOME` and `JAVA_HOME`.
|
|
192
|
+
|
|
193
|
+
3. Upload an `.apk` on the **App builds** page (app bundles, `.aab`, are refused), set the
|
|
194
|
+
package id and build on the test environment's **Apps**, create a run with platform
|
|
195
|
+
*Android* and queue it on this runner's pool.
|
|
196
|
+
|
|
197
|
+
The runner advertises phones (`android`, `android-real`), running emulators and emulators that
|
|
198
|
+
exist but are off (`android`, `android-emulator`); an emulator is booted when a job needs it and
|
|
199
|
+
stays booted. Each job installs the build, clears the app's data, records an MP4 when asked
|
|
200
|
+
(runs over 3 minutes need `ffmpeg` on `PATH`) and captures the app's logcat warnings, errors and
|
|
201
|
+
crashes per step. The same devices serve **device sessions**, in which an AI agent explores the
|
|
202
|
+
app over MCP before writing a script. Details: [Android](https://github.com/leera-app/leera-qa-runner/blob/main/docs/test-runners.md#android),
|
|
203
|
+
[device sessions](https://github.com/leera-app/leera-qa-runner/blob/main/docs/qa-automation-authoring.md#device-sessions).
|
|
204
|
+
|
|
205
|
+
## Configuration
|
|
206
|
+
|
|
207
|
+
Settings are read in this order; the first one set wins:
|
|
208
|
+
|
|
209
|
+
1. command-line flags
|
|
210
|
+
2. `RUNNER_*` environment variables
|
|
211
|
+
3. legacy `LEERA_URL` / `LEERA_RUNNER_TOKEN`
|
|
212
|
+
4. `~/.leera-qa-runner/config.json` (and the `token` file)
|
|
213
|
+
5. defaults
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
leera-qa-runner config list
|
|
217
|
+
leera-qa-runner config get url
|
|
218
|
+
leera-qa-runner config set concurrency.web 2
|
|
219
|
+
leera-qa-runner config unset proxy
|
|
220
|
+
leera-qa-runner config set-token # replace the token (prompted, or --token-stdin)
|
|
221
|
+
leera-qa-runner config path
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| Config key | Environment variable | Default | Meaning |
|
|
225
|
+
|---|---|---|---|
|
|
226
|
+
| `url` | `RUNNER_URL` (`LEERA_URL`) | — | Your workspace server, e.g. `https://app.example.com` |
|
|
227
|
+
| token (`config set-token`) | `RUNNER_TOKEN` (`LEERA_RUNNER_TOKEN`) | — | Runner pool token, `pm_run_…` |
|
|
228
|
+
| `name` | `RUNNER_NAME` | host name | Name shown on the Test runners page |
|
|
229
|
+
| `labels.extra` | — | — | Labels added to the detected ones (comma separated) |
|
|
230
|
+
| `labels.exclude` | — | — | Detected labels to leave out |
|
|
231
|
+
| — | `RUNNER_LABELS` | detected | Exact label list; replaces detection (`--labels` too) |
|
|
232
|
+
| `concurrency.web` | `RUNNER_CONCURRENCY` | `1` | Browser jobs at once (`--concurrency`) |
|
|
233
|
+
| `headless` | `RUNNER_HEADLESS` | `true` | Run Chromium without a window (`--headed` overrides) |
|
|
234
|
+
| `proxy` | `RUNNER_PROXY` | — | Proxy server for the browser |
|
|
235
|
+
| `action_timeout_ms` | `RUNNER_ACTION_TIMEOUT_MS` | `15000` | Time limit per action |
|
|
236
|
+
| `job_timeout_ms` | `RUNNER_JOB_TIMEOUT_MS` | `900000` | Time limit per job; later steps are blocked |
|
|
237
|
+
| `android.sdk_path` | — (`ANDROID_HOME`) | detected | Android SDK directory, absolute path; wins over `ANDROID_HOME` |
|
|
238
|
+
| `builds.allow_local_paths` | `RUNNER_ALLOW_LOCAL_PATHS` | `false` | Install apps from a file path on this machine (install `path`); off: such jobs are blocked |
|
|
239
|
+
| — | `RUNNER_ONCE` | `false` | Exit after one job (`--once`) |
|
|
240
|
+
| — | `RUNNER_RUN_ID` | — | Only take jobs from this test run (`--run-id`) |
|
|
241
|
+
| `env.NAME` | — | — | Variables applied when the service starts |
|
|
242
|
+
| — | `LEERA_RUNNER_HOME` | `~/.leera-qa-runner` | Where configuration, token, logs and browsers live |
|
|
243
|
+
| — | `PLAYWRIGHT_BROWSERS_PATH` | `~/.leera-qa-runner/browsers` when present | Where Chromium is installed |
|
|
244
|
+
|
|
245
|
+
Labels route jobs: a run that requires labels only goes to runners that have all of them. The
|
|
246
|
+
runner detects `web-chromium` when Chromium is installed, and `android`, `android-emulator` and
|
|
247
|
+
`android-real` for its Android devices.
|
|
248
|
+
|
|
249
|
+
### Files in the runner home
|
|
250
|
+
|
|
251
|
+
| Path | Contents |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `config.json` | Settings above (mode 0600) |
|
|
254
|
+
| `token` | The runner token (mode 0600; narrowed again if someone widens it) |
|
|
255
|
+
| `state.json` | pid, runner id and running jobs, for `service status` |
|
|
256
|
+
| `install.json` | How the runner was installed (`method`: pkg, exe, tar, brew, winget), read by `update` |
|
|
257
|
+
| `versions/` | Versions installed by `update`; `current` (a symlink, `current.txt` on Windows) names the active one |
|
|
258
|
+
| `logs/runner.log` | Service log, JSON lines |
|
|
259
|
+
| `browsers/` | Chromium installed by `setup browsers` |
|
|
260
|
+
| `appium/` | Appium home with the UiAutomator2 driver, seeded by `setup android` |
|
|
261
|
+
| `cache/builds/` | Downloaded app builds by SHA-256 (up to 10 GB) |
|
|
262
|
+
|
|
263
|
+
## Commands and exit codes
|
|
264
|
+
|
|
265
|
+
| Command | What it does |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `start [--once] [--service] [--headed] [--labels a,b] [--concurrency N] [--run-id N]` | Register and run jobs until stopped |
|
|
268
|
+
| `connect --url URL [--token-stdin] [--name NAME]` | Save URL and token, then run `doctor` |
|
|
269
|
+
| `doctor [--json] [--fix] [--offline] [--platform web\|android]` | Check the machine |
|
|
270
|
+
| `setup browsers` | Install Chromium |
|
|
271
|
+
| `setup android [--install-sdk] [--image ID] [--avd NAME]` | Prepare Appium and the Android SDK, list devices |
|
|
272
|
+
| `service install\|uninstall\|start\|stop\|restart\|status\|logs [-f]` | Background service |
|
|
273
|
+
| `config get\|set\|unset\|list\|path\|set-token` | Stored configuration |
|
|
274
|
+
| `version [--json]` | Print the version |
|
|
275
|
+
| `update [--check] [--version X] [--yes]` | Update the runner; `--check` exits 10 when an update is available |
|
|
276
|
+
|
|
277
|
+
| Exit code | Meaning |
|
|
278
|
+
|---|---|
|
|
279
|
+
| 0 | OK |
|
|
280
|
+
| 1 | Error |
|
|
281
|
+
| 2 | Usage or configuration error (unknown option, missing URL or token) |
|
|
282
|
+
| 3 | The server rejected the runner token — create a new one and run `config set-token` |
|
|
283
|
+
| 4 | The machine is not ready (`doctor` found a failing check) |
|
|
284
|
+
| 5 | The server needs a newer runner version — update the runner |
|
|
285
|
+
| 130 | Interrupted (Ctrl-C) |
|
|
286
|
+
|
|
287
|
+
## Update
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
leera-qa-runner update --check # exit 0: up to date, 10: an update is available
|
|
291
|
+
leera-qa-runner update # asks first; --yes skips the question, --version X picks a version
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`update` follows the way the runner was installed. Package, installer and tarball installs
|
|
295
|
+
download `leera-qa-runner-<os>-<arch>.tar.gz` (`.zip` on Windows) from the release, check it
|
|
296
|
+
against the release manifest `releases.json` (its Ed25519 signature is required when the build
|
|
297
|
+
carries a release key, and the SHA-256 always), unpack it into `~/.leera-qa-runner/versions/<version>`,
|
|
298
|
+
switch `versions/current` to it, keep the two newest versions and restart the background service —
|
|
299
|
+
no administrator rights. The installed launcher runs `versions/current` whenever it is newer
|
|
300
|
+
than the version it was installed with. npm, Homebrew and winget installs print (or, with
|
|
301
|
+
`--yes`, run) their own upgrade command; Docker users pull the new image. `RUNNER_UPDATE_BASE`
|
|
302
|
+
points `update` at a mirror of the releases page. A running runner logs once a day when the server
|
|
303
|
+
reports a newer version.
|
|
304
|
+
|
|
305
|
+
## Docker
|
|
306
|
+
|
|
307
|
+
For servers and CI without a desktop, the web-only image keeps working as before (it cannot
|
|
308
|
+
run Android jobs):
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
docker run -d --restart unless-stopped \
|
|
312
|
+
-e RUNNER_URL=https://app.example.com \
|
|
313
|
+
-e RUNNER_TOKEN=pm_run_… \
|
|
314
|
+
-e RUNNER_NAME=docker-1 \
|
|
315
|
+
ghcr.io/leera-app/leera-qa-runner:latest
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
It reads the same `RUNNER_*` variables.
|
|
319
|
+
|
|
320
|
+
## Uninstall
|
|
321
|
+
|
|
322
|
+
1. `leera-qa-runner service uninstall` (if you installed the service).
|
|
323
|
+
2. Remove the program:
|
|
324
|
+
- macOS package: `sudo rm -rf /usr/local/lib/leera-qa-runner /usr/local/bin/leera-qa-runner && sudo pkgutil --forget io.leera.qa-runner`
|
|
325
|
+
- Linux: `rm -rf ~/.local/lib/leera-qa-runner ~/.local/bin/leera-qa-runner`
|
|
326
|
+
- Windows: *Settings → Apps → Leera QA Runner → Uninstall*
|
|
327
|
+
- npm: `npm uninstall -g @leera.io/qa-runner`
|
|
328
|
+
3. Remove its data: `rm -rf ~/.leera-qa-runner` (Windows: `%USERPROFILE%\.leera-qa-runner`).
|
|
329
|
+
4. Delete the runner from the Test runners page, or revoke its token.
|
|
330
|
+
|
|
331
|
+
## License
|
|
332
|
+
|
|
333
|
+
Proprietary — see [LICENSE](LICENSE). Free to use with a Leera workspace.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
export const APPIUM_DRIVERS = [
|
|
5
|
+
{
|
|
6
|
+
name: 'uiautomator2',
|
|
7
|
+
pkg: 'appium-uiautomator2-driver',
|
|
8
|
+
automationName: 'UiAutomator2',
|
|
9
|
+
platforms: ['android'],
|
|
10
|
+
hosts: ['darwin', 'linux', 'win32'],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'xcuitest',
|
|
14
|
+
pkg: 'appium-xcuitest-driver',
|
|
15
|
+
automationName: 'XCUITest',
|
|
16
|
+
platforms: ['ios'],
|
|
17
|
+
hosts: ['darwin'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'mac2',
|
|
21
|
+
pkg: 'appium-mac2-driver',
|
|
22
|
+
automationName: 'Mac2',
|
|
23
|
+
platforms: ['macos'],
|
|
24
|
+
hosts: ['darwin'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'windows',
|
|
28
|
+
pkg: 'appium-windows-driver',
|
|
29
|
+
automationName: 'Windows',
|
|
30
|
+
platforms: ['windows'],
|
|
31
|
+
hosts: ['win32'],
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const require = createRequire(import.meta.url);
|
|
35
|
+
/** Finds an installed package next to the runner (optional dependencies may be absent). */
|
|
36
|
+
export function resolvePackage(pkg, resolve = require.resolve) {
|
|
37
|
+
try {
|
|
38
|
+
const manifest = resolve(`${pkg}/package.json`);
|
|
39
|
+
const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
40
|
+
return { name: pkg, version: parsed.version ?? '0.0.0', dir: dirname(manifest) };
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Drivers installed in the runner's node_modules that work on this host. */
|
|
47
|
+
export function bundledDrivers(host = process.platform, resolve) {
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const spec of APPIUM_DRIVERS) {
|
|
50
|
+
if (!spec.hosts.includes(host))
|
|
51
|
+
continue;
|
|
52
|
+
const found = resolvePackage(spec.pkg, resolve);
|
|
53
|
+
if (found)
|
|
54
|
+
out.push({ ...spec, version: found.version, dir: found.dir });
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
export function driverFor(platform) {
|
|
59
|
+
return APPIUM_DRIVERS.find((spec) => spec.platforms.includes(platform));
|
|
60
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
/** `~/.leera-qa-runner/appium`: Appium's APPIUM_HOME for this runner, never `~/.appium`. */
|
|
4
|
+
export function appiumHomeDir(paths) {
|
|
5
|
+
return join(paths.root, 'appium');
|
|
6
|
+
}
|
|
7
|
+
/** Appium caches the extensions it found here; deleting it makes Appium rescan node_modules. */
|
|
8
|
+
export function manifestPath(home) {
|
|
9
|
+
return join(home, 'node_modules', '.cache', 'appium', 'extensions.yaml');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Seeds APPIUM_HOME with the drivers bundled in the runner's node_modules.
|
|
13
|
+
*
|
|
14
|
+
* Instead of `appium driver install --source=local` (which runs `npm link`, needs npm and
|
|
15
|
+
* writes to npm's global prefix), each driver is linked into `APPIUM_HOME/node_modules` and
|
|
16
|
+
* Appium's extension manifest cache is dropped whenever the bundled versions change, so
|
|
17
|
+
* Appium rescans and records them itself on the next start. Node resolves a linked driver
|
|
18
|
+
* through its real path, so it loads the same `appium` package the server runs from.
|
|
19
|
+
*/
|
|
20
|
+
export function seedAppiumHome(home, appium, drivers) {
|
|
21
|
+
const modules = join(home, 'node_modules');
|
|
22
|
+
mkdirSync(modules, { recursive: true, mode: 0o700 });
|
|
23
|
+
const pkgJson = join(home, 'package.json');
|
|
24
|
+
if (!existsSync(pkgJson))
|
|
25
|
+
writeFileSync(pkgJson, `${JSON.stringify({ private: true, description: 'Appium home for the QA runner' }, null, 2)}\n`);
|
|
26
|
+
const seedFile = join(home, 'seed.json');
|
|
27
|
+
const wanted = { appium: appium.version, drivers: Object.fromEntries(drivers.map((d) => [d.name, `${d.version}@${d.dir}`])) };
|
|
28
|
+
let previous = null;
|
|
29
|
+
try {
|
|
30
|
+
previous = JSON.parse(readFileSync(seedFile, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
previous = null;
|
|
34
|
+
}
|
|
35
|
+
let changed = false;
|
|
36
|
+
for (const driver of drivers) {
|
|
37
|
+
const link = join(modules, driver.pkg);
|
|
38
|
+
if (linkTarget(link) !== resolve(driver.dir)) {
|
|
39
|
+
rmSync(link, { recursive: true, force: true });
|
|
40
|
+
symlinkSync(driver.dir, link, process.platform === 'win32' ? 'junction' : 'dir');
|
|
41
|
+
changed = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (changed || JSON.stringify(previous) !== JSON.stringify(wanted) || !existsSync(manifestPath(home))) {
|
|
45
|
+
rmSync(manifestPath(home), { force: true });
|
|
46
|
+
writeFileSync(seedFile, `${JSON.stringify(wanted, null, 2)}\n`);
|
|
47
|
+
changed = true;
|
|
48
|
+
}
|
|
49
|
+
return { home, changed, drivers: drivers.map((d) => ({ name: d.name, version: d.version })) };
|
|
50
|
+
}
|
|
51
|
+
function linkTarget(path) {
|
|
52
|
+
try {
|
|
53
|
+
if (!lstatSync(path).isSymbolicLink())
|
|
54
|
+
return null;
|
|
55
|
+
return resolve(join(path, '..'), readlinkSync(path));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|