@vostride/agent-qa-plugin-browserstack 0.0.0-stage → 0.3.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.md ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Pranshu Chittora
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/NOTICE.md ADDED
@@ -0,0 +1,11 @@
1
+ # agent-qa Notice
2
+
3
+ agent-qa is provided by Pranshu Chittora.
4
+
5
+ Copyright 2026 Pranshu Chittora
6
+
7
+ agent-qa is licensed under the Functional Source License, Version 1.1, ALv2 Future License (FSL-1.1-ALv2). See `LICENSE.md` for the full license terms.
8
+
9
+ Under the Grant of Future License in `LICENSE.md`, each version of the Software is also granted under the Apache License, Version 2.0 effective on the second anniversary of the date that version was made available.
10
+
11
+ Third-party dependencies are distributed under their own license terms. This notice covers agent-qa itself and does not replace or modify notices required by third-party dependency owners.
package/README.md CHANGED
@@ -1,3 +1,250 @@
1
- # Temporary Holding Version
1
+ # @vostride/agent-qa-plugin-browserstack
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The agent-qa plugin for [BrowserStack](https://www.browserstack.com). It runs agent-qa web tests on
4
+ BrowserStack browsers over the Playwright protocol, and device tests on BrowserStack Android and
5
+ iOS devices over Appium. It uploads a target's app once per content, returns each session's
6
+ dashboard link and marks each session with the test's outcome.
7
+
8
+ ## Install
9
+
10
+ Install the plugin at exactly the version of agent-qa the project runs:
11
+
12
+ ```bash
13
+ npm install --save-dev --save-exact @vostride/agent-qa-plugin-browserstack@<agent-qa version>
14
+ ```
15
+
16
+ `agent-qa plugins list` prints this line, with the version filled in, when the package is missing
17
+ or its version differs from agent-qa's.
18
+
19
+ ## Configure
20
+
21
+ ```yaml
22
+ plugins:
23
+ - name: browserstack
24
+ package: "@vostride/agent-qa-plugin-browserstack"
25
+ options:
26
+ username: "{{secret:BROWSERSTACK_USERNAME}}"
27
+ accessKey: "{{secret:BROWSERSTACK_ACCESS_KEY}}"
28
+
29
+ registry:
30
+ remotes:
31
+ - name: browserstack
32
+ plugin: browserstack
33
+ settings:
34
+ os: Windows
35
+ osVersion: "11"
36
+ - name: browserstack-devices
37
+ plugin: browserstack
38
+ devices:
39
+ bs-pixel:
40
+ platform: android
41
+ remote: browserstack-devices
42
+ model: Google Pixel 9
43
+ os: "15"
44
+ ```
45
+
46
+ A web test runs on BrowserStack with `use.browser.remote: browserstack` in the config, a suite or
47
+ the test. A device test runs there when its target's `device` is a device whose `remote` is a
48
+ BrowserStack remote.
49
+
50
+ ### Options
51
+
52
+ Options belong to the `plugins` entry and describe the account.
53
+
54
+ | Option | Default | Meaning |
55
+ | ----------------- | --------------------------------------- | -------------------------------------------------------------------- |
56
+ | `username` | required | The BrowserStack username, a credential. |
57
+ | `accessKey` | required | The BrowserStack access key, a credential. |
58
+ | `apiUrl` | `https://api.browserstack.com` | The Automate API: web session lookups, marks and the plan. |
59
+ | `appApiUrl` | `https://api-cloud.browserstack.com` | The App Automate API: uploads, device sessions, the plan. |
60
+ | `hubUrl` | `https://hub.browserstack.com/wd/hub` | The Appium hub device sessions connect to. |
61
+ | `playwrightUrl` | `wss://cdp.browserstack.com/playwright` | The Playwright endpoint web sessions connect to. |
62
+ | `local` | `false` | Start a BrowserStack Local tunnel for the run. |
63
+ | `localIdentifier` | - | The tunnel's identifier; without `local`, a tunnel already running. |
64
+ | `localBinary` | - | The BrowserStackLocal binary `local` runs, instead of the wrapper's. |
65
+
66
+ Write the credentials as `{{secret:<name>}}` references. The endpoints must use `https://`
67
+ (`wss://` for `playwrightUrl`) and carry no user or password, unless they point at this machine;
68
+ otherwise the entry is refused at the option's path. The credentials travel only in the masked
69
+ Playwright URL, in the `bstack:options` agent-qa masks and in the basic auth header of REST calls.
70
+ They never appear in a message, a log or an answer.
71
+
72
+ ### Settings
73
+
74
+ Settings belong to each remote and describe one place.
75
+
76
+ | Setting | Default | Meaning |
77
+ | ---------------- | ---------- | ------------------------------------------------------------------------------------- |
78
+ | `os` | `OS X` | The web session's operating system, for example `Windows`. |
79
+ | `osVersion` | `Sequoia` | The operating system version, for example `11` or `Sequoia`. |
80
+ | `browser` | by engine | `chrome`, `edge`, `playwright-chromium`, `playwright-firefox` or `playwright-webkit`. |
81
+ | `browserVersion` | - | The Chrome or Edge version, for example `latest` or `latest-1`. |
82
+ | `project` | `agent-qa` | The BrowserStack project sessions are filed under. |
83
+ | `build` | the run id | The BrowserStack build name. |
84
+ | `appiumVersion` | `3.5.2` | The Appium version device sessions ask for. |
85
+ | `extra` | - | Capabilities merged last, overriding the ones above. |
86
+
87
+ `agent-qa plugins describe browserstack` prints the same tables from the schemas the package
88
+ ships, and `--json` prints them as JSON.
89
+
90
+ ## Web tests
91
+
92
+ A web acquire answers the Playwright URL with a `caps` query holding JSON capabilities:
93
+
94
+ - `browserstack.username` and `browserstack.accessKey`;
95
+ - `browser`: `settings.browser`, or by the test's engine, `chrome` for Chromium,
96
+ `playwright-firefox` for Firefox and `playwright-webkit` for WebKit. A browser that cannot run
97
+ the test's engine is refused at `settings.browser`;
98
+ - `os`, `os_version` and, when set, `browser_version`;
99
+ - `client.playwrightVersion`: agent-qa's Playwright version;
100
+ - `project`, `build` (the run id unless `settings.build` is set) and `name` (the test's name);
101
+ - `settings.extra`, merged last.
102
+
103
+ The query carries the access key, so agent-qa masks the whole URL.
104
+
105
+ ### Playwright versions
106
+
107
+ BrowserStack runs a web session with the Playwright version its client asks for, and only versions
108
+ BrowserStack lists. No API lists them, so the plugin carries BrowserStack's list: today Playwright
109
+ 1.59 to 1.63. When agent-qa's Playwright major.minor is not on the list, every web test on
110
+ BrowserStack is refused with kind `version`:
111
+
112
+ ```text
113
+ BrowserStack lists Playwright 1.59 to 1.63, not 1.64, so web tests cannot run there yet; run this test here or on another remote.
114
+ ```
115
+
116
+ The list moves with each plugin release, which follows agent-qa's version. When BrowserStack adds
117
+ a version, a plugin release adds it, and `agent-qa doctor` stops warning.
118
+
119
+ ## Device tests
120
+
121
+ A device acquire answers `hubUrl` with `bstack:options` holding:
122
+
123
+ - `userName` and `accessKey`;
124
+ - `appiumVersion`: the device's own `bstack:options.appiumVersion`, then `settings.appiumVersion`,
125
+ then the plugin's default, `3.5.2`;
126
+ - `projectName`, `buildName` (the run id unless `settings.build` is set) and `sessionName` (the
127
+ test's name);
128
+ - `settings.extra`, merged last.
129
+
130
+ agent-qa adds the device's identity: `model` as `appium:deviceName` and `os` as
131
+ `appium:platformVersion`. The device's own `capabilities` are merged over the answer.
132
+
133
+ ## Uploads and reuse
134
+
135
+ When a target sets `app.ref` (a `bs://` id or an `https` URL), BrowserStack installs that app and
136
+ the plugin uploads nothing.
137
+
138
+ The plugin's `uploadApp` takes the target's `app.path`:
139
+
140
+ 1. it names the content with the custom id `agent-qa-<first 32 hex of the file's SHA-256>`;
141
+ 2. it asks `GET app-automate/recent_apps/<custom id>` and reuses the newest upload listed there;
142
+ 3. when nothing is listed, it checks that the file still has the size and SHA-256 agent-qa sent,
143
+ refusing it at path `path` when it changed, then posts the file once to
144
+ `POST app-automate/upload` with that custom id, reporting `uploading app (<n> MB)` and then
145
+ `app uploaded` as progress.
146
+
147
+ It answers the `bs://` ref the session installs. The same file uploads once, and later runs reuse
148
+ it while BrowserStack keeps it, which is 30 days. A cancel stops the upload.
149
+
150
+ ## Links and marks
151
+
152
+ When a session starts, the plugin returns its dashboard link, BrowserStack's `browser_url`. It needs
153
+ a BrowserStack login. The shareable `public_url` is never returned.
154
+
155
+ - A device session is read by its WebDriver session id from
156
+ `GET app-automate/sessions/<id>.json`.
157
+ - A web session is found through the Automate REST API. The plugin lists running builds
158
+ (`GET automate/builds.json`), keeps the build named for the run, lists its running sessions and
159
+ keeps those with the test's name. It then reads each one's details and keeps those whose
160
+ `created_at` is no more than 5 seconds before agent-qa asked for the session. When exactly one
161
+ is left, that is the session. When none or several are left, the test gets no link and the
162
+ session stays unmarked, with one line on the plugin's stderr. A guessed session is never marked.
163
+
164
+ When the test ends, the plugin marks the session:
165
+
166
+ | Outcome | Mark |
167
+ | ---------------- | ------------------------------------------------ |
168
+ | passed | `passed` |
169
+ | failed | `failed`, with the reason, cut to 255 characters |
170
+ | agent-qa's error | `failed`, with agent-qa's sentence |
171
+ | cancelled | none |
172
+
173
+ Marks go to `PUT automate/sessions/<id>.json` for web and `PUT app-automate/sessions/<id>.json`
174
+ for devices. A lookup or a mark that fails is one stderr line and never fails the test.
175
+
176
+ ## Failures
177
+
178
+ | BrowserStack answer | Kind |
179
+ | --------------------- | ------------- |
180
+ | `401` | `auth` |
181
+ | `429` | `capacity` |
182
+ | no answer | `unreachable` |
183
+ | any other non-success | `refused` |
184
+
185
+ Each failure is one line naming the status, the request and BrowserStack's message, with
186
+ `retry after` on a `429`. A Playwright version BrowserStack does not list is kind `version`. A
187
+ setting or option that is wrong is kind `refused` with its path. A BrowserStack Local tunnel that
188
+ cannot start is kind `unreachable` at path `options.local`, or `options.localBinary` when that is
189
+ set.
190
+
191
+ ## Account check
192
+
193
+ `agent-qa doctor` runs the plugin's `check`, which opens no session:
194
+
195
+ - `GET automate/plan.json` and `GET app-automate/plan.json` report each product's sessions, for
196
+ example `Automate: 1 of 5 parallel sessions running, 0 queued`.
197
+ - A `401` on either warns `the username or access key was refused`.
198
+ - A `404` warns that the product is not on the account, for example
199
+ `App Automate is not on this account`.
200
+ - When BrowserStack does not list agent-qa's Playwright version, it warns
201
+ `BrowserStack lists Playwright 1.59 to 1.63, not 1.64; web tests on BrowserStack are refused until it does.`
202
+
203
+ ## BrowserStack Local
204
+
205
+ BrowserStack Local is a tunnel that lets BrowserStack's browsers and devices reach a target only
206
+ this machine can reach, such as `localhost` or a staging host behind a VPN.
207
+
208
+ - `local: true` makes the plugin start its own tunnel on the run's first acquire, with a fresh
209
+ identifier, `agent-qa-<16 hex>`, or with `localIdentifier` when that is set too. The start
210
+ counts against the acquire's deadline, and acquires that arrive while it starts wait for the
211
+ same tunnel. Every web and device session of the run then asks for that tunnel:
212
+ `browserstack.local` and `browserstack.localIdentifier` in the web capabilities, `local` and
213
+ `localIdentifier` in `bstack:options`.
214
+ - `localIdentifier` without `local` asks every session for a tunnel that is already running
215
+ under that identifier, and the plugin starts nothing.
216
+ - The tunnel comes from BrowserStack's `browserstack-local` package, pinned at `1.5.16`. It finds
217
+ BrowserStack's binary in `~/.browserstack`, the working directory or the temp directory, and
218
+ downloads it from BrowserStack over `https` when none is there. `localBinary` names a binary
219
+ to run instead; it needs `local: true`. When a binary fails, the plugin never deletes it or
220
+ downloads another one; the next acquire runs the same binary again.
221
+ - The tunnel logs to a fresh file under the temp directory,
222
+ `agent-qa-browserstack-local-<pid>-<random>.log`, readable only by you. Nothing is written to
223
+ the project. When the tunnel cannot start, the refusal has kind `unreachable` at path
224
+ `options.local`, or `options.localBinary` when that is set, and names that log.
225
+ - Before it starts the tunnel, the plugin runs the binary once with `--version` to check that
226
+ this machine can run it. A binary this machine cannot run refuses the session the same way,
227
+ with kind `unreachable` at the same path, naming the binary, the cause and the fix. The tunnel
228
+ never ran, so the line names no log and no log file is created. The fix follows the cause:
229
+ - A Mac binary built for another CPU (`EBADARCH`): install Rosetta
230
+ (`softwareupdate --install-rosetta --agree-to-license`) or set `localBinary` to a binary
231
+ built for this Mac. `browserstack-local` downloads BrowserStack's Intel build on every Mac,
232
+ which an Apple Silicon Mac runs only under Rosetta.
233
+ - A path that does not exist (`ENOENT`): set `localBinary` to the path of a BrowserStackLocal
234
+ binary.
235
+ - A file that is not executable (`EACCES`): make it executable, or set `localBinary` to one that
236
+ is.
237
+ - Any other failure: set `localBinary` to a binary this machine can run.
238
+ - The tunnel runs as a daemon outside the plugin's process group, so the plugin stops it itself:
239
+ on `shutdown`, when agent-qa closes the plugin's stdin and on `SIGTERM`. A tunnel still starting
240
+ then is aborted: the plugin kills the launch and runs the binary's `--daemon stop` for the
241
+ tunnel's identifier, and stops the tunnel too if it connects meanwhile.
242
+ - The wrapper's own console output goes to the plugin's stderr, never to the protocol stream.
243
+
244
+ BrowserStack's tunnel binary takes the access key as `--key` on its command line. That is
245
+ BrowserStack's design, and it means other users of the same machine can read the key with `ps`
246
+ while the tunnel runs.
247
+
248
+ ## License
249
+
250
+ FSL-1.1-ALv2. See `LICENSE.md` and `NOTICE.md` in the package.
package/dist/main.js ADDED
@@ -0,0 +1,7 @@
1
+ import { n as plugin_default } from "./plugin-fBvi25yB.js";
2
+ import { runPlugin } from "@vostride/agent-qa-plugin-kit";
3
+ // src/main.ts
4
+ runPlugin(plugin_default);
5
+ export {};
6
+
7
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","names":["plugin"],"sources":["../src/main.ts"],"sourcesContent":["import { runPlugin } from '@vostride/agent-qa-plugin-kit'\nimport plugin from './plugin.js'\n\nrunPlugin(plugin)\n"],"mappings":";;;AAGA,UAAUA,cAAM"}
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "username": {
6
+ "type": "string",
7
+ "credential": true,
8
+ "description": "The BrowserStack username"
9
+ },
10
+ "accessKey": {
11
+ "type": "string",
12
+ "credential": true,
13
+ "description": "The BrowserStack access key"
14
+ },
15
+ "apiUrl": {
16
+ "default": "https://api.browserstack.com",
17
+ "description": "BrowserStack's Automate API, https:// unless it points at this machine",
18
+ "type": "string",
19
+ "format": "uri"
20
+ },
21
+ "appApiUrl": {
22
+ "default": "https://api-cloud.browserstack.com",
23
+ "description": "BrowserStack's App Automate API, https:// unless it points at this machine",
24
+ "type": "string",
25
+ "format": "uri"
26
+ },
27
+ "hubUrl": {
28
+ "default": "https://hub.browserstack.com/wd/hub",
29
+ "description": "BrowserStack's Appium hub, https:// unless it points at this machine",
30
+ "type": "string",
31
+ "format": "uri"
32
+ },
33
+ "playwrightUrl": {
34
+ "default": "wss://cdp.browserstack.com/playwright",
35
+ "description": "BrowserStack's Playwright endpoint, wss:// unless it points at this machine",
36
+ "type": "string",
37
+ "format": "uri"
38
+ },
39
+ "local": {
40
+ "default": false,
41
+ "description": "Start a BrowserStack Local tunnel for this run, named by localIdentifier or a fresh one",
42
+ "type": "boolean"
43
+ },
44
+ "localIdentifier": {
45
+ "description": "The identifier of a BrowserStack Local tunnel; without local, a tunnel already running",
46
+ "type": "string",
47
+ "minLength": 1
48
+ },
49
+ "localBinary": {
50
+ "description": "The BrowserStackLocal binary to run instead of the one the wrapper downloads",
51
+ "type": "string",
52
+ "minLength": 1
53
+ }
54
+ },
55
+ "required": [
56
+ "username",
57
+ "accessKey"
58
+ ],
59
+ "additionalProperties": false
60
+ }