browserscale-ts 1.7.0 → 1.8.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/README.md +136 -78
- package/dist/browser.js +1 -1
- package/dist/browserscale.browser.js +427 -162
- package/dist/client.d.ts +241 -62
- package/dist/client.js +312 -83
- package/dist/defaults.d.ts +11 -9
- package/dist/defaults.js +15 -12
- package/dist/dom-mirror.d.ts +27 -0
- package/dist/dom-mirror.js +27 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +37 -0
- package/dist/gen/wrc_pb.d.ts +348 -91
- package/dist/gen/wrc_pb.js +53 -58
- package/dist/index.d.ts +8 -5
- package/dist/index.js +8 -5
- package/dist/internal/convert.js +1 -0
- package/dist/locator.d.ts +12 -11
- package/dist/locator.js +14 -22
- package/dist/network-capture.d.ts +3 -2
- package/dist/network-capture.js +3 -2
- package/dist/options.d.ts +1 -1
- package/dist/scripts.d.ts +4 -3
- package/dist/scripts.js +4 -3
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -3,8 +3,7 @@ import { create } from "@bufbuild/protobuf";
|
|
|
3
3
|
import { Browser,
|
|
4
4
|
// request / response schemas
|
|
5
5
|
SetProxyRequestSchema, GetPagesRequestSchema, NavigateRequestSchema, LoadHTMLRequestSchema, EvaluateRequestSchema, WaitForAnyParamsSchema, WaitConditionSchema, ClickRequestSchema, FillRequestSchema, MoveToRequestSchema, ScrollToRequestSchema, DragRequestSchema, SelectOptionRequestSchema, GetDOMRequestSchema, GetDOMHashRequestSchema, GetObservationRequestSchema, ScreenshotRequestSchema, ReadCanvasRequestSchema, SetBlockListRequestSchema, SetStaticPathsRequestSchema, WaitForAnyRequestRequestSchema, WaitForAnyResponseRequestSchema, ModifyRequestRequestSchema, GetCookiesRequestSchema, SetCookiesRequestSchema, ClearCookiesRequestSchema, GetStorageRequestSchema, SetStorageRequestSchema, ClearStorageRequestSchema, GetAuthSessionRequestSchema, SetAuthSessionRequestSchema, InspectAtPositionRequestSchema, HighlightNodeRequestSchema, InsertTextRequestSchema, TypeRequestSchema, PressKeyRequestSchema, ReleaseKeyRequestSchema, GetSelectionRequestSchema, SolveCaptchaRequestSchema, GetStreamConfigRequestSchema, StartStreamRequestSchema, StopStreamRequestSchema, AddReactionRequestSchema, RemoveReactionRequestSchema, ListReactionsRequestSchema, StartNetworkCaptureRequestSchema, StopNetworkCaptureRequestSchema, StartDomMirrorRequestSchema, StopDomMirrorRequestSchema, GetDomChildrenRequestSchema, ReleaseDomSubtreeRequestSchema, RevealDomNodeRequestSchema, GetDomRevisionRequestSchema, StreamDomEventsRequestSchema, StreamNetworkExchangesRequestSchema, RunScriptRequestSchema, StartScriptRequestSchema, StopScriptsRequestSchema, ListScriptRunsRequestSchema, StreamScriptEventsRequestSchema, } from "./gen/wrc_pb.js";
|
|
6
|
-
import {
|
|
7
|
-
import { BrowserScaleError } from "./errors.js";
|
|
6
|
+
import { BrowserScaleError, CommandError, throwCommandError } from "./errors.js";
|
|
8
7
|
import { NetworkCapture } from "./network-capture.js";
|
|
9
8
|
import { ScriptFollow, ScriptRun, scriptLogEntryFromProto, } from "./scripts.js";
|
|
10
9
|
import { DomMirror, } from "./dom-mirror.js";
|
|
@@ -13,9 +12,7 @@ import { authSessionFromProto, authSessionToProto, cookieParamFromProto, cookieP
|
|
|
13
12
|
* CloudBrowser is the SDK-side handle for an active browserscale browser session.
|
|
14
13
|
*
|
|
15
14
|
* One CloudBrowser corresponds to exactly one browser context, which
|
|
16
|
-
* always has at least one page
|
|
17
|
-
* primary page server-side — the proto's page_id field is currently
|
|
18
|
-
* ignored server-side, so the SDK never sets it.
|
|
15
|
+
* always has at least one page; its commands act on the primary page.
|
|
19
16
|
*
|
|
20
17
|
* Construct via rentBrowser() / createWebSocketBrowser() — never
|
|
21
18
|
* directly.
|
|
@@ -52,7 +49,8 @@ export class CloudBrowser {
|
|
|
52
49
|
* {@link createWebSocketBrowser} the rental stays untouched; only the
|
|
53
50
|
* transport is closed.
|
|
54
51
|
*
|
|
55
|
-
*
|
|
52
|
+
* Rejects only when the stop API or the transport close fails. The session is
|
|
53
|
+
* released either way; retrying a stop is safe.
|
|
56
54
|
*
|
|
57
55
|
* @example
|
|
58
56
|
* await browser.stopBrowser();
|
|
@@ -77,7 +75,9 @@ export class CloudBrowser {
|
|
|
77
75
|
* @param proxyUsername - proxy auth user; empty for unauthenticated proxies
|
|
78
76
|
* @param proxyPassword - proxy auth password; empty for unauthenticated proxies
|
|
79
77
|
*
|
|
80
|
-
*
|
|
78
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
79
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
80
|
+
* are no error codes to branch on.
|
|
81
81
|
*
|
|
82
82
|
* @example
|
|
83
83
|
* await browser.setProxy("proxy.example.com", 8080, "user", "pass");
|
|
@@ -95,7 +95,8 @@ export class CloudBrowser {
|
|
|
95
95
|
if (proxyPassword)
|
|
96
96
|
req.proxyPassword = proxyPassword;
|
|
97
97
|
}
|
|
98
|
-
await this.client.setProxy(req);
|
|
98
|
+
const res = await this.client.setProxy(req);
|
|
99
|
+
throwCommandError("setProxy", res.error);
|
|
99
100
|
}
|
|
100
101
|
/**
|
|
101
102
|
* Returns all open pages (tabs and popups) for this session's browser
|
|
@@ -107,7 +108,9 @@ export class CloudBrowser {
|
|
|
107
108
|
*
|
|
108
109
|
* @returns PageInfo[] for every page currently open in the context
|
|
109
110
|
*
|
|
110
|
-
*
|
|
111
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
112
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
113
|
+
* are no error codes to branch on.
|
|
111
114
|
*
|
|
112
115
|
* @example
|
|
113
116
|
* const pages = await browser.getPages();
|
|
@@ -118,6 +121,7 @@ export class CloudBrowser {
|
|
|
118
121
|
sessionId: this.sessionId,
|
|
119
122
|
apiKey: this.apiKey,
|
|
120
123
|
}));
|
|
124
|
+
throwCommandError("getPages", resp.error);
|
|
121
125
|
return resp.pages.map(pageInfoFromProto);
|
|
122
126
|
}
|
|
123
127
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -136,7 +140,16 @@ export class CloudBrowser {
|
|
|
136
140
|
* @returns NavigateResult with the final resolved URL and the frameId
|
|
137
141
|
* of the main frame after navigation
|
|
138
142
|
*
|
|
139
|
-
* @throws
|
|
143
|
+
* @throws timeout - nothing committed before the deadline; the page may still
|
|
144
|
+
* be loading, so a longer timeout can be the whole fix
|
|
145
|
+
* @throws net_error - the URL never loaded: DNS, TLS, a refused connection, or
|
|
146
|
+
* a proxy that could not reach it. The message carries the underlying net
|
|
147
|
+
* error name, which is what separates a bad proxy from a bad host - worth
|
|
148
|
+
* logging, since the two need different fixes
|
|
149
|
+
* @throws crashed - the renderer died mid-navigation; the page is unusable and
|
|
150
|
+
* has to be navigated again
|
|
151
|
+
*
|
|
152
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
140
153
|
*
|
|
141
154
|
* @example
|
|
142
155
|
* await browser.navigate("https://example.com");
|
|
@@ -150,6 +163,7 @@ export class CloudBrowser {
|
|
|
150
163
|
if (opts?.timeoutMs)
|
|
151
164
|
req.timeout = opts.timeoutMs;
|
|
152
165
|
const resp = await this.client.navigate(req);
|
|
166
|
+
throwCommandError("navigate", resp.error);
|
|
153
167
|
return { frameId: resp.frameId, url: resp.url };
|
|
154
168
|
}
|
|
155
169
|
/**
|
|
@@ -164,7 +178,11 @@ export class CloudBrowser {
|
|
|
164
178
|
* @param html - response body to serve
|
|
165
179
|
* @param opts - optional headers and statusCode (default 200)
|
|
166
180
|
*
|
|
167
|
-
* @throws
|
|
181
|
+
* @throws timeout - the page never requested the URL, so the prepared
|
|
182
|
+
* response had nobody to hand it to; usually the navigation was cancelled
|
|
183
|
+
* or redirected away before reaching it
|
|
184
|
+
*
|
|
185
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
168
186
|
*
|
|
169
187
|
* @example
|
|
170
188
|
* await browser.loadHTML("https://example.com", "<h1>hi</h1>");
|
|
@@ -180,7 +198,8 @@ export class CloudBrowser {
|
|
|
180
198
|
});
|
|
181
199
|
if (opts?.statusCode)
|
|
182
200
|
req.statusCode = opts.statusCode;
|
|
183
|
-
await this.client.loadHTML(req);
|
|
201
|
+
const res = await this.client.loadHTML(req);
|
|
202
|
+
throwCommandError("loadHTML", res.error);
|
|
184
203
|
}
|
|
185
204
|
// ──────────────────────────────────────────────────────────────────
|
|
186
205
|
// Evaluation
|
|
@@ -197,16 +216,34 @@ export class CloudBrowser {
|
|
|
197
216
|
* The generic T is a TypeScript hint only — there is no runtime
|
|
198
217
|
* validation that the JS expression actually returned that type.
|
|
199
218
|
*
|
|
219
|
+
* A falsy answer and a broken expression are different outcomes. Returning
|
|
220
|
+
* null, false or undefined is a successful evaluation and resolves normally;
|
|
221
|
+
* an expression that throws or will not compile rejects with a
|
|
222
|
+
* {@link CommandError}, so a typo can never read as "the page says null".
|
|
223
|
+
*
|
|
200
224
|
* @param expression - JavaScript expression evaluated in the main frame
|
|
201
225
|
*
|
|
202
226
|
* @returns EvaluateResult with either value (non-Element) or element
|
|
203
227
|
* metadata (Element)
|
|
204
228
|
*
|
|
205
|
-
* @throws
|
|
229
|
+
* @throws {@link CommandError} - code `"threw"` (the expression raised; the
|
|
230
|
+
* message carries the exception text), `"not_run"` (it could not be
|
|
231
|
+
* compiled, or execution never started), `"aborted"` (the browser stopped
|
|
232
|
+
* execution) or `"no_context"` (the frame had no live script context)
|
|
206
233
|
*
|
|
207
234
|
* @example
|
|
208
235
|
* const res = await browser.evaluate<string>("document.title");
|
|
209
236
|
* console.log(res.value);
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* // Telling a false answer from a broken expression.
|
|
240
|
+
* try {
|
|
241
|
+
* const res = await browser.evaluate<boolean>("window.__ready === true");
|
|
242
|
+
* if (!res.value) { /* legitimately not ready yet *\/ }
|
|
243
|
+
* } catch (e) {
|
|
244
|
+
* if (e instanceof CommandError) throw new Error(`expression broken: ${e.message}`);
|
|
245
|
+
* throw e;
|
|
246
|
+
* }
|
|
210
247
|
*/
|
|
211
248
|
async evaluate(expression) {
|
|
212
249
|
return this._evaluate("", expression);
|
|
@@ -239,6 +276,15 @@ export class CloudBrowser {
|
|
|
239
276
|
if (frameId)
|
|
240
277
|
req.frameId = frameId;
|
|
241
278
|
const resp = await this.client.evaluate(req);
|
|
279
|
+
// An expression that threw arrives as success=false in the payload, not as a
|
|
280
|
+
// transport error, so it is raised here rather than by the call above.
|
|
281
|
+
if (resp.error) {
|
|
282
|
+
throw new CommandError({
|
|
283
|
+
command: "evaluate",
|
|
284
|
+
code: resp.error.code,
|
|
285
|
+
message: resp.error.message,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
242
288
|
let value = null;
|
|
243
289
|
if (resp.result !== "") {
|
|
244
290
|
try {
|
|
@@ -249,6 +295,7 @@ export class CloudBrowser {
|
|
|
249
295
|
}
|
|
250
296
|
}
|
|
251
297
|
return {
|
|
298
|
+
success: resp.success,
|
|
252
299
|
value: value,
|
|
253
300
|
backendNodeId: resp.backendNodeId,
|
|
254
301
|
isVisible: resp.isVisible,
|
|
@@ -340,7 +387,8 @@ export class CloudBrowser {
|
|
|
340
387
|
sessionId: this.sessionId,
|
|
341
388
|
apiKey: this.apiKey,
|
|
342
389
|
conditions: pbConds,
|
|
343
|
-
|
|
390
|
+
// Left unset unless the caller passed one, so the API applies its default.
|
|
391
|
+
timeout: opts?.timeoutMs,
|
|
344
392
|
});
|
|
345
393
|
if (frameId)
|
|
346
394
|
req.frameId = frameId;
|
|
@@ -660,7 +708,9 @@ export class CloudBrowser {
|
|
|
660
708
|
* @returns DOMResult with the JSON string in `.dom` (the `.hash` field
|
|
661
709
|
* is populated by {@link getDOMHash}, not by this call)
|
|
662
710
|
*
|
|
663
|
-
*
|
|
711
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
712
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
713
|
+
* are no error codes to branch on.
|
|
664
714
|
*
|
|
665
715
|
* @example
|
|
666
716
|
* const { dom } = await browser.getDOM();
|
|
@@ -675,6 +725,7 @@ export class CloudBrowser {
|
|
|
675
725
|
if (opts?.depth !== undefined)
|
|
676
726
|
req.depth = opts.depth;
|
|
677
727
|
const resp = await this.client.getDOM(req);
|
|
728
|
+
throwCommandError("getDOM", resp.error);
|
|
678
729
|
return { hash: "", dom: resp.dom };
|
|
679
730
|
}
|
|
680
731
|
/**
|
|
@@ -689,7 +740,9 @@ export class CloudBrowser {
|
|
|
689
740
|
*
|
|
690
741
|
* @returns 16-char hex string (the first 8 bytes of sha256 of the DOM JSON)
|
|
691
742
|
*
|
|
692
|
-
*
|
|
743
|
+
* Rejects only on a transport failure. The hash is computed from a serialized
|
|
744
|
+
* tree, so there is no semantic failure of its own and no error codes to branch
|
|
745
|
+
* on.
|
|
693
746
|
*
|
|
694
747
|
* @example
|
|
695
748
|
* const hash = await browser.getDOMHash();
|
|
@@ -738,7 +791,11 @@ export class CloudBrowser {
|
|
|
738
791
|
*
|
|
739
792
|
* @returns the observation in the requested format, ready to hand to a model
|
|
740
793
|
*
|
|
741
|
-
* @throws
|
|
794
|
+
* @throws not_found - the requested scope root is not on the page, so there
|
|
795
|
+
* was nothing to observe. Distinct from an observation that comes back
|
|
796
|
+
* empty, which means the scope exists and holds nothing worth reporting
|
|
797
|
+
*
|
|
798
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
742
799
|
*
|
|
743
800
|
* @example
|
|
744
801
|
* const obs = await browser.getObservation();
|
|
@@ -771,6 +828,7 @@ export class CloudBrowser {
|
|
|
771
828
|
if (opts?.frameId !== undefined)
|
|
772
829
|
req.frameId = opts.frameId;
|
|
773
830
|
const resp = await this.client.getObservation(req);
|
|
831
|
+
throwCommandError("getObservation", resp.error);
|
|
774
832
|
return resp.observation;
|
|
775
833
|
}
|
|
776
834
|
/**
|
|
@@ -787,7 +845,11 @@ export class CloudBrowser {
|
|
|
787
845
|
* @returns ScreenshotResult with the base64 image in `dataBase64` and the
|
|
788
846
|
* physical pixel `width`/`height`
|
|
789
847
|
*
|
|
790
|
-
* @throws
|
|
848
|
+
* @throws capture_failed - the page had no frame to copy. A page that has not
|
|
849
|
+
* produced one yet, or is not being composited at the moment, has nothing to
|
|
850
|
+
* hand over; retrying after it renders usually works
|
|
851
|
+
*
|
|
852
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
791
853
|
*
|
|
792
854
|
* @example
|
|
793
855
|
* const shot = await browser.screenshot({ format: "png" });
|
|
@@ -803,6 +865,7 @@ export class CloudBrowser {
|
|
|
803
865
|
if (opts?.quality !== undefined)
|
|
804
866
|
req.quality = opts.quality;
|
|
805
867
|
const resp = await this.client.screenshot(req);
|
|
868
|
+
throwCommandError("screenshot", resp.error);
|
|
806
869
|
return {
|
|
807
870
|
dataBase64: resp.dataBase64,
|
|
808
871
|
width: resp.width,
|
|
@@ -826,11 +889,15 @@ export class CloudBrowser {
|
|
|
826
889
|
* canvas `width`/`height`, resolved `frameId`/`backendNodeId` and the
|
|
827
890
|
* `originClean` flag
|
|
828
891
|
*
|
|
829
|
-
* @throws
|
|
830
|
-
* @throws
|
|
831
|
-
* @throws
|
|
832
|
-
*
|
|
833
|
-
*
|
|
892
|
+
* @throws not_found - no element matched the locator
|
|
893
|
+
* @throws not_element - the expression was truthy but did not yield an element
|
|
894
|
+
* @throws not_readable - the target was found but is not a readable canvas
|
|
895
|
+
*
|
|
896
|
+
* A target that is empty, uses `at(x, y)` or names several things at once is
|
|
897
|
+
* rejected before anything is sent. A closed page or a frame that is gone is a
|
|
898
|
+
* transport failure rather than a code.
|
|
899
|
+
*
|
|
900
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
834
901
|
*
|
|
835
902
|
* @example
|
|
836
903
|
* const res = await browser.readCanvas(css("#game canvas"));
|
|
@@ -862,6 +929,7 @@ export class CloudBrowser {
|
|
|
862
929
|
req.sh = opts.sh;
|
|
863
930
|
}
|
|
864
931
|
const resp = await this.client.readCanvas(req);
|
|
932
|
+
throwCommandError("readCanvas", resp.error);
|
|
865
933
|
return {
|
|
866
934
|
success: resp.success,
|
|
867
935
|
frameId: resp.frameId,
|
|
@@ -885,7 +953,9 @@ export class CloudBrowser {
|
|
|
885
953
|
*
|
|
886
954
|
* @param patterns - URL wildcards to block; empty array clears the list
|
|
887
955
|
*
|
|
888
|
-
*
|
|
956
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
957
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
958
|
+
* are no error codes to branch on.
|
|
889
959
|
*
|
|
890
960
|
* @example
|
|
891
961
|
* await browser.setBlockList([
|
|
@@ -894,11 +964,12 @@ export class CloudBrowser {
|
|
|
894
964
|
* ]);
|
|
895
965
|
*/
|
|
896
966
|
async setBlockList(patterns) {
|
|
897
|
-
await this.client.setBlockList(create(SetBlockListRequestSchema, {
|
|
967
|
+
const res = await this.client.setBlockList(create(SetBlockListRequestSchema, {
|
|
898
968
|
sessionId: this.sessionId,
|
|
899
969
|
apiKey: this.apiKey,
|
|
900
970
|
patterns,
|
|
901
971
|
}));
|
|
972
|
+
throwCommandError("setBlockList", res.error);
|
|
902
973
|
}
|
|
903
974
|
/**
|
|
904
975
|
* Configures the session to serve cached static responses for requests
|
|
@@ -912,18 +983,21 @@ export class CloudBrowser {
|
|
|
912
983
|
* @param blobName - server-side identifier of the snapshot to serve from
|
|
913
984
|
* @param patterns - URL wildcards to redirect to the cache; empty disables
|
|
914
985
|
*
|
|
915
|
-
*
|
|
986
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
987
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
988
|
+
* are no error codes to branch on.
|
|
916
989
|
*
|
|
917
990
|
* @example
|
|
918
991
|
* await browser.setStaticPaths("snap-2026-05", ["*.example.com/*"]);
|
|
919
992
|
*/
|
|
920
993
|
async setStaticPaths(blobName, patterns) {
|
|
921
|
-
await this.client.setStaticPaths(create(SetStaticPathsRequestSchema, {
|
|
994
|
+
const res = await this.client.setStaticPaths(create(SetStaticPathsRequestSchema, {
|
|
922
995
|
sessionId: this.sessionId,
|
|
923
996
|
apiKey: this.apiKey,
|
|
924
997
|
blobName,
|
|
925
998
|
patterns,
|
|
926
999
|
}));
|
|
1000
|
+
throwCommandError("setStaticPaths", res.error);
|
|
927
1001
|
}
|
|
928
1002
|
/**
|
|
929
1003
|
* Blocks until the next request whose URL matches one of the supplied
|
|
@@ -940,7 +1014,12 @@ export class CloudBrowser {
|
|
|
940
1014
|
* (the captured method/URL/headers/body; null if intercepted with
|
|
941
1015
|
* no body)
|
|
942
1016
|
*
|
|
943
|
-
* @throws
|
|
1017
|
+
* @throws timeout - no request matched any pattern before the deadline.
|
|
1018
|
+
* Nothing occurring is an answer, and it stays distinguishable from a
|
|
1019
|
+
* connection that died on the way. Supplying no patterns is a caller mistake
|
|
1020
|
+
* rather than an outcome, and rejects separately
|
|
1021
|
+
*
|
|
1022
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
944
1023
|
*
|
|
945
1024
|
* @example
|
|
946
1025
|
* const { index, request } = await browser.waitForAnyRequest(
|
|
@@ -963,6 +1042,7 @@ export class CloudBrowser {
|
|
|
963
1042
|
if (opts?.timeoutMs)
|
|
964
1043
|
req.timeout = opts.timeoutMs;
|
|
965
1044
|
const resp = await this.client.waitForAnyRequest(req);
|
|
1045
|
+
throwCommandError("waitForAnyRequest", resp.error);
|
|
966
1046
|
return {
|
|
967
1047
|
index: resp.index,
|
|
968
1048
|
request: interceptedRequestFromProto(resp.request),
|
|
@@ -981,6 +1061,13 @@ export class CloudBrowser {
|
|
|
981
1061
|
* @returns object with `index` (matched pattern index) and `response`
|
|
982
1062
|
* (the captured status/headers/body; null if no body was returned)
|
|
983
1063
|
*
|
|
1064
|
+
* @throws timeout - no response matched any pattern before the deadline.
|
|
1065
|
+
* Nothing occurring is an answer, and it stays distinguishable from a
|
|
1066
|
+
* connection that died on the way. Supplying no patterns is a caller mistake
|
|
1067
|
+
* rather than an outcome, and rejects separately
|
|
1068
|
+
*
|
|
1069
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1070
|
+
*
|
|
984
1071
|
* @example
|
|
985
1072
|
* const { index, response } = await browser.waitForAnyResponse(
|
|
986
1073
|
* [{ url: "*\/api/login" }],
|
|
@@ -1002,6 +1089,7 @@ export class CloudBrowser {
|
|
|
1002
1089
|
if (opts?.timeoutMs)
|
|
1003
1090
|
req.timeout = opts.timeoutMs;
|
|
1004
1091
|
const resp = await this.client.waitForAnyResponse(req);
|
|
1092
|
+
throwCommandError("waitForAnyResponse", resp.error);
|
|
1005
1093
|
return {
|
|
1006
1094
|
index: resp.index,
|
|
1007
1095
|
response: interceptedResponseFromProto(resp.response),
|
|
@@ -1023,7 +1111,10 @@ export class CloudBrowser {
|
|
|
1023
1111
|
* were actually sent on the wire after modifications were applied;
|
|
1024
1112
|
* null when no request payload was reported
|
|
1025
1113
|
*
|
|
1026
|
-
* @throws
|
|
1114
|
+
* @throws timeout - no matching request appeared before the deadline, so
|
|
1115
|
+
* nothing was modified
|
|
1116
|
+
*
|
|
1117
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1027
1118
|
*
|
|
1028
1119
|
* @example
|
|
1029
1120
|
* const req = await browser.modifyRequest("*\/api/me", {
|
|
@@ -1047,6 +1138,7 @@ export class CloudBrowser {
|
|
|
1047
1138
|
if (opts?.timeoutMs)
|
|
1048
1139
|
req.timeout = opts.timeoutMs;
|
|
1049
1140
|
const resp = await this.client.modifyRequest(req);
|
|
1141
|
+
throwCommandError("modifyRequest", resp.error);
|
|
1050
1142
|
return interceptedRequestFromProto(resp.request);
|
|
1051
1143
|
}
|
|
1052
1144
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -1076,7 +1168,9 @@ export class CloudBrowser {
|
|
|
1076
1168
|
* @returns NetworkCapture handle for stopping the capture and inspecting how
|
|
1077
1169
|
* it ended
|
|
1078
1170
|
*
|
|
1079
|
-
*
|
|
1171
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1172
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1173
|
+
* are no error codes to branch on.
|
|
1080
1174
|
*
|
|
1081
1175
|
* @example
|
|
1082
1176
|
* const capture = await browser.captureNetwork(
|
|
@@ -1112,29 +1206,34 @@ export class CloudBrowser {
|
|
|
1112
1206
|
*
|
|
1113
1207
|
* @param opts - which requests to capture and whether to keep bodies
|
|
1114
1208
|
*
|
|
1115
|
-
*
|
|
1209
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1210
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1211
|
+
* are no error codes to branch on.
|
|
1116
1212
|
*/
|
|
1117
1213
|
async startNetworkCapture(opts) {
|
|
1118
|
-
await this.client.startNetworkCapture(create(StartNetworkCaptureRequestSchema, {
|
|
1214
|
+
const res = await this.client.startNetworkCapture(create(StartNetworkCaptureRequestSchema, {
|
|
1119
1215
|
sessionId: this.sessionId,
|
|
1120
1216
|
apiKey: this.apiKey,
|
|
1121
1217
|
patterns: opts.patterns ?? [],
|
|
1122
1218
|
bodies: opts.bodies ?? "none",
|
|
1123
1219
|
bodyPatterns: opts.bodyPatterns ?? [],
|
|
1124
1220
|
}));
|
|
1221
|
+
throwCommandError("startNetworkCapture", res.error);
|
|
1125
1222
|
}
|
|
1126
1223
|
/**
|
|
1127
1224
|
* Disarms the session's capture.
|
|
1128
1225
|
*
|
|
1129
1226
|
* @returns whether a capture was running
|
|
1130
1227
|
*
|
|
1131
|
-
*
|
|
1228
|
+
* Rejects only on a transport failure. Stopping a capture that is not running
|
|
1229
|
+
* is a no-op rather than a failure, so there are no error codes to branch on.
|
|
1132
1230
|
*/
|
|
1133
1231
|
async stopNetworkCapture() {
|
|
1134
1232
|
const resp = await this.client.stopNetworkCapture(create(StopNetworkCaptureRequestSchema, {
|
|
1135
1233
|
sessionId: this.sessionId,
|
|
1136
1234
|
apiKey: this.apiKey,
|
|
1137
1235
|
}));
|
|
1236
|
+
throwCommandError("stopNetworkCapture", resp.error);
|
|
1138
1237
|
return resp.stopped;
|
|
1139
1238
|
}
|
|
1140
1239
|
/**
|
|
@@ -1151,7 +1250,8 @@ export class CloudBrowser {
|
|
|
1151
1250
|
* @returns NetworkCapture attached to whatever capture is running; onExchange
|
|
1152
1251
|
* simply never fires when none is
|
|
1153
1252
|
*
|
|
1154
|
-
*
|
|
1253
|
+
* Rejects only on a transport failure: opening the subscription has no semantic
|
|
1254
|
+
* failure of its own.
|
|
1155
1255
|
*/
|
|
1156
1256
|
async streamNetworkExchanges(onExchange) {
|
|
1157
1257
|
return this.subscribeNetworkExchanges(onExchange);
|
|
@@ -1233,7 +1333,10 @@ export class CloudBrowser {
|
|
|
1233
1333
|
* @param onResync called when the copy had to be rebuilt, after the new tree
|
|
1234
1334
|
* is in place. Rebuilding is automatic; this is for telling the user why
|
|
1235
1335
|
* their expanded nodes collapsed.
|
|
1236
|
-
* @throws
|
|
1336
|
+
* @throws mirror_failed - the page could not be serialized, usually a document
|
|
1337
|
+
* that went away while the tree was being built. No mirror is left running
|
|
1338
|
+
*
|
|
1339
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1237
1340
|
*/
|
|
1238
1341
|
async mirrorDom(opts, onChange, onResync) {
|
|
1239
1342
|
const abort = new AbortController();
|
|
@@ -1282,6 +1385,11 @@ export class CloudBrowser {
|
|
|
1282
1385
|
* Starts (or restarts) the page's mirror and returns the main document,
|
|
1283
1386
|
* without subscribing to changes. {@link CloudBrowser.mirrorDom} is what you
|
|
1284
1387
|
* normally want; this is the raw command.
|
|
1388
|
+
*
|
|
1389
|
+
* @throws mirror_failed - the page could not be serialized, usually a document
|
|
1390
|
+
* that went away while the tree was being built. No mirror is left running
|
|
1391
|
+
*
|
|
1392
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1285
1393
|
*/
|
|
1286
1394
|
async startDomMirror(opts = {}) {
|
|
1287
1395
|
const req = create(StartDomMirrorRequestSchema, {
|
|
@@ -1293,6 +1401,7 @@ export class CloudBrowser {
|
|
|
1293
1401
|
if (opts.pierce !== undefined)
|
|
1294
1402
|
req.pierce = opts.pierce;
|
|
1295
1403
|
const resp = await this.client.startDomMirror(req);
|
|
1404
|
+
throwCommandError("startDomMirror", resp.error);
|
|
1296
1405
|
return {
|
|
1297
1406
|
root: resp.root,
|
|
1298
1407
|
frameId: resp.frameId,
|
|
@@ -1301,10 +1410,11 @@ export class CloudBrowser {
|
|
|
1301
1410
|
}
|
|
1302
1411
|
/** Stops the page's mirror, every frame of it. Idempotent. */
|
|
1303
1412
|
async stopDomMirror() {
|
|
1304
|
-
await this.client.stopDomMirror(create(StopDomMirrorRequestSchema, {
|
|
1413
|
+
const res = await this.client.stopDomMirror(create(StopDomMirrorRequestSchema, {
|
|
1305
1414
|
sessionId: this.sessionId,
|
|
1306
1415
|
apiKey: this.apiKey,
|
|
1307
1416
|
}));
|
|
1417
|
+
throwCommandError("stopDomMirror", res.error);
|
|
1308
1418
|
}
|
|
1309
1419
|
/**
|
|
1310
1420
|
* Fetches a node's children and starts reporting changes inside them.
|
|
@@ -1312,6 +1422,17 @@ export class CloudBrowser {
|
|
|
1312
1422
|
*
|
|
1313
1423
|
* On an `<iframe>` the one child is the document it hosts, and this call is
|
|
1314
1424
|
* what starts mirroring that frame.
|
|
1425
|
+
*
|
|
1426
|
+
* An id that is simply unknown is not a failure: the call resolves with an
|
|
1427
|
+
* empty result.
|
|
1428
|
+
*
|
|
1429
|
+
* @throws not_mirrored - the page has no mirror, or frameId is not part of the
|
|
1430
|
+
* one it has; start a mirror first, and after a resync fetch the current tree
|
|
1431
|
+
* before addressing nodes again
|
|
1432
|
+
* @throws mirror_failed - the subtree could not be serialized, usually a
|
|
1433
|
+
* document that went away mid-read
|
|
1434
|
+
*
|
|
1435
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1315
1436
|
*/
|
|
1316
1437
|
async getDomChildren(backendNodeId, frameId = "", depth) {
|
|
1317
1438
|
const req = create(GetDomChildrenRequestSchema, {
|
|
@@ -1324,11 +1445,19 @@ export class CloudBrowser {
|
|
|
1324
1445
|
if (depth !== undefined)
|
|
1325
1446
|
req.depth = depth;
|
|
1326
1447
|
const resp = await this.client.getDomChildren(req);
|
|
1448
|
+
throwCommandError("getDomChildren", resp.error);
|
|
1327
1449
|
return { children: resp.children, seq: Number(resp.seq) };
|
|
1328
1450
|
}
|
|
1329
1451
|
/**
|
|
1330
1452
|
* Stops reporting changes inside a node, and inside any frame below it.
|
|
1331
1453
|
* {@link DomMirror.collapse} calls this.
|
|
1454
|
+
*
|
|
1455
|
+
* @throws not_mirrored - the page has no mirror, or frameId is not part of
|
|
1456
|
+
* the one it has; this is what replaying ids from a tree that has since
|
|
1457
|
+
* been resynced looks like, so fetch the current tree and address the node
|
|
1458
|
+
* again
|
|
1459
|
+
*
|
|
1460
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1332
1461
|
*/
|
|
1333
1462
|
async releaseDomSubtree(backendNodeId, frameId = "") {
|
|
1334
1463
|
const req = create(ReleaseDomSubtreeRequestSchema, {
|
|
@@ -1338,13 +1467,24 @@ export class CloudBrowser {
|
|
|
1338
1467
|
});
|
|
1339
1468
|
if (frameId)
|
|
1340
1469
|
req.frameId = frameId;
|
|
1341
|
-
await this.client.releaseDomSubtree(req);
|
|
1470
|
+
const res = await this.client.releaseDomSubtree(req);
|
|
1471
|
+
throwCommandError("releaseDomSubtree", res.error);
|
|
1342
1472
|
}
|
|
1343
1473
|
/**
|
|
1344
1474
|
* Returns the chain from the main document down to a node, each ancestor
|
|
1345
1475
|
* with its own children, crossing into frames where it has to and starting
|
|
1346
1476
|
* the ones it passes through. {@link DomMirror.reveal} calls this and
|
|
1347
1477
|
* splices it in.
|
|
1478
|
+
*
|
|
1479
|
+
* An id that is simply unknown is not a failure: the call resolves with an
|
|
1480
|
+
* empty result.
|
|
1481
|
+
*
|
|
1482
|
+
* @throws not_mirrored - the page has no mirror, or frameId is not part of the
|
|
1483
|
+
* one it has
|
|
1484
|
+
* @throws mirror_failed - the path could not be serialized, usually a document
|
|
1485
|
+
* that went away mid-read
|
|
1486
|
+
*
|
|
1487
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1348
1488
|
*/
|
|
1349
1489
|
async revealDomNode(backendNodeId, frameId = "") {
|
|
1350
1490
|
const req = create(RevealDomNodeRequestSchema, {
|
|
@@ -1355,6 +1495,7 @@ export class CloudBrowser {
|
|
|
1355
1495
|
if (frameId)
|
|
1356
1496
|
req.frameId = frameId;
|
|
1357
1497
|
const resp = await this.client.revealDomNode(req);
|
|
1498
|
+
throwCommandError("revealDomNode", resp.error);
|
|
1358
1499
|
return { path: resp.path, seq: Number(resp.seq) };
|
|
1359
1500
|
}
|
|
1360
1501
|
/**
|
|
@@ -1366,6 +1507,10 @@ export class CloudBrowser {
|
|
|
1366
1507
|
* whole tree just to hash it. The two answer different questions: a hash
|
|
1367
1508
|
* compares content, a revision only says whether this document moved since
|
|
1368
1509
|
* you last asked.
|
|
1510
|
+
*
|
|
1511
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1512
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1513
|
+
* are no error codes to branch on.
|
|
1369
1514
|
*/
|
|
1370
1515
|
async getDomRevision(frameId = "") {
|
|
1371
1516
|
const req = create(GetDomRevisionRequestSchema, {
|
|
@@ -1375,6 +1520,7 @@ export class CloudBrowser {
|
|
|
1375
1520
|
if (frameId)
|
|
1376
1521
|
req.frameId = frameId;
|
|
1377
1522
|
const resp = await this.client.getDomRevision(req);
|
|
1523
|
+
throwCommandError("getDomRevision", resp.error);
|
|
1378
1524
|
return Number(resp.revision);
|
|
1379
1525
|
}
|
|
1380
1526
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -1385,7 +1531,9 @@ export class CloudBrowser {
|
|
|
1385
1531
|
*
|
|
1386
1532
|
* @returns CookieParam[], one per cookie in the context
|
|
1387
1533
|
*
|
|
1388
|
-
*
|
|
1534
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1535
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1536
|
+
* are no error codes to branch on.
|
|
1389
1537
|
*
|
|
1390
1538
|
* @example
|
|
1391
1539
|
* const cookies = await browser.getCookies();
|
|
@@ -1396,6 +1544,7 @@ export class CloudBrowser {
|
|
|
1396
1544
|
sessionId: this.sessionId,
|
|
1397
1545
|
apiKey: this.apiKey,
|
|
1398
1546
|
}));
|
|
1547
|
+
throwCommandError("getCookies", resp.error);
|
|
1399
1548
|
return resp.cookies.map(cookieParamFromProto);
|
|
1400
1549
|
}
|
|
1401
1550
|
/**
|
|
@@ -1406,7 +1555,9 @@ export class CloudBrowser {
|
|
|
1406
1555
|
*
|
|
1407
1556
|
* @param cookies - cookies to write; empty array is a no-op
|
|
1408
1557
|
*
|
|
1409
|
-
*
|
|
1558
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1559
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1560
|
+
* are no error codes to branch on.
|
|
1410
1561
|
*
|
|
1411
1562
|
* @example
|
|
1412
1563
|
* await browser.setCookies([
|
|
@@ -1414,25 +1565,29 @@ export class CloudBrowser {
|
|
|
1414
1565
|
* ]);
|
|
1415
1566
|
*/
|
|
1416
1567
|
async setCookies(cookies) {
|
|
1417
|
-
await this.client.setCookies(create(SetCookiesRequestSchema, {
|
|
1568
|
+
const res = await this.client.setCookies(create(SetCookiesRequestSchema, {
|
|
1418
1569
|
sessionId: this.sessionId,
|
|
1419
1570
|
apiKey: this.apiKey,
|
|
1420
1571
|
cookies: cookieParamsToProto(cookies),
|
|
1421
1572
|
}));
|
|
1573
|
+
throwCommandError("setCookies", res.error);
|
|
1422
1574
|
}
|
|
1423
1575
|
/**
|
|
1424
1576
|
* Deletes every cookie in the browser context.
|
|
1425
1577
|
*
|
|
1426
|
-
*
|
|
1578
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1579
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1580
|
+
* are no error codes to branch on.
|
|
1427
1581
|
*
|
|
1428
1582
|
* @example
|
|
1429
1583
|
* await browser.clearCookies();
|
|
1430
1584
|
*/
|
|
1431
1585
|
async clearCookies() {
|
|
1432
|
-
await this.client.clearCookies(create(ClearCookiesRequestSchema, {
|
|
1586
|
+
const res = await this.client.clearCookies(create(ClearCookiesRequestSchema, {
|
|
1433
1587
|
sessionId: this.sessionId,
|
|
1434
1588
|
apiKey: this.apiKey,
|
|
1435
1589
|
}));
|
|
1590
|
+
throwCommandError("clearCookies", res.error);
|
|
1436
1591
|
}
|
|
1437
1592
|
// ──────────────────────────────────────────────────────────────────
|
|
1438
1593
|
// Storage (localStorage)
|
|
@@ -1450,7 +1605,9 @@ export class CloudBrowser {
|
|
|
1450
1605
|
*
|
|
1451
1606
|
* @returns StorageOriginEntry[], one per origin with localStorage data
|
|
1452
1607
|
*
|
|
1453
|
-
*
|
|
1608
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1609
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1610
|
+
* are no error codes to branch on.
|
|
1454
1611
|
*
|
|
1455
1612
|
* @example
|
|
1456
1613
|
* const storage = await browser.getStorage();
|
|
@@ -1466,6 +1623,7 @@ export class CloudBrowser {
|
|
|
1466
1623
|
if (origin)
|
|
1467
1624
|
req.origin = origin;
|
|
1468
1625
|
const resp = await this.client.getStorage(req);
|
|
1626
|
+
throwCommandError("getStorage", resp.error);
|
|
1469
1627
|
return resp.storage.map(storageEntryFromProto);
|
|
1470
1628
|
}
|
|
1471
1629
|
/**
|
|
@@ -1479,7 +1637,9 @@ export class CloudBrowser {
|
|
|
1479
1637
|
*
|
|
1480
1638
|
* @param storage - entries to write, grouped by origin
|
|
1481
1639
|
*
|
|
1482
|
-
*
|
|
1640
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1641
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1642
|
+
* are no error codes to branch on.
|
|
1483
1643
|
*
|
|
1484
1644
|
* @example
|
|
1485
1645
|
* await browser.setStorage([
|
|
@@ -1493,11 +1653,12 @@ export class CloudBrowser {
|
|
|
1493
1653
|
* ]);
|
|
1494
1654
|
*/
|
|
1495
1655
|
async setStorage(storage) {
|
|
1496
|
-
await this.client.setStorage(create(SetStorageRequestSchema, {
|
|
1656
|
+
const res = await this.client.setStorage(create(SetStorageRequestSchema, {
|
|
1497
1657
|
sessionId: this.sessionId,
|
|
1498
1658
|
apiKey: this.apiKey,
|
|
1499
1659
|
storage: storageEntriesToProto(storage),
|
|
1500
1660
|
}));
|
|
1661
|
+
throwCommandError("setStorage", res.error);
|
|
1501
1662
|
}
|
|
1502
1663
|
/**
|
|
1503
1664
|
* Deletes localStorage in the browser context.
|
|
@@ -1505,7 +1666,9 @@ export class CloudBrowser {
|
|
|
1505
1666
|
* @param origin - if set, only this origin's storage is deleted
|
|
1506
1667
|
* (e.g. "https://example.com"); omit to delete all origins
|
|
1507
1668
|
*
|
|
1508
|
-
*
|
|
1669
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1670
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1671
|
+
* are no error codes to branch on.
|
|
1509
1672
|
*
|
|
1510
1673
|
* @example
|
|
1511
1674
|
* // Wipe one origin.
|
|
@@ -1521,7 +1684,8 @@ export class CloudBrowser {
|
|
|
1521
1684
|
});
|
|
1522
1685
|
if (origin)
|
|
1523
1686
|
req.origin = origin;
|
|
1524
|
-
await this.client.clearStorage(req);
|
|
1687
|
+
const res = await this.client.clearStorage(req);
|
|
1688
|
+
throwCommandError("clearStorage", res.error);
|
|
1525
1689
|
}
|
|
1526
1690
|
// ──────────────────────────────────────────────────────────────────
|
|
1527
1691
|
// Auth / DBSC (portable signed-in persona)
|
|
@@ -1536,7 +1700,9 @@ export class CloudBrowser {
|
|
|
1536
1700
|
*
|
|
1537
1701
|
* @returns AuthSession, or undefined when there is nothing to export
|
|
1538
1702
|
*
|
|
1539
|
-
*
|
|
1703
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1704
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1705
|
+
* are no error codes to branch on.
|
|
1540
1706
|
*
|
|
1541
1707
|
* @example
|
|
1542
1708
|
* const auth = await browser.getAuthSession();
|
|
@@ -1547,6 +1713,7 @@ export class CloudBrowser {
|
|
|
1547
1713
|
sessionId: this.sessionId,
|
|
1548
1714
|
apiKey: this.apiKey,
|
|
1549
1715
|
}));
|
|
1716
|
+
throwCommandError("getAuthSession", resp.error);
|
|
1550
1717
|
return resp.session ? authSessionFromProto(resp.session) : undefined;
|
|
1551
1718
|
}
|
|
1552
1719
|
/**
|
|
@@ -1558,18 +1725,21 @@ export class CloudBrowser {
|
|
|
1558
1725
|
*
|
|
1559
1726
|
* @param session - session as returned by getAuthSession()
|
|
1560
1727
|
*
|
|
1561
|
-
*
|
|
1728
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1729
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1730
|
+
* are no error codes to branch on.
|
|
1562
1731
|
*
|
|
1563
1732
|
* @example
|
|
1564
1733
|
* await browser.setAuthSession(saved);
|
|
1565
1734
|
* await browser.navigate("https://mail.google.com");
|
|
1566
1735
|
*/
|
|
1567
1736
|
async setAuthSession(session) {
|
|
1568
|
-
await this.client.setAuthSession(create(SetAuthSessionRequestSchema, {
|
|
1737
|
+
const res = await this.client.setAuthSession(create(SetAuthSessionRequestSchema, {
|
|
1569
1738
|
sessionId: this.sessionId,
|
|
1570
1739
|
apiKey: this.apiKey,
|
|
1571
1740
|
session: authSessionToProto(session),
|
|
1572
1741
|
}));
|
|
1742
|
+
throwCommandError("setAuthSession", res.error);
|
|
1573
1743
|
}
|
|
1574
1744
|
// ──────────────────────────────────────────────────────────────────
|
|
1575
1745
|
// Devtools / live-UI helpers
|
|
@@ -1589,7 +1759,9 @@ export class CloudBrowser {
|
|
|
1589
1759
|
* @returns InspectResult with the resolved backendNodeId, frameId, tag
|
|
1590
1760
|
* name, trimmed textContent, visibility and bounds
|
|
1591
1761
|
*
|
|
1592
|
-
*
|
|
1762
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1763
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1764
|
+
* are no error codes to branch on.
|
|
1593
1765
|
*
|
|
1594
1766
|
* @example
|
|
1595
1767
|
* const r = await browser.inspectAtPosition(200, 300);
|
|
@@ -1602,6 +1774,7 @@ export class CloudBrowser {
|
|
|
1602
1774
|
x,
|
|
1603
1775
|
y,
|
|
1604
1776
|
}));
|
|
1777
|
+
throwCommandError("inspectAtPosition", resp.error);
|
|
1605
1778
|
return {
|
|
1606
1779
|
backendNodeId: resp.backendNodeId,
|
|
1607
1780
|
frameId: resp.frameId,
|
|
@@ -1621,7 +1794,9 @@ export class CloudBrowser {
|
|
|
1621
1794
|
* @param frameId - id of the frame the node lives in; empty string
|
|
1622
1795
|
* targets the main frame
|
|
1623
1796
|
*
|
|
1624
|
-
*
|
|
1797
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1798
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1799
|
+
* are no error codes to branch on.
|
|
1625
1800
|
*
|
|
1626
1801
|
* @example
|
|
1627
1802
|
* await browser.highlightNode(res.backendNodeId, res.frameId);
|
|
@@ -1634,7 +1809,8 @@ export class CloudBrowser {
|
|
|
1634
1809
|
});
|
|
1635
1810
|
if (frameId)
|
|
1636
1811
|
req.frameId = frameId;
|
|
1637
|
-
await this.client.highlightNode(req);
|
|
1812
|
+
const res = await this.client.highlightNode(req);
|
|
1813
|
+
throwCommandError("highlight", res.error);
|
|
1638
1814
|
}
|
|
1639
1815
|
/**
|
|
1640
1816
|
* Pastes text at the current caret using IME-style input.
|
|
@@ -1646,17 +1822,22 @@ export class CloudBrowser {
|
|
|
1646
1822
|
*
|
|
1647
1823
|
* @param text - the text to insert at the caret
|
|
1648
1824
|
*
|
|
1649
|
-
* @throws
|
|
1825
|
+
* @throws no_focus - nothing in the page holds focus, so there is no caret to
|
|
1826
|
+
* insert at; click the field first
|
|
1827
|
+
* @throws busy - another action is already running on this page
|
|
1828
|
+
*
|
|
1829
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1650
1830
|
*
|
|
1651
1831
|
* @example
|
|
1652
1832
|
* await browser.insertText("hello world");
|
|
1653
1833
|
*/
|
|
1654
1834
|
async insertText(text) {
|
|
1655
|
-
await this.client.insertText(create(InsertTextRequestSchema, {
|
|
1835
|
+
const res = await this.client.insertText(create(InsertTextRequestSchema, {
|
|
1656
1836
|
sessionId: this.sessionId,
|
|
1657
1837
|
apiKey: this.apiKey,
|
|
1658
1838
|
text,
|
|
1659
1839
|
}));
|
|
1840
|
+
throwCommandError("insertText", res.error);
|
|
1660
1841
|
}
|
|
1661
1842
|
/**
|
|
1662
1843
|
* Types `text` into the currently focused element as a per-key stream of real
|
|
@@ -1678,7 +1859,10 @@ export class CloudBrowser {
|
|
|
1678
1859
|
* @param opts - optional: `clearFirst` clears the focused field (Ctrl+A,
|
|
1679
1860
|
* Delete) before typing
|
|
1680
1861
|
*
|
|
1681
|
-
*
|
|
1862
|
+
* `type` has no semantic failure of its own: the keys land wherever focus
|
|
1863
|
+
* happens to be, so there is no target it can miss. Only the page or context
|
|
1864
|
+
* being torn down mid-stream surfaces, and that is a transport failure rather
|
|
1865
|
+
* than a code.
|
|
1682
1866
|
*
|
|
1683
1867
|
* @example
|
|
1684
1868
|
* // OTP field that auto-advances across boxes.
|
|
@@ -1693,7 +1877,7 @@ export class CloudBrowser {
|
|
|
1693
1877
|
});
|
|
1694
1878
|
if (opts?.clearFirst)
|
|
1695
1879
|
req.clearFirst = true;
|
|
1696
|
-
await this.client.type(req);
|
|
1880
|
+
throwCommandError("type", (await this.client.type(req)).error);
|
|
1697
1881
|
}
|
|
1698
1882
|
/**
|
|
1699
1883
|
* Fires a single key-down event.
|
|
@@ -1708,7 +1892,11 @@ export class CloudBrowser {
|
|
|
1708
1892
|
* `modifiers` (bit-flag: Alt=1, Ctrl=2, Meta=4, Shift=8),
|
|
1709
1893
|
* `location` (0=standard, 1=left, 2=right, 3=numpad)
|
|
1710
1894
|
*
|
|
1711
|
-
* @throws
|
|
1895
|
+
* @throws no_focus - nothing in the page holds focus, so the key has nowhere
|
|
1896
|
+
* to go; click the field first
|
|
1897
|
+
* @throws busy - another action is already running on this page
|
|
1898
|
+
*
|
|
1899
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1712
1900
|
*
|
|
1713
1901
|
* @example
|
|
1714
1902
|
* // Ctrl+A
|
|
@@ -1727,7 +1915,8 @@ export class CloudBrowser {
|
|
|
1727
1915
|
req.modifiers = opts.modifiers;
|
|
1728
1916
|
if (opts?.location !== undefined)
|
|
1729
1917
|
req.location = opts.location;
|
|
1730
|
-
await this.client.pressKey(req);
|
|
1918
|
+
const res = await this.client.pressKey(req);
|
|
1919
|
+
throwCommandError("pressKey", res.error);
|
|
1731
1920
|
}
|
|
1732
1921
|
/**
|
|
1733
1922
|
* Fires a single key-up event.
|
|
@@ -1753,7 +1942,8 @@ export class CloudBrowser {
|
|
|
1753
1942
|
req.modifiers = opts.modifiers;
|
|
1754
1943
|
if (opts?.location !== undefined)
|
|
1755
1944
|
req.location = opts.location;
|
|
1756
|
-
await this.client.releaseKey(req);
|
|
1945
|
+
const res = await this.client.releaseKey(req);
|
|
1946
|
+
throwCommandError("releaseKey", res.error);
|
|
1757
1947
|
}
|
|
1758
1948
|
/**
|
|
1759
1949
|
* Returns the current text selection.
|
|
@@ -1764,7 +1954,9 @@ export class CloudBrowser {
|
|
|
1764
1954
|
*
|
|
1765
1955
|
* @returns the selected text, or `""` when nothing is selected
|
|
1766
1956
|
*
|
|
1767
|
-
*
|
|
1957
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
1958
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
1959
|
+
* are no error codes to branch on.
|
|
1768
1960
|
*
|
|
1769
1961
|
* @example
|
|
1770
1962
|
* const sel = await browser.getSelection();
|
|
@@ -1775,6 +1967,7 @@ export class CloudBrowser {
|
|
|
1775
1967
|
sessionId: this.sessionId,
|
|
1776
1968
|
apiKey: this.apiKey,
|
|
1777
1969
|
}));
|
|
1970
|
+
throwCommandError("getSelection", resp.error);
|
|
1778
1971
|
return resp.text;
|
|
1779
1972
|
}
|
|
1780
1973
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -1795,8 +1988,10 @@ export class CloudBrowser {
|
|
|
1795
1988
|
*
|
|
1796
1989
|
* @returns empty string on success — the solution is applied server-side
|
|
1797
1990
|
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
1991
|
+
* Rejects when no captcha appeared within imeoutMs, or when the one that did
|
|
1992
|
+
* could not be solved within
|
|
1993
|
+
etryAmount attempts. Neither carries a code:
|
|
1994
|
+
* solving runs outside the page, so there is no per-command code set here.
|
|
1800
1995
|
*
|
|
1801
1996
|
* @example
|
|
1802
1997
|
* await browser.solveCaptcha({ retryAmount: 2 });
|
|
@@ -1824,7 +2019,8 @@ export class CloudBrowser {
|
|
|
1824
2019
|
*
|
|
1825
2020
|
* @returns the ICE servers for the client `RTCPeerConnection`
|
|
1826
2021
|
*
|
|
1827
|
-
*
|
|
2022
|
+
* Rejects when TURN is not configured on the server. That is a deployment
|
|
2023
|
+
* condition rather than a per-call outcome, so it carries no code.
|
|
1828
2024
|
*
|
|
1829
2025
|
* @example
|
|
1830
2026
|
* const ice = await browser.getStreamConfig();
|
|
@@ -1851,8 +2047,16 @@ export class CloudBrowser {
|
|
|
1851
2047
|
* @returns the SDP answer to apply as the remote description, plus the
|
|
1852
2048
|
* viewport to map input coordinates into
|
|
1853
2049
|
*
|
|
1854
|
-
* @throws
|
|
1855
|
-
*
|
|
2050
|
+
* @throws already_active - a stream is already running on this session; stop it
|
|
2051
|
+
* before starting another
|
|
2052
|
+
* @throws negotiation_failed - the browser could not agree on a connection. The
|
|
2053
|
+
* message carries the negotiator's own diagnostic, which is usually where the
|
|
2054
|
+
* actual cause is
|
|
2055
|
+
*
|
|
2056
|
+
* An empty offer or an unconfigured TURN setup is a caller mistake rather than
|
|
2057
|
+
* an outcome, and rejects separately.
|
|
2058
|
+
*
|
|
2059
|
+
* @see {@link CommandError} for reading the code off the rejection
|
|
1856
2060
|
*
|
|
1857
2061
|
* @example
|
|
1858
2062
|
* const { answerSdp, viewport } = await browser.startStream(offer.sdp);
|
|
@@ -1864,6 +2068,7 @@ export class CloudBrowser {
|
|
|
1864
2068
|
apiKey: this.apiKey,
|
|
1865
2069
|
offerSdp,
|
|
1866
2070
|
}));
|
|
2071
|
+
throwCommandError("startStream", resp.error);
|
|
1867
2072
|
return {
|
|
1868
2073
|
answerSdp: resp.answerSdp,
|
|
1869
2074
|
viewport: resp.viewport
|
|
@@ -1875,25 +2080,27 @@ export class CloudBrowser {
|
|
|
1875
2080
|
* Tears down the live video stream for the session's page. Safe to call even
|
|
1876
2081
|
* if no stream is running.
|
|
1877
2082
|
*
|
|
1878
|
-
*
|
|
2083
|
+
* Rejects only on a transport failure. Stopping a stream that is not running is
|
|
2084
|
+
* a no-op rather than a failure, so there are no error codes to branch on.
|
|
1879
2085
|
*
|
|
1880
2086
|
* @example
|
|
1881
2087
|
* await browser.stopStream();
|
|
1882
2088
|
*/
|
|
1883
2089
|
async stopStream() {
|
|
1884
|
-
await this.client.stopStream(create(StopStreamRequestSchema, {
|
|
2090
|
+
const res = await this.client.stopStream(create(StopStreamRequestSchema, {
|
|
1885
2091
|
sessionId: this.sessionId,
|
|
1886
2092
|
apiKey: this.apiKey,
|
|
1887
2093
|
}));
|
|
2094
|
+
throwCommandError("stopStream", res.error);
|
|
1888
2095
|
}
|
|
1889
2096
|
// ──────────────────────────────────────────────────────────────────
|
|
1890
2097
|
// Reactions
|
|
1891
2098
|
// ──────────────────────────────────────────────────────────────────
|
|
1892
2099
|
/**
|
|
1893
|
-
* Registers a one-shot "reaction":
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
1896
|
-
*
|
|
2100
|
+
* Registers a one-shot "reaction": the browser watches for the `match`
|
|
2101
|
+
* locator in the background and, as soon as it matches, clicks it the same
|
|
2102
|
+
* way {@link CloudBrowser.click} does (scroll, human path, occlusion check) —
|
|
2103
|
+
* then removes itself. The reaction yields to any in-flight input
|
|
1897
2104
|
* action and only fires while the pointer is idle, so a reaction naturally
|
|
1898
2105
|
* slots into the gaps of a retrying foreground action (e.g. it dismisses a
|
|
1899
2106
|
* newsletter modal blocking a {@link CloudBrowser.click}, after which the
|
|
@@ -1951,6 +2158,7 @@ export class CloudBrowser {
|
|
|
1951
2158
|
if (opts?.intervalMs)
|
|
1952
2159
|
req.interval = opts.intervalMs;
|
|
1953
2160
|
const resp = await this.client.addReaction(req);
|
|
2161
|
+
throwCommandError("addReaction", resp.error);
|
|
1954
2162
|
return resp.reactionId;
|
|
1955
2163
|
}
|
|
1956
2164
|
/**
|
|
@@ -1961,6 +2169,10 @@ export class CloudBrowser {
|
|
|
1961
2169
|
*
|
|
1962
2170
|
* @returns true if a pending reaction with this id existed and was removed
|
|
1963
2171
|
*
|
|
2172
|
+
* Removing an id that is not registered is a no-op rather than an error, so the
|
|
2173
|
+
* returned boolean - not a rejection - is what tells you whether anything was
|
|
2174
|
+
* there. Rejects only on a transport failure.
|
|
2175
|
+
*
|
|
1964
2176
|
* @example
|
|
1965
2177
|
* const removed = await browser.removeReaction(id);
|
|
1966
2178
|
*/
|
|
@@ -1970,6 +2182,7 @@ export class CloudBrowser {
|
|
|
1970
2182
|
apiKey: this.apiKey,
|
|
1971
2183
|
reactionId,
|
|
1972
2184
|
}));
|
|
2185
|
+
throwCommandError("removeReaction", resp.error);
|
|
1973
2186
|
return resp.removed;
|
|
1974
2187
|
}
|
|
1975
2188
|
/**
|
|
@@ -1978,6 +2191,10 @@ export class CloudBrowser {
|
|
|
1978
2191
|
*
|
|
1979
2192
|
* @returns the pending reactions for the page
|
|
1980
2193
|
*
|
|
2194
|
+
* Rejects only on a transport failure - a dead session, a page that is gone,
|
|
2195
|
+
* a broken connection. This call has no semantic failure of its own, so there
|
|
2196
|
+
* are no error codes to branch on.
|
|
2197
|
+
*
|
|
1981
2198
|
* @example
|
|
1982
2199
|
* const pending = await browser.listReactions();
|
|
1983
2200
|
* for (const r of pending) console.log(r.reactionId, r.matchSelector);
|
|
@@ -1987,6 +2204,7 @@ export class CloudBrowser {
|
|
|
1987
2204
|
sessionId: this.sessionId,
|
|
1988
2205
|
apiKey: this.apiKey,
|
|
1989
2206
|
}));
|
|
2207
|
+
throwCommandError("listReactions", resp.error);
|
|
1990
2208
|
return resp.reactions.map((r) => ({
|
|
1991
2209
|
reactionId: r.reactionId,
|
|
1992
2210
|
matchSelector: r.matchSelector ?? "",
|
|
@@ -2003,12 +2221,14 @@ export class CloudBrowser {
|
|
|
2003
2221
|
/**
|
|
2004
2222
|
* Runs `source` in the session's browser and waits for it to finish.
|
|
2005
2223
|
*
|
|
2006
|
-
* The script
|
|
2007
|
-
* page, and reaches the
|
|
2008
|
-
*
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
2011
|
-
*
|
|
2224
|
+
* The script runs beside the browser, in a V8 isolate of its own rather than
|
|
2225
|
+
* in the page, and reaches the document through the engine: a cross-origin
|
|
2226
|
+
* `<iframe>` is read as plain `contentDocument` with no frame ids anywhere,
|
|
2227
|
+
* values come back as live objects it can assign to rather than snapshots, an
|
|
2228
|
+
* element can be handed straight to `browser.click`, and the page sees nothing
|
|
2229
|
+
* injected. Steps cost microseconds rather than network round trips, so work
|
|
2230
|
+
* that is chatty by nature — polling for a selector, walking a list,
|
|
2231
|
+
* following pagination — is affordable there. A guide for it is still to come.
|
|
2012
2232
|
*
|
|
2013
2233
|
* This waits for as long as the script runs, and cannot be bounded: the run
|
|
2014
2234
|
* id needed to cancel only arrives with the reply. Use
|
|
@@ -2021,7 +2241,10 @@ export class CloudBrowser {
|
|
|
2021
2241
|
* @returns the return value and the script's whole console output. A script
|
|
2022
2242
|
* that threw is reported as `success: false`, not as a rejection
|
|
2023
2243
|
*
|
|
2024
|
-
*
|
|
2244
|
+
* Rejects only on a transport failure. A script that fails to compile or throws
|
|
2245
|
+
* is not a rejection: the returned result has success: false and
|
|
2246
|
+
esult holds
|
|
2247
|
+
* the message, so a broken script stays distinguishable from a broken connection.
|
|
2025
2248
|
*
|
|
2026
2249
|
* @example
|
|
2027
2250
|
* const result = await browser.runScript(`
|
|
@@ -2069,7 +2292,8 @@ export class CloudBrowser {
|
|
|
2069
2292
|
*
|
|
2070
2293
|
* @returns ScriptRun handle for awaiting or cancelling the run
|
|
2071
2294
|
*
|
|
2072
|
-
*
|
|
2295
|
+
* Rejects only on a transport failure: a script that fails to compile or throws
|
|
2296
|
+
* surfaces on the run itself rather than here.
|
|
2073
2297
|
*
|
|
2074
2298
|
* @example
|
|
2075
2299
|
* const run = await browser.startScript(source, (ev) => {
|
|
@@ -2154,7 +2378,8 @@ export class CloudBrowser {
|
|
|
2154
2378
|
*
|
|
2155
2379
|
* @returns ScriptFollow handle for stopping the subscription
|
|
2156
2380
|
*
|
|
2157
|
-
*
|
|
2381
|
+
* Rejects only on a transport failure: opening the subscription has no semantic
|
|
2382
|
+
* failure of its own.
|
|
2158
2383
|
*
|
|
2159
2384
|
* @example
|
|
2160
2385
|
* const follow = await browser.followScript(runId, (ev) => {
|
|
@@ -2187,7 +2412,9 @@ export class CloudBrowser {
|
|
|
2187
2412
|
* @returns how many runs were cancelled; 0 when the id named nothing in
|
|
2188
2413
|
* flight
|
|
2189
2414
|
*
|
|
2190
|
-
*
|
|
2415
|
+
* Rejects only on a transport failure. Cancelling runs that have already
|
|
2416
|
+
* finished, or none at all, is a no-op - read the returned count to learn how
|
|
2417
|
+
* many were actually stopped.
|
|
2191
2418
|
*
|
|
2192
2419
|
* @example
|
|
2193
2420
|
* await browser.stopScripts(""); // abandon everything running
|
|
@@ -2210,7 +2437,9 @@ export class CloudBrowser {
|
|
|
2210
2437
|
*
|
|
2211
2438
|
* @returns one entry per run still executing
|
|
2212
2439
|
*
|
|
2213
|
-
*
|
|
2440
|
+
* Rejects only on a transport failure - a dead session, a broken connection.
|
|
2441
|
+
* This call has no semantic failure of its own, so there are no error codes to
|
|
2442
|
+
* branch on.
|
|
2214
2443
|
*
|
|
2215
2444
|
* @example
|
|
2216
2445
|
* for (const run of await browser.listScriptRuns()) {
|