@vostride/agent-qa-plugin-hyperbrowser 0.0.0-stage → 0.3.2

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,185 @@
1
- # Temporary Holding Version
1
+ # @vostride/agent-qa-plugin-hyperbrowser
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 [Hyperbrowser](https://hyperbrowser.ai) cloud browsers. Before a web test
4
+ connects, it creates a Hyperbrowser session and answers agent-qa with the session's CDP URL, and
5
+ after the test it stops the session. Hyperbrowser runs Chromium only.
6
+
7
+ ## Install
8
+
9
+ Install the plugin at exactly the version of agent-qa the project runs:
10
+
11
+ ```bash
12
+ npm install --save-dev --save-exact @vostride/agent-qa-plugin-hyperbrowser@<agent-qa version>
13
+ ```
14
+
15
+ `agent-qa plugins list` prints this line, with the version filled in, when the package is missing
16
+ or its version differs from agent-qa's.
17
+
18
+ ## Configure
19
+
20
+ ```yaml
21
+ plugins:
22
+ - name: hyperbrowser
23
+ package: "@vostride/agent-qa-plugin-hyperbrowser"
24
+ options:
25
+ apiKey: "{{secret:HYPERBROWSER_API_KEY}}"
26
+
27
+ registry:
28
+ remotes:
29
+ - name: hyperbrowser
30
+ plugin: hyperbrowser
31
+ - name: hyperbrowser-unrecorded
32
+ plugin: hyperbrowser
33
+ settings:
34
+ recording: false
35
+ ```
36
+
37
+ A test runs there with `use.browser.remote: hyperbrowser` in the config, a suite or the test.
38
+
39
+ ### Options
40
+
41
+ Options belong to the `plugins` entry and describe the account.
42
+
43
+ | Option | Default | Meaning |
44
+ | -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------ |
45
+ | `apiKey` | - | The Hyperbrowser API key, a credential, required. Write it as a `{{secret:<name>}}` reference. |
46
+ | `apiUrl` | `https://api.hyperbrowser.ai` | Hyperbrowser's API. It must use `https://` with no user or password, unless it points at this machine. |
47
+
48
+ The key is sent only as the `x-api-key` header. It never appears in a message, a log or a check
49
+ line.
50
+
51
+ ### Settings
52
+
53
+ Settings belong to each remote and describe one place.
54
+
55
+ | Setting | Default | Meaning |
56
+ | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
57
+ | `timeoutMinutes` | `720` | Minutes before Hyperbrowser stops the session (1 to 720), always sent as `timeoutMinutes`. It overrides the team default in Hyperbrowser's settings. |
58
+ | `recording` | - | Hyperbrowser's web recording, sent as `enableWebRecording` when set. Unset keeps the account's default, which records. `false` turns it off. |
59
+ | `stealth` | `false` | Hyperbrowser's stealth mode, sent as `useStealth`. |
60
+ | `proxy` | `false` | Hyperbrowser's proxy: `true`, or `{ country, state, city }` with any of the three. Sent as `useProxy`, with `proxyCountry`, `proxyState` and `proxyCity` for the fields given. |
61
+ | `solveCaptchas` | `false` | Hyperbrowser's CAPTCHA solving, sent as `solveCaptchas`. |
62
+ | `adblock` | `false` | Hyperbrowser's ad blocking, sent as `adblock`. |
63
+ | `trackers` | `false` | Hyperbrowser's tracker blocking, sent as `trackers`. |
64
+ | `annoyances` | `false` | Hyperbrowser's annoyance blocking, sent as `annoyances`. |
65
+ | `acceptCookies` | `false` | Hyperbrowser's automatic acceptance of cookie banners, sent as `acceptCookies`. |
66
+ | `screen` | - | The browser screen, `{ width, height }` in pixels, sent as `screen`. Unset keeps Hyperbrowser's default. |
67
+ | `region` | - | The Hyperbrowser region, for example `us-east` or `europe-west`, sent as `region`. |
68
+ | `profile` | - | A Hyperbrowser profile id, loaded read-only and sent as `profile: { id, persistChanges: false }`. Needs `context: reuse`. |
69
+ | `extensions` | - | Hyperbrowser extension ids loaded into the browser, sent as `extensionIds`. |
70
+ | `extra` | - | Any other Hyperbrowser request field, merged last. |
71
+
72
+ `agent-qa plugins describe hyperbrowser` prints the same tables from the schemas the package ships,
73
+ and `--json` prints them as JSON.
74
+
75
+ ## How the request body is built
76
+
77
+ The create request (`POST /api/session`) is built in this order, each part overriding the one
78
+ before:
79
+
80
+ 1. the lifetime, `timeoutMinutes`;
81
+ 2. the features that change the page: stealth, proxies, CAPTCHA solving, ad, tracker and annoyance
82
+ blocking and cookie auto-accept. Each is sent off unless a setting turns it on, because a test
83
+ should see the app the way its users do;
84
+ 3. the proxy fields: `proxy: true` sends `useProxy: true`, and a proxy object also sends
85
+ `proxyCountry`, `proxyState` and `proxyCity` for the fields it gives;
86
+ 4. the other settings above, in Hyperbrowser's field names, each only when it is set;
87
+ 5. `extra`, last, so a pass-through key overrides both a default and a setting.
88
+
89
+ For example, `settings: { extra: { useUltraStealth: true } }` asks for Hyperbrowser's Enterprise
90
+ stealth mode. Hyperbrowser's own proxy servers for Enterprise accounts go through `extra` too.
91
+ agent-qa refuses a password there unless it is a `{{secret:<name>}}` reference:
92
+
93
+ ```yaml
94
+ settings:
95
+ extra:
96
+ proxyServer: "http://proxy.example.com:8080"
97
+ proxyServerUsername: "<proxy user>"
98
+ proxyServerPassword: "{{secret:HYPERBROWSER_PROXY_PASSWORD}}"
99
+ ```
100
+
101
+ Proxies and CAPTCHA solving need a paid Hyperbrowser plan. When a feature is not on the plan,
102
+ Hyperbrowser refuses the create, as described under Failures.
103
+
104
+ ## Lifetime and disconnects
105
+
106
+ By default the plugin asks for a 720-minute session, the longest Hyperbrowser allows.
107
+ `timeoutMinutes` lowers it; a value outside 1 to 720 is refused with path
108
+ `settings.timeoutMinutes` before any session is created.
109
+
110
+ The plugin never asks Hyperbrowser for keep-alive, so Hyperbrowser stops a session whose connection
111
+ drops. A session left behind by a killed job, where agent-qa and the plugin die together, stops
112
+ when its connection does. A session whose create was still in flight when the job was killed was
113
+ never connected and has no idle timeout, so it runs until `timeoutMinutes`; lower
114
+ `timeoutMinutes` to bound it. agent-qa also stops every session itself after the test; the
115
+ lifetime only bounds a session that agent-qa could not stop.
116
+
117
+ ## Recording
118
+
119
+ Hyperbrowser records sessions by default and keeps the replay on its dashboard, behind its login.
120
+ agent-qa's masking does not reach that replay, so a secret a test types may show in it. Set
121
+ `recording: false` on a remote to turn recording off for its sessions:
122
+
123
+ ```yaml
124
+ registry:
125
+ remotes:
126
+ - name: hyperbrowser-unrecorded
127
+ plugin: hyperbrowser
128
+ settings:
129
+ recording: false
130
+ ```
131
+
132
+ Video replay is off unless you ask for it with `extra: { enableVideoWebRecording: true }`. It needs
133
+ web recording, so do not combine it with `recording: false`.
134
+
135
+ ## Profiles
136
+
137
+ `profile` loads a Hyperbrowser profile by id, and the plugin sends `persistChanges: false`, so
138
+ nothing a test does is saved back. A profile lives in the browser's first context, so the remote
139
+ needs `context: reuse`. Without it, the remote is refused with kind `refused` and path
140
+ `settings.profile`.
141
+
142
+ ## Failures
143
+
144
+ | Hyperbrowser answer | Kind |
145
+ | --------------------- | ------------- |
146
+ | no answer | `unreachable` |
147
+ | `401` | `auth` |
148
+ | `429` | `capacity` |
149
+ | any other non-success | `refused` |
150
+
151
+ Each failure is one line naming the status, the request and Hyperbrowser's reason, with the
152
+ `Retry-After` wait on a `429`. A `403` most often means a feature is not on the plan, for example
153
+ `Hyperbrowser answered 403 to POST /api/session: Proxy usage requires a paid plan`; turn the
154
+ setting off or change the plan.
155
+
156
+ Hyperbrowser chooses the session id, so a create that fails has no session to stop. When
157
+ Hyperbrowser answers a session without a `wsEndpoint`, the remote is refused and agent-qa stops
158
+ the session by the id it answered.
159
+
160
+ On release, `2xx` and `404` both count as stopped, so stopping a session Hyperbrowser already
161
+ stopped after a disconnect succeeds.
162
+
163
+ ## Links and the live view
164
+
165
+ The session link is the session's page in Hyperbrowser's dashboard, which sits behind
166
+ Hyperbrowser's login and shows the replay. The plugin returns it only when it is an `https://` URL.
167
+
168
+ Hyperbrowser's live view URL and the session token let anyone who has them watch and drive the
169
+ session, so both are credentials: the plugin adds them, with the CDP URL, to the values agent-qa
170
+ masks, and never returns either as the link.
171
+
172
+ ## Account check
173
+
174
+ `agent-qa doctor` runs the plugin's `check`, which reads the active sessions with
175
+ `GET /api/sessions?status=active&page=1` and opens none:
176
+
177
+ - An accepted key reads `the API key was accepted`, followed by the number of active sessions
178
+ when Hyperbrowser gives it, for example `2 active sessions`.
179
+ - A refused key warns `the API key was refused`.
180
+ - Any other failure warns with its one line, for example
181
+ `Hyperbrowser could not be reached at <apiUrl>`.
182
+
183
+ ## License
184
+
185
+ FSL-1.1-ALv2. See `LICENSE.md` and `NOTICE.md` in the package.
package/dist/main.js ADDED
@@ -0,0 +1,7 @@
1
+ import { t as plugin_default } from "./plugin-6PEkbMWx.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,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "apiKey": {
6
+ "type": "string",
7
+ "credential": true,
8
+ "description": "The Hyperbrowser API key"
9
+ },
10
+ "apiUrl": {
11
+ "default": "https://api.hyperbrowser.ai",
12
+ "description": "Hyperbrowser's API, https:// unless it points at this machine",
13
+ "type": "string",
14
+ "format": "uri"
15
+ }
16
+ },
17
+ "required": [
18
+ "apiKey"
19
+ ],
20
+ "additionalProperties": false
21
+ }
@@ -0,0 +1,198 @@
1
+ import { PluginError, checkEndpoints, createVendorApi, credential, definePlugin, z } from "@vostride/agent-qa-plugin-kit";
2
+ // src/hyperbrowser-api.ts
3
+ const HyperbrowserSessionSchema = z.object({
4
+ id: z.string().min(1),
5
+ wsEndpoint: z.string().min(1),
6
+ liveUrl: z.string().min(1).optional(),
7
+ token: z.string().min(1).optional(),
8
+ sessionUrl: z.string().min(1).optional()
9
+ });
10
+ function createHyperbrowserApi(input) {
11
+ const api = createVendorApi({
12
+ vendor: "Hyperbrowser",
13
+ baseUrl: input.apiUrl,
14
+ headers: { "x-api-key": input.apiKey }
15
+ });
16
+ return {
17
+ async createSession(body) {
18
+ const response = await api.send("POST", "api/session", {
19
+ headers: {
20
+ "content-type": "application/json",
21
+ accept: "application/json"
22
+ },
23
+ body: JSON.stringify(body)
24
+ });
25
+ if (!response.ok) throw await api.failure(response, "POST /api/session");
26
+ return api.json(response);
27
+ },
28
+ async stop(id, signal) {
29
+ const response = await api.send("PUT", `api/session/${encodeURIComponent(id)}/stop`, {
30
+ headers: { accept: "application/json" },
31
+ signal
32
+ });
33
+ if (response.ok || response.status === 404) {
34
+ await response.body?.cancel();
35
+ return;
36
+ }
37
+ throw await api.failure(response, "PUT /api/session/{id}/stop");
38
+ },
39
+ async activeSessions(signal) {
40
+ const response = await api.send("GET", "api/sessions?status=active&page=1", {
41
+ headers: { accept: "application/json" },
42
+ signal
43
+ });
44
+ if (!response.ok) throw await api.failure(response, "GET /api/sessions");
45
+ return api.json(response);
46
+ }
47
+ };
48
+ }
49
+ // src/plugin.ts
50
+ const options = z.strictObject({
51
+ apiKey: credential("The Hyperbrowser API key"),
52
+ apiUrl: z.url().default("https://api.hyperbrowser.ai").describe("Hyperbrowser's API, https:// unless it points at this machine")
53
+ });
54
+ const settings = z.strictObject({
55
+ timeoutMinutes: z.int().min(1).max(720).default(720).describe("Minutes before Hyperbrowser stops the session"),
56
+ recording: z.boolean().optional().describe("Hyperbrowser's web recording; unset keeps the account's default, which records"),
57
+ stealth: z.boolean().default(false).describe("Hyperbrowser's stealth mode, which changes what pages see"),
58
+ proxy: z.union([z.boolean(), z.strictObject({
59
+ country: z.string().min(1).optional(),
60
+ state: z.string().min(1).optional(),
61
+ city: z.string().min(1).optional()
62
+ })]).default(false).describe("Hyperbrowser's proxy, true or { country, state, city }"),
63
+ solveCaptchas: z.boolean().default(false).describe("Hyperbrowser's CAPTCHA solving"),
64
+ adblock: z.boolean().default(false).describe("Hyperbrowser's ad blocking"),
65
+ trackers: z.boolean().default(false).describe("Hyperbrowser's tracker blocking"),
66
+ annoyances: z.boolean().default(false).describe("Hyperbrowser's annoyance blocking"),
67
+ acceptCookies: z.boolean().default(false).describe("Hyperbrowser's automatic acceptance of cookie banners"),
68
+ screen: z.strictObject({
69
+ width: z.int().positive(),
70
+ height: z.int().positive()
71
+ }).optional().describe("The browser screen size in pixels"),
72
+ region: z.string().min(1).optional().describe("The Hyperbrowser region the session runs in"),
73
+ profile: z.string().min(1).optional().describe("A Hyperbrowser profile id loaded read-only; needs context: reuse on the remote"),
74
+ extensions: z.array(z.string().min(1)).min(1).optional().describe("Hyperbrowser extension ids loaded into the browser"),
75
+ extra: z.record(z.string(), z.unknown()).optional().describe("Fields merged last into the session create request, overriding the rest")
76
+ });
77
+ function checkSettings(input, session) {
78
+ if (input.profile !== void 0 && (session.platform !== "web" || session.context !== "reuse")) throw new PluginError("profile loads only into a reused context; set context: reuse on the remote.", {
79
+ kind: "refused",
80
+ path: "settings.profile"
81
+ });
82
+ }
83
+ function proxyBody(proxy) {
84
+ if (typeof proxy === "boolean") return { useProxy: proxy };
85
+ return {
86
+ useProxy: true,
87
+ ...proxy.country === void 0 ? {} : { proxyCountry: proxy.country },
88
+ ...proxy.state === void 0 ? {} : { proxyState: proxy.state },
89
+ ...proxy.city === void 0 ? {} : { proxyCity: proxy.city }
90
+ };
91
+ }
92
+ function createBody(input) {
93
+ return {
94
+ timeoutMinutes: input.timeoutMinutes,
95
+ useStealth: input.stealth,
96
+ ...proxyBody(input.proxy),
97
+ solveCaptchas: input.solveCaptchas,
98
+ adblock: input.adblock,
99
+ trackers: input.trackers,
100
+ annoyances: input.annoyances,
101
+ acceptCookies: input.acceptCookies,
102
+ ...input.recording === void 0 ? {} : { enableWebRecording: input.recording },
103
+ ...input.screen === void 0 ? {} : { screen: input.screen },
104
+ ...input.region === void 0 ? {} : { region: input.region },
105
+ ...input.profile === void 0 ? {} : { profile: {
106
+ id: input.profile,
107
+ persistChanges: false
108
+ } },
109
+ ...input.extensions === void 0 ? {} : { extensionIds: input.extensions },
110
+ ...input.extra
111
+ };
112
+ }
113
+ function sessionIdOf(answer) {
114
+ if (typeof answer !== "object" || answer === null || !("id" in answer)) return void 0;
115
+ const id = answer.id;
116
+ return typeof id === "string" && id !== "" ? id : void 0;
117
+ }
118
+ function dashboardLink(url) {
119
+ if (url === void 0) return void 0;
120
+ return URL.parse(url)?.protocol === "https:" ? url : void 0;
121
+ }
122
+ const HandleSchema = z.object({ sessionId: z.string().min(1) });
123
+ const ActiveSessionsSchema = z.object({ totalCount: z.int().nonnegative() });
124
+ function activeLine(count) {
125
+ return count === 1 ? "1 active session" : `${String(count)} active sessions`;
126
+ }
127
+ function failureLine(error) {
128
+ return error instanceof Error ? error.message : String(error);
129
+ }
130
+ var plugin_default = definePlugin({
131
+ options,
132
+ settings,
133
+ serves: ["cdp"],
134
+ skill: "./skill",
135
+ initialize({ options }) {
136
+ checkEndpoints(options, [{
137
+ path: "apiUrl",
138
+ scheme: "https"
139
+ }]);
140
+ },
141
+ async check({ options }, ctx) {
142
+ let answer;
143
+ try {
144
+ answer = await createHyperbrowserApi(options).activeSessions(ctx.signal);
145
+ } catch (error) {
146
+ if (error instanceof PluginError && error.kind === "auth") return {
147
+ state: "warn",
148
+ lines: ["the API key was refused"]
149
+ };
150
+ return {
151
+ state: "warn",
152
+ lines: [failureLine(error)]
153
+ };
154
+ }
155
+ const count = ActiveSessionsSchema.safeParse(answer);
156
+ return {
157
+ state: "ok",
158
+ lines: ["the API key was accepted", ...count.success ? [activeLine(count.data.totalCount)] : []]
159
+ };
160
+ },
161
+ async acquire({ options, settings, session }) {
162
+ if (session.platform !== "web" || session.engine !== "chromium") {
163
+ const what = session.platform === "web" ? session.engine : session.platform;
164
+ throw new PluginError(`Hyperbrowser runs Chromium web sessions only, not ${what}.`, { kind: "refused" });
165
+ }
166
+ checkSettings(settings, session);
167
+ const created = await createHyperbrowserApi(options).createSession(createBody(settings));
168
+ const answer = HyperbrowserSessionSchema.safeParse(created);
169
+ if (!answer.success) {
170
+ const sessionId = sessionIdOf(created);
171
+ throw new PluginError(`Hyperbrowser answered a session without ${sessionId === void 0 ? "an id" : "a usable wsEndpoint"}.`, {
172
+ kind: "refused",
173
+ ...sessionId === void 0 ? {} : { handle: { sessionId } }
174
+ });
175
+ }
176
+ const { id, wsEndpoint, liveUrl, token, sessionUrl } = answer.data;
177
+ const link = dashboardLink(sessionUrl);
178
+ return {
179
+ handle: { sessionId: id },
180
+ protocol: "cdp",
181
+ url: wsEndpoint,
182
+ mask: [
183
+ wsEndpoint,
184
+ liveUrl,
185
+ token
186
+ ].filter((value) => value !== void 0),
187
+ ...link === void 0 ? {} : { link }
188
+ };
189
+ },
190
+ async release({ options, handle }, ctx) {
191
+ const parsed = HandleSchema.safeParse(handle);
192
+ if (!parsed.success) throw new PluginError("A Hyperbrowser handle names its session by sessionId.", { kind: "refused" });
193
+ await createHyperbrowserApi(options).stop(parsed.data.sessionId, ctx.signal);
194
+ }
195
+ });
196
+ export { plugin_default as t };
197
+
198
+ //# sourceMappingURL=plugin-6PEkbMWx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-6PEkbMWx.js","names":[],"sources":["../src/hyperbrowser-api.ts","../src/plugin.ts"],"sourcesContent":["import { createVendorApi, z } from '@vostride/agent-qa-plugin-kit'\n\nexport const HyperbrowserSessionSchema = z.object({\n id: z.string().min(1),\n wsEndpoint: z.string().min(1),\n liveUrl: z.string().min(1).optional(),\n token: z.string().min(1).optional(),\n sessionUrl: z.string().min(1).optional(),\n})\n\ninterface HyperbrowserApiInput {\n apiUrl: string\n apiKey: string\n}\n\nexport interface HyperbrowserApi {\n createSession(body: Record<string, unknown>): Promise<unknown>\n stop(id: string, signal?: AbortSignal): Promise<void>\n activeSessions(signal?: AbortSignal): Promise<unknown>\n}\n\nexport function createHyperbrowserApi(input: HyperbrowserApiInput): HyperbrowserApi {\n const api = createVendorApi({\n vendor: 'Hyperbrowser',\n baseUrl: input.apiUrl,\n headers: { 'x-api-key': input.apiKey },\n })\n\n return {\n async createSession(body) {\n const response = await api.send('POST', 'api/session', {\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) throw await api.failure(response, 'POST /api/session')\n return api.json(response)\n },\n\n async stop(id, signal) {\n const response = await api.send('PUT', `api/session/${encodeURIComponent(id)}/stop`, {\n headers: { accept: 'application/json' },\n signal,\n })\n if (response.ok || response.status === 404) {\n await response.body?.cancel()\n return\n }\n throw await api.failure(response, 'PUT /api/session/{id}/stop')\n },\n\n async activeSessions(signal) {\n const response = await api.send('GET', 'api/sessions?status=active&page=1', {\n headers: { accept: 'application/json' },\n signal,\n })\n if (!response.ok) throw await api.failure(response, 'GET /api/sessions')\n return api.json(response)\n },\n }\n}\n","import {\n checkEndpoints,\n credential,\n definePlugin,\n PluginError,\n z,\n type AcquireInput,\n} from '@vostride/agent-qa-plugin-kit'\nimport { createHyperbrowserApi, HyperbrowserSessionSchema } from './hyperbrowser-api.js'\n\nconst HYPERBROWSER_API_URL = 'https://api.hyperbrowser.ai'\n\nconst options = z.strictObject({\n apiKey: credential('The Hyperbrowser API key'),\n apiUrl: z\n .url()\n .default(HYPERBROWSER_API_URL)\n .describe(\"Hyperbrowser's API, https:// unless it points at this machine\"),\n})\n\nconst settings = z.strictObject({\n timeoutMinutes: z\n .int()\n .min(1)\n .max(720)\n .default(720)\n .describe('Minutes before Hyperbrowser stops the session'),\n recording: z\n .boolean()\n .optional()\n .describe(\"Hyperbrowser's web recording; unset keeps the account's default, which records\"),\n stealth: z\n .boolean()\n .default(false)\n .describe(\"Hyperbrowser's stealth mode, which changes what pages see\"),\n proxy: z\n .union([\n z.boolean(),\n z.strictObject({\n country: z.string().min(1).optional(),\n state: z.string().min(1).optional(),\n city: z.string().min(1).optional(),\n }),\n ])\n .default(false)\n .describe(\"Hyperbrowser's proxy, true or { country, state, city }\"),\n solveCaptchas: z.boolean().default(false).describe(\"Hyperbrowser's CAPTCHA solving\"),\n adblock: z.boolean().default(false).describe(\"Hyperbrowser's ad blocking\"),\n trackers: z.boolean().default(false).describe(\"Hyperbrowser's tracker blocking\"),\n annoyances: z.boolean().default(false).describe(\"Hyperbrowser's annoyance blocking\"),\n acceptCookies: z\n .boolean()\n .default(false)\n .describe(\"Hyperbrowser's automatic acceptance of cookie banners\"),\n screen: z\n .strictObject({ width: z.int().positive(), height: z.int().positive() })\n .optional()\n .describe('The browser screen size in pixels'),\n region: z.string().min(1).optional().describe('The Hyperbrowser region the session runs in'),\n profile: z\n .string()\n .min(1)\n .optional()\n .describe('A Hyperbrowser profile id loaded read-only; needs context: reuse on the remote'),\n extensions: z\n .array(z.string().min(1))\n .min(1)\n .optional()\n .describe('Hyperbrowser extension ids loaded into the browser'),\n extra: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Fields merged last into the session create request, overriding the rest'),\n})\n\ntype HyperbrowserSettings = z.output<typeof settings>\ntype Session = AcquireInput<unknown, unknown>['session']\n\nfunction checkSettings(input: HyperbrowserSettings, session: Session): void {\n if (input.profile !== undefined && (session.platform !== 'web' || session.context !== 'reuse')) {\n throw new PluginError(\n 'profile loads only into a reused context; set context: reuse on the remote.',\n { kind: 'refused', path: 'settings.profile' },\n )\n }\n}\n\nfunction proxyBody(proxy: HyperbrowserSettings['proxy']): Record<string, unknown> {\n if (typeof proxy === 'boolean') return { useProxy: proxy }\n return {\n useProxy: true,\n ...(proxy.country === undefined ? {} : { proxyCountry: proxy.country }),\n ...(proxy.state === undefined ? {} : { proxyState: proxy.state }),\n ...(proxy.city === undefined ? {} : { proxyCity: proxy.city }),\n }\n}\n\nfunction createBody(input: HyperbrowserSettings): Record<string, unknown> {\n return {\n timeoutMinutes: input.timeoutMinutes,\n useStealth: input.stealth,\n ...proxyBody(input.proxy),\n solveCaptchas: input.solveCaptchas,\n adblock: input.adblock,\n trackers: input.trackers,\n annoyances: input.annoyances,\n acceptCookies: input.acceptCookies,\n ...(input.recording === undefined ? {} : { enableWebRecording: input.recording }),\n ...(input.screen === undefined ? {} : { screen: input.screen }),\n ...(input.region === undefined ? {} : { region: input.region }),\n ...(input.profile === undefined\n ? {}\n : { profile: { id: input.profile, persistChanges: false } }),\n ...(input.extensions === undefined ? {} : { extensionIds: input.extensions }),\n ...input.extra,\n }\n}\n\nfunction sessionIdOf(answer: unknown): string | undefined {\n if (typeof answer !== 'object' || answer === null || !('id' in answer)) return undefined\n const id = answer.id\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\nfunction dashboardLink(url: string | undefined): string | undefined {\n if (url === undefined) return undefined\n return URL.parse(url)?.protocol === 'https:' ? url : undefined\n}\n\nconst HandleSchema = z.object({ sessionId: z.string().min(1) })\n\nconst ActiveSessionsSchema = z.object({ totalCount: z.int().nonnegative() })\n\nfunction activeLine(count: number): string {\n return count === 1 ? '1 active session' : `${String(count)} active sessions`\n}\n\nfunction failureLine(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nexport default definePlugin({\n options,\n settings,\n serves: ['cdp'],\n skill: './skill',\n\n initialize({ options }) {\n checkEndpoints(options, [{ path: 'apiUrl', scheme: 'https' }])\n },\n\n async check({ options }, ctx) {\n let answer: unknown\n try {\n answer = await createHyperbrowserApi(options).activeSessions(ctx.signal)\n } catch (error) {\n if (error instanceof PluginError && error.kind === 'auth') {\n return { state: 'warn', lines: ['the API key was refused'] }\n }\n return { state: 'warn', lines: [failureLine(error)] }\n }\n const count = ActiveSessionsSchema.safeParse(answer)\n return {\n state: 'ok',\n lines: [\n 'the API key was accepted',\n ...(count.success ? [activeLine(count.data.totalCount)] : []),\n ],\n }\n },\n\n async acquire({ options, settings, session }) {\n if (session.platform !== 'web' || session.engine !== 'chromium') {\n const what = session.platform === 'web' ? session.engine : session.platform\n throw new PluginError(`Hyperbrowser runs Chromium web sessions only, not ${what}.`, {\n kind: 'refused',\n })\n }\n checkSettings(settings, session)\n // The create is not cancelled: once Hyperbrowser has a session, the handle must come back to\n // be stopped, and the kit releases a handle that arrives after a cancel was answered.\n const created = await createHyperbrowserApi(options).createSession(createBody(settings))\n const answer = HyperbrowserSessionSchema.safeParse(created)\n if (!answer.success) {\n const sessionId = sessionIdOf(created)\n const missing = sessionId === undefined ? 'an id' : 'a usable wsEndpoint'\n throw new PluginError(`Hyperbrowser answered a session without ${missing}.`, {\n kind: 'refused',\n ...(sessionId === undefined ? {} : { handle: { sessionId } }),\n })\n }\n const { id, wsEndpoint, liveUrl, token, sessionUrl } = answer.data\n const link = dashboardLink(sessionUrl)\n return {\n handle: { sessionId: id },\n protocol: 'cdp' as const,\n url: wsEndpoint,\n mask: [wsEndpoint, liveUrl, token].filter((value) => value !== undefined),\n ...(link === undefined ? {} : { link }),\n }\n },\n\n async release({ options, handle }, ctx) {\n const parsed = HandleSchema.safeParse(handle)\n if (!parsed.success) {\n throw new PluginError('A Hyperbrowser handle names its session by sessionId.', {\n kind: 'refused',\n })\n }\n await createHyperbrowserApi(options).stop(parsed.data.sessionId, ctx.signal)\n },\n})\n"],"mappings":"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"}
package/dist/plugin.js ADDED
@@ -0,0 +1 @@
1
+ export { t as default } from "./plugin-6PEkbMWx.js";
@@ -0,0 +1,123 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "timeoutMinutes": {
6
+ "default": 720,
7
+ "description": "Minutes before Hyperbrowser stops the session",
8
+ "type": "integer",
9
+ "minimum": 1,
10
+ "maximum": 720
11
+ },
12
+ "recording": {
13
+ "description": "Hyperbrowser's web recording; unset keeps the account's default, which records",
14
+ "type": "boolean"
15
+ },
16
+ "stealth": {
17
+ "default": false,
18
+ "description": "Hyperbrowser's stealth mode, which changes what pages see",
19
+ "type": "boolean"
20
+ },
21
+ "proxy": {
22
+ "default": false,
23
+ "description": "Hyperbrowser's proxy, true or { country, state, city }",
24
+ "anyOf": [
25
+ {
26
+ "type": "boolean"
27
+ },
28
+ {
29
+ "type": "object",
30
+ "properties": {
31
+ "country": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ },
35
+ "state": {
36
+ "type": "string",
37
+ "minLength": 1
38
+ },
39
+ "city": {
40
+ "type": "string",
41
+ "minLength": 1
42
+ }
43
+ },
44
+ "additionalProperties": false
45
+ }
46
+ ]
47
+ },
48
+ "solveCaptchas": {
49
+ "default": false,
50
+ "description": "Hyperbrowser's CAPTCHA solving",
51
+ "type": "boolean"
52
+ },
53
+ "adblock": {
54
+ "default": false,
55
+ "description": "Hyperbrowser's ad blocking",
56
+ "type": "boolean"
57
+ },
58
+ "trackers": {
59
+ "default": false,
60
+ "description": "Hyperbrowser's tracker blocking",
61
+ "type": "boolean"
62
+ },
63
+ "annoyances": {
64
+ "default": false,
65
+ "description": "Hyperbrowser's annoyance blocking",
66
+ "type": "boolean"
67
+ },
68
+ "acceptCookies": {
69
+ "default": false,
70
+ "description": "Hyperbrowser's automatic acceptance of cookie banners",
71
+ "type": "boolean"
72
+ },
73
+ "screen": {
74
+ "description": "The browser screen size in pixels",
75
+ "type": "object",
76
+ "properties": {
77
+ "width": {
78
+ "type": "integer",
79
+ "exclusiveMinimum": 0,
80
+ "maximum": 9007199254740991
81
+ },
82
+ "height": {
83
+ "type": "integer",
84
+ "exclusiveMinimum": 0,
85
+ "maximum": 9007199254740991
86
+ }
87
+ },
88
+ "required": [
89
+ "width",
90
+ "height"
91
+ ],
92
+ "additionalProperties": false
93
+ },
94
+ "region": {
95
+ "description": "The Hyperbrowser region the session runs in",
96
+ "type": "string",
97
+ "minLength": 1
98
+ },
99
+ "profile": {
100
+ "description": "A Hyperbrowser profile id loaded read-only; needs context: reuse on the remote",
101
+ "type": "string",
102
+ "minLength": 1
103
+ },
104
+ "extensions": {
105
+ "description": "Hyperbrowser extension ids loaded into the browser",
106
+ "minItems": 1,
107
+ "type": "array",
108
+ "items": {
109
+ "type": "string",
110
+ "minLength": 1
111
+ }
112
+ },
113
+ "extra": {
114
+ "description": "Fields merged last into the session create request, overriding the rest",
115
+ "type": "object",
116
+ "propertyNames": {
117
+ "type": "string"
118
+ },
119
+ "additionalProperties": {}
120
+ }
121
+ },
122
+ "additionalProperties": false
123
+ }
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@vostride/agent-qa-plugin-hyperbrowser",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.2",
4
+ "private": false,
5
+ "description": "agent-qa plugin for Hyperbrowser: creates a Hyperbrowser browser session before a test connects and stops it after.",
6
+ "keywords": [
7
+ "agent-qa",
8
+ "agent-qa-plugin",
9
+ "hyperbrowser"
10
+ ],
11
+ "license": "FSL-1.1-ALv2",
12
+ "homepage": "https://github.com/vostride/agent-qa#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/vostride/agent-qa/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/vostride/agent-qa.git",
19
+ "directory": "plugins/hyperbrowser"
20
+ },
21
+ "engines": {
22
+ "node": ">=24"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "registry": "https://registry.npmjs.org/"
27
+ },
28
+ "type": "module",
29
+ "exports": {
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "skill",
35
+ "LICENSE.md",
36
+ "NOTICE.md"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsdown && node scripts/emit.mjs",
40
+ "test": "vitest run",
41
+ "typecheck": "tsc --noEmit",
42
+ "clean": "rm -rf dist .turbo"
43
+ },
44
+ "dependencies": {
45
+ "@vostride/agent-qa-plugin-kit": "0.3.2"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "~24.19.2",
49
+ "@vostride/agent-qa-arch": "0.3.2",
50
+ "tsdown": "~0.23.4",
51
+ "typescript": "~7.0.2",
52
+ "vitest": "~5.0.5"
53
+ },
54
+ "agent-qa": {
55
+ "plugin": {
56
+ "kind": "remote",
57
+ "runs": "process",
58
+ "protocol": 1,
59
+ "entry": "./dist/main.js",
60
+ "serves": [
61
+ "cdp"
62
+ ],
63
+ "methods": [
64
+ "check"
65
+ ],
66
+ "options": "./dist/options.schema.json",
67
+ "settings": "./dist/settings.schema.json",
68
+ "skill": "./skill"
69
+ }
70
+ }
71
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: agent-qa-plugin-hyperbrowser
3
+ description: Use when configuring agent-qa web tests to run in Hyperbrowser cloud browsers through the Hyperbrowser plugin, or when a Hyperbrowser remote is refused.
4
+ ---
5
+
6
+ # Hyperbrowser remotes for agent-qa
7
+
8
+ The Hyperbrowser plugin gives an agent-qa web test a Hyperbrowser browser session. Before the test
9
+ connects it creates a session through Hyperbrowser's API, and after the test it stops the session.
10
+ Hyperbrowser runs Chromium only. Stealth, proxies, CAPTCHA solving, ad, tracker and annoyance
11
+ blocking and cookie auto-accept are off unless a setting turns them on, so a test sees the app the
12
+ way its users do.
13
+
14
+ ## Declare the plugin and a remote
15
+
16
+ ```yaml
17
+ plugins:
18
+ - name: hyperbrowser
19
+ package: "@vostride/agent-qa-plugin-hyperbrowser"
20
+ options:
21
+ apiKey: "{{secret:HYPERBROWSER_API_KEY}}"
22
+
23
+ registry:
24
+ remotes:
25
+ - name: hyperbrowser
26
+ plugin: hyperbrowser
27
+ - name: hyperbrowser-unrecorded
28
+ plugin: hyperbrowser
29
+ settings:
30
+ recording: false
31
+ ```
32
+
33
+ - The `plugins` entry holds the account: `apiKey`, which is required, and `apiUrl`, which defaults
34
+ to `https://api.hyperbrowser.ai`. Write the key as a `{{secret:<name>}}` reference to a name in
35
+ the file `workspace.secretsFile` names. Never write the key itself in the config.
36
+ - The remote holds one place: `plugin` names the `plugins` entry, and `settings` hold that place's
37
+ Hyperbrowser settings.
38
+ - `use.browser.remote: hyperbrowser` in the config, a suite or a test runs the test there.
39
+ - Install the package at the exact version of agent-qa the project runs:
40
+ `npm install --save-dev --save-exact @vostride/agent-qa-plugin-hyperbrowser@<agent-qa version>`.
41
+ `agent-qa plugins list` prints that line when the package is missing or the version differs.
42
+
43
+ ## Settings
44
+
45
+ - `timeoutMinutes` (default `720`, 1 to 720): minutes before Hyperbrowser stops the session. It
46
+ overrides the team default in Hyperbrowser's settings. The plugin never asks for keep-alive, so
47
+ Hyperbrowser also stops a session whose connection drops, and agent-qa stops every session after
48
+ its test. A session whose create was in flight when the job was killed was never connected, so
49
+ it runs until `timeoutMinutes`.
50
+ - `recording` (unset by default): Hyperbrowser's web recording. Unset keeps the account's default,
51
+ which records. `recording: false` turns it off.
52
+ - `stealth`, `solveCaptchas`, `adblock`, `trackers`, `annoyances`, `acceptCookies` (each default
53
+ `false`): Hyperbrowser's stealth mode, CAPTCHA solving, ad, tracker and annoyance blocking and
54
+ automatic acceptance of cookie banners.
55
+ - `proxy` (default `false`): `true`, or `{ country, state, city }` with any of the three. Proxies
56
+ and CAPTCHA solving need a paid Hyperbrowser plan.
57
+ - `screen` (`{ width, height }` in pixels), `region` (for example `us-east` or `europe-west`),
58
+ `extensions` (extension ids): Hyperbrowser's own create fields, sent only when set.
59
+ - `profile`: a Hyperbrowser profile id. It loads read-only: nothing the test does is saved back. It
60
+ needs `context: reuse` on the remote, because the profile lives in the browser's first context:
61
+
62
+ ```yaml
63
+ - name: hyperbrowser-signed-in
64
+ plugin: hyperbrowser
65
+ context: reuse
66
+ settings:
67
+ profile: <Hyperbrowser profile id>
68
+ ```
69
+
70
+ - `extra`: any other Hyperbrowser request field. It is merged last, so it overrides the settings
71
+ above. Video replay is `extra: { enableVideoWebRecording: true }`, and needs recording on.
72
+ Enterprise proxy servers go here as `proxyServer`, `proxyServerUsername` and
73
+ `proxyServerPassword`; write the password as a `{{secret:<name>}}` reference.
74
+
75
+ ## When a remote is refused
76
+
77
+ - `auth`: Hyperbrowser refused the API key. Check the secret's value.
78
+ - `capacity`: Hyperbrowser answered 429. The account has no free sessions, or is rate limited.
79
+ Wait, or lower the run's concurrency.
80
+ - `refused` with `Hyperbrowser answered 403 to POST /api/session: <reason>`: a feature the remote
81
+ turns on is not on the plan, for example `Proxy usage requires a paid plan`. Turn that setting
82
+ off, or change the plan.
83
+ - `refused` with path `settings.profile`: the remote sets `profile` without `context: reuse`. Add
84
+ `context: reuse` to the remote.
85
+ - `refused` with path `settings.timeoutMinutes`: the lifetime is outside 1 to 720. Set a value in
86
+ that range, or leave it out for 720.
87
+ - `refused` naming a browser or platform: Hyperbrowser runs Chromium web tests only. Use
88
+ `use.browser.name: chromium`, or another remote.
89
+ - `refused` at `options.apiUrl`: the endpoint must use `https://` with no user or password, unless
90
+ it points at this machine.
91
+ - `unreachable`: Hyperbrowser could not be reached. Check the network and `apiUrl`.
92
+
93
+ ## Recording
94
+
95
+ Hyperbrowser records sessions by default and keeps the replay on its dashboard, behind its login.
96
+ agent-qa's masking does not reach that replay, so a secret a test types may show in it. Set
97
+ `recording: false` on the remote to turn recording off. The session link opens the session's page
98
+ in Hyperbrowser's dashboard. Hyperbrowser's live view URL and the session token let anyone who has
99
+ them watch and drive the session, so agent-qa masks both and never shows them as the link.
100
+
101
+ ## Check and describe
102
+
103
+ - `agent-qa doctor` runs the plugin's account check. It reads the active sessions to prove the key,
104
+ reports how many there are, and opens none.
105
+ - `agent-qa plugins describe hyperbrowser --json` lists every option and setting with its type,
106
+ default and description.