browserscale-ts 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +1 -1
- package/dist/browserscale.browser.js +303 -38
- package/dist/client.d.ts +141 -2
- package/dist/client.js +236 -1
- package/dist/errors.d.ts +33 -6
- package/dist/errors.js +11 -3
- package/dist/gen/wrc_pb.d.ts +559 -3
- package/dist/gen/wrc_pb.js +146 -66
- package/dist/index.d.ts +2 -2
- package/dist/internal/convert.js +20 -0
- package/dist/options.d.ts +31 -0
- package/dist/types.d.ts +59 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Transport } from "@connectrpc/connect";
|
|
2
2
|
import type { Locator } from "./locator.ts";
|
|
3
|
-
import type { DOMResult, DragResult, ElementResult, EvaluateResult, InterceptedRequest, InterceptedResponse, InspectResult, NavigateResult, ObservationResult, PageInfo, ScreenshotResult, ReadCanvasResult, SelectOptionResult, WaitResult } from "./types.ts";
|
|
4
|
-
import type { ClickOpts, FillOpts, GetDOMOpts, GetObservationOpts, LoadHTMLOpts, NavigateOpts, ScreenshotOpts, ReadCanvasOpts, SelectOpts, WaitOpts } from "./options.ts";
|
|
3
|
+
import type { DOMResult, DragResult, ElementResult, EvaluateResult, InterceptedRequest, InterceptedResponse, InspectResult, NavigateResult, ObservationResult, PageInfo, ScreenshotResult, ReadCanvasResult, SelectOptionResult, WaitResult, IceServer, ReactionInfo } from "./types.ts";
|
|
4
|
+
import type { ClickOpts, FillOpts, GetDOMOpts, GetObservationOpts, LoadHTMLOpts, NavigateOpts, ReactionOpts, ScreenshotOpts, ReadCanvasOpts, SelectOpts, WaitOpts } from "./options.ts";
|
|
5
5
|
import type { HeaderModification, RequestPattern } from "./network.ts";
|
|
6
6
|
import type { CookieParam } from "./cookies.ts";
|
|
7
7
|
import type { StorageOriginEntry } from "./storage.ts";
|
|
@@ -818,6 +818,36 @@ export declare class CloudBrowser {
|
|
|
818
818
|
* await browser.insertText("hello world");
|
|
819
819
|
*/
|
|
820
820
|
insertText(text: string): Promise<void>;
|
|
821
|
+
/**
|
|
822
|
+
* Types `text` into the currently focused element as a per-key stream of real
|
|
823
|
+
* keyboard events (keyDown/char/keyUp with the context's QWERTZ/QWERTY layout
|
|
824
|
+
* and human cadence) — unlike {@link insertText}, a single IME-style commit
|
|
825
|
+
* with no key events.
|
|
826
|
+
*
|
|
827
|
+
* `type` is intentionally UNtargeted and loose: it does not locate or focus
|
|
828
|
+
* any element and does NOT pin focus, so the page is free to route keys and
|
|
829
|
+
* move focus between fields mid-stream — ideal for one-time-code / OTP inputs
|
|
830
|
+
* that auto-advance to the next box on each digit. To type one specific field
|
|
831
|
+
* that must stay focused for the whole value, use {@link fill} instead
|
|
832
|
+
* (strict, target-bound, per-key focus-verified).
|
|
833
|
+
*
|
|
834
|
+
* Nothing is focused for you: {@link click} (or {@link fill}) the field first,
|
|
835
|
+
* or otherwise ensure focus, before calling `type`.
|
|
836
|
+
*
|
|
837
|
+
* @param text - the text to type as real key events
|
|
838
|
+
* @param opts - optional: `clearFirst` clears the focused field (Ctrl+A,
|
|
839
|
+
* Delete) before typing
|
|
840
|
+
*
|
|
841
|
+
* @throws UNKNOWN_ERROR - the page/context was torn down mid-stream
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* // OTP field that auto-advances across boxes.
|
|
845
|
+
* await browser.click(css("input.otp-0"));
|
|
846
|
+
* await browser.type("123456");
|
|
847
|
+
*/
|
|
848
|
+
type(text: string, opts?: {
|
|
849
|
+
clearFirst?: boolean;
|
|
850
|
+
}): Promise<void>;
|
|
821
851
|
/**
|
|
822
852
|
* Fires a single key-down event.
|
|
823
853
|
*
|
|
@@ -901,4 +931,113 @@ export declare class CloudBrowser {
|
|
|
901
931
|
timeoutMs?: number;
|
|
902
932
|
retryAmount?: number;
|
|
903
933
|
}): Promise<string>;
|
|
934
|
+
/**
|
|
935
|
+
* Returns the ICE servers (TURN URL + short-lived credentials) to put in
|
|
936
|
+
* your `RTCPeerConnection` BEFORE creating the offer, so it can gather relay
|
|
937
|
+
* candidates.
|
|
938
|
+
*
|
|
939
|
+
* Live streaming is a two-step, client-offerer handshake: call
|
|
940
|
+
* `getStreamConfig`, build your peer with the returned servers, create an
|
|
941
|
+
* offer, then pass its SDP to {@link startStream} and apply the answer.
|
|
942
|
+
*
|
|
943
|
+
* @returns the ICE servers for the client `RTCPeerConnection`
|
|
944
|
+
*
|
|
945
|
+
* @throws UNKNOWN_ERROR - TURN is not configured on the server
|
|
946
|
+
*
|
|
947
|
+
* @example
|
|
948
|
+
* const ice = await browser.getStreamConfig();
|
|
949
|
+
* const pc = new RTCPeerConnection({ iceServers: ice });
|
|
950
|
+
*/
|
|
951
|
+
getStreamConfig(): Promise<IceServer[]>;
|
|
952
|
+
/**
|
|
953
|
+
* Answers your WebRTC SDP offer and starts streaming the page as a video
|
|
954
|
+
* track, returning the SDP answer to set as your peer's remote description.
|
|
955
|
+
* The browser is the answerer; you are the offerer (see
|
|
956
|
+
* {@link getStreamConfig} for the credentials to build the offer).
|
|
957
|
+
*
|
|
958
|
+
* @param offerSdp - your `RTCPeerConnection`'s SDP offer
|
|
959
|
+
*
|
|
960
|
+
* @returns the SDP answer to apply as the remote description
|
|
961
|
+
*
|
|
962
|
+
* @throws UNKNOWN_ERROR - the offer was empty, TURN is unconfigured, or the
|
|
963
|
+
* browser could not negotiate the stream
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* const answer = await browser.startStream(offer.sdp);
|
|
967
|
+
* await pc.setRemoteDescription({ type: "answer", sdp: answer });
|
|
968
|
+
*/
|
|
969
|
+
startStream(offerSdp: string): Promise<string>;
|
|
970
|
+
/**
|
|
971
|
+
* Tears down the live video stream for the session's page. Safe to call even
|
|
972
|
+
* if no stream is running.
|
|
973
|
+
*
|
|
974
|
+
* @throws UNKNOWN_ERROR - the stream could not be stopped
|
|
975
|
+
*
|
|
976
|
+
* @example
|
|
977
|
+
* await browser.stopStream();
|
|
978
|
+
*/
|
|
979
|
+
stopStream(): Promise<void>;
|
|
980
|
+
/**
|
|
981
|
+
* Registers a one-shot "reaction": a background poller (one shared loop per
|
|
982
|
+
* page) watches for the `match` locator and, as soon as it matches, clicks it
|
|
983
|
+
* with the full smart-click machinery (scroll, human path, occlusion gate,
|
|
984
|
+
* evade) — then removes itself. The poller yields to any in-flight input
|
|
985
|
+
* action and only fires while the pointer is idle, so a reaction naturally
|
|
986
|
+
* slots into the gaps of a retrying foreground action (e.g. it dismisses a
|
|
987
|
+
* newsletter modal blocking a {@link CloudBrowser.click}, after which the
|
|
988
|
+
* click's own retry succeeds). Reactions are scoped to the page and torn
|
|
989
|
+
* down automatically when the page/session ends.
|
|
990
|
+
*
|
|
991
|
+
* `match` must be a `css()` or `js()` {@link Locator} — `node()`/`at()` are
|
|
992
|
+
* rejected. Use `.inAllFrames()` to watch every frame and `.visible(false)`
|
|
993
|
+
* to opt out of the default visibility gate. Pass a `ReactionOpts` to click a
|
|
994
|
+
* different target (`on`), change the button/click count, or the poll cadence.
|
|
995
|
+
*
|
|
996
|
+
* @param match - the css()/js() locator to watch for
|
|
997
|
+
* @param opts - optional reaction customization; see {@link ReactionOpts}
|
|
998
|
+
*
|
|
999
|
+
* @returns the reactionId (pass to {@link CloudBrowser.removeReaction})
|
|
1000
|
+
*
|
|
1001
|
+
* @throws {@link BrowserScaleError} - `match` (or `opts.on`) is not a css()/js()
|
|
1002
|
+
* locator, or a server/transport error
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* // Auto-dismiss a consent button whenever it appears, in any frame.
|
|
1006
|
+
* const id = await browser.addReaction(css("button#accept").inAllFrames());
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* // Watch for a newsletter modal, but click its close "X" instead.
|
|
1010
|
+
* const id = await browser.addReaction(css("#newsletter-modal"), {
|
|
1011
|
+
* on: css(".modal-close"),
|
|
1012
|
+
* });
|
|
1013
|
+
*/
|
|
1014
|
+
addReaction(match: Locator, opts?: ReactionOpts): Promise<string>;
|
|
1015
|
+
/**
|
|
1016
|
+
* Removes a pending reaction by id. Returns `false` if the reaction had
|
|
1017
|
+
* already fired (one-shot) or was never registered.
|
|
1018
|
+
*
|
|
1019
|
+
* @param reactionId - id returned by {@link CloudBrowser.addReaction}
|
|
1020
|
+
*
|
|
1021
|
+
* @returns true if a pending reaction with this id existed and was removed
|
|
1022
|
+
*
|
|
1023
|
+
* @example
|
|
1024
|
+
* const removed = await browser.removeReaction(id);
|
|
1025
|
+
*/
|
|
1026
|
+
removeReaction(reactionId: string): Promise<boolean>;
|
|
1027
|
+
/**
|
|
1028
|
+
* Returns the still-pending reactions registered for the current page.
|
|
1029
|
+
* Reactions that have already fired (one-shot) are not included.
|
|
1030
|
+
*
|
|
1031
|
+
* @returns the pending reactions for the page
|
|
1032
|
+
*
|
|
1033
|
+
* @example
|
|
1034
|
+
* const pending = await browser.listReactions();
|
|
1035
|
+
* for (const r of pending) console.log(r.reactionId, r.matchSelector);
|
|
1036
|
+
*/
|
|
1037
|
+
listReactions(): Promise<ReactionInfo[]>;
|
|
1038
|
+
/**
|
|
1039
|
+
* @internal — a reaction match/action must be a css()/js() locator: node()
|
|
1040
|
+
* and at() are rejected (a reaction watches for a condition, like a wait).
|
|
1041
|
+
*/
|
|
1042
|
+
private assertReactionLocator;
|
|
904
1043
|
}
|
package/dist/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createClient } from "@connectrpc/connect";
|
|
|
2
2
|
import { create } from "@bufbuild/protobuf";
|
|
3
3
|
import { Browser,
|
|
4
4
|
// request / response schemas
|
|
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, InspectAtPositionRequestSchema, HighlightNodeRequestSchema, InsertTextRequestSchema, PressKeyRequestSchema, ReleaseKeyRequestSchema, GetSelectionRequestSchema, SolveCaptchaRequestSchema, } from "./gen/wrc_pb.js";
|
|
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, InspectAtPositionRequestSchema, HighlightNodeRequestSchema, InsertTextRequestSchema, TypeRequestSchema, PressKeyRequestSchema, ReleaseKeyRequestSchema, GetSelectionRequestSchema, SolveCaptchaRequestSchema, GetStreamConfigRequestSchema, StartStreamRequestSchema, StopStreamRequestSchema, AddReactionRequestSchema, RemoveReactionRequestSchema, ListReactionsRequestSchema, } from "./gen/wrc_pb.js";
|
|
6
6
|
import { DefaultWaitTimeoutMs } from "./defaults.js";
|
|
7
7
|
import { BrowserScaleError } from "./errors.js";
|
|
8
8
|
import { cookieParamFromProto, cookieParamsToProto, elementFields, headerModsToProto, headersToProto, interceptedRequestFromProto, interceptedResponseFromProto, pageInfoFromProto, rectFromProto, splitRequestPatterns, storageEntriesToProto, storageEntryFromProto, unwrapClick, unwrapDrag, unwrapFill, unwrapMove, unwrapScroll, unwrapSelect, unwrapWait, } from "./internal/convert.js";
|
|
@@ -437,6 +437,10 @@ export class CloudBrowser {
|
|
|
437
437
|
});
|
|
438
438
|
if (opts?.clearFirst)
|
|
439
439
|
req.clearFirst = true;
|
|
440
|
+
if (opts?.timeoutMs !== undefined)
|
|
441
|
+
req.timeout = opts.timeoutMs;
|
|
442
|
+
if (opts?.steadyMs !== undefined)
|
|
443
|
+
req.steadyTime = opts.steadyMs;
|
|
440
444
|
return unwrapFill(await this.client.fill(req));
|
|
441
445
|
}
|
|
442
446
|
/**
|
|
@@ -1243,6 +1247,43 @@ export class CloudBrowser {
|
|
|
1243
1247
|
text,
|
|
1244
1248
|
}));
|
|
1245
1249
|
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Types `text` into the currently focused element as a per-key stream of real
|
|
1252
|
+
* keyboard events (keyDown/char/keyUp with the context's QWERTZ/QWERTY layout
|
|
1253
|
+
* and human cadence) — unlike {@link insertText}, a single IME-style commit
|
|
1254
|
+
* with no key events.
|
|
1255
|
+
*
|
|
1256
|
+
* `type` is intentionally UNtargeted and loose: it does not locate or focus
|
|
1257
|
+
* any element and does NOT pin focus, so the page is free to route keys and
|
|
1258
|
+
* move focus between fields mid-stream — ideal for one-time-code / OTP inputs
|
|
1259
|
+
* that auto-advance to the next box on each digit. To type one specific field
|
|
1260
|
+
* that must stay focused for the whole value, use {@link fill} instead
|
|
1261
|
+
* (strict, target-bound, per-key focus-verified).
|
|
1262
|
+
*
|
|
1263
|
+
* Nothing is focused for you: {@link click} (or {@link fill}) the field first,
|
|
1264
|
+
* or otherwise ensure focus, before calling `type`.
|
|
1265
|
+
*
|
|
1266
|
+
* @param text - the text to type as real key events
|
|
1267
|
+
* @param opts - optional: `clearFirst` clears the focused field (Ctrl+A,
|
|
1268
|
+
* Delete) before typing
|
|
1269
|
+
*
|
|
1270
|
+
* @throws UNKNOWN_ERROR - the page/context was torn down mid-stream
|
|
1271
|
+
*
|
|
1272
|
+
* @example
|
|
1273
|
+
* // OTP field that auto-advances across boxes.
|
|
1274
|
+
* await browser.click(css("input.otp-0"));
|
|
1275
|
+
* await browser.type("123456");
|
|
1276
|
+
*/
|
|
1277
|
+
async type(text, opts) {
|
|
1278
|
+
const req = create(TypeRequestSchema, {
|
|
1279
|
+
sessionId: this.sessionId,
|
|
1280
|
+
apiKey: this.apiKey,
|
|
1281
|
+
text,
|
|
1282
|
+
});
|
|
1283
|
+
if (opts?.clearFirst)
|
|
1284
|
+
req.clearFirst = true;
|
|
1285
|
+
await this.client.type(req);
|
|
1286
|
+
}
|
|
1246
1287
|
/**
|
|
1247
1288
|
* Fires a single key-down event.
|
|
1248
1289
|
*
|
|
@@ -1358,4 +1399,198 @@ export class CloudBrowser {
|
|
|
1358
1399
|
}));
|
|
1359
1400
|
return resp.result;
|
|
1360
1401
|
}
|
|
1402
|
+
// ──────────────────────────────────────────────────────────────────
|
|
1403
|
+
// Live streaming (WebRTC)
|
|
1404
|
+
// ──────────────────────────────────────────────────────────────────
|
|
1405
|
+
/**
|
|
1406
|
+
* Returns the ICE servers (TURN URL + short-lived credentials) to put in
|
|
1407
|
+
* your `RTCPeerConnection` BEFORE creating the offer, so it can gather relay
|
|
1408
|
+
* candidates.
|
|
1409
|
+
*
|
|
1410
|
+
* Live streaming is a two-step, client-offerer handshake: call
|
|
1411
|
+
* `getStreamConfig`, build your peer with the returned servers, create an
|
|
1412
|
+
* offer, then pass its SDP to {@link startStream} and apply the answer.
|
|
1413
|
+
*
|
|
1414
|
+
* @returns the ICE servers for the client `RTCPeerConnection`
|
|
1415
|
+
*
|
|
1416
|
+
* @throws UNKNOWN_ERROR - TURN is not configured on the server
|
|
1417
|
+
*
|
|
1418
|
+
* @example
|
|
1419
|
+
* const ice = await browser.getStreamConfig();
|
|
1420
|
+
* const pc = new RTCPeerConnection({ iceServers: ice });
|
|
1421
|
+
*/
|
|
1422
|
+
async getStreamConfig() {
|
|
1423
|
+
const resp = await this.client.getStreamConfig(create(GetStreamConfigRequestSchema, {
|
|
1424
|
+
sessionId: this.sessionId,
|
|
1425
|
+
apiKey: this.apiKey,
|
|
1426
|
+
}));
|
|
1427
|
+
return resp.iceServers.map((s) => ({
|
|
1428
|
+
urls: s.urls,
|
|
1429
|
+
username: s.username ?? "",
|
|
1430
|
+
credential: s.credential ?? "",
|
|
1431
|
+
}));
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Answers your WebRTC SDP offer and starts streaming the page as a video
|
|
1435
|
+
* track, returning the SDP answer to set as your peer's remote description.
|
|
1436
|
+
* The browser is the answerer; you are the offerer (see
|
|
1437
|
+
* {@link getStreamConfig} for the credentials to build the offer).
|
|
1438
|
+
*
|
|
1439
|
+
* @param offerSdp - your `RTCPeerConnection`'s SDP offer
|
|
1440
|
+
*
|
|
1441
|
+
* @returns the SDP answer to apply as the remote description
|
|
1442
|
+
*
|
|
1443
|
+
* @throws UNKNOWN_ERROR - the offer was empty, TURN is unconfigured, or the
|
|
1444
|
+
* browser could not negotiate the stream
|
|
1445
|
+
*
|
|
1446
|
+
* @example
|
|
1447
|
+
* const answer = await browser.startStream(offer.sdp);
|
|
1448
|
+
* await pc.setRemoteDescription({ type: "answer", sdp: answer });
|
|
1449
|
+
*/
|
|
1450
|
+
async startStream(offerSdp) {
|
|
1451
|
+
const resp = await this.client.startStream(create(StartStreamRequestSchema, {
|
|
1452
|
+
sessionId: this.sessionId,
|
|
1453
|
+
apiKey: this.apiKey,
|
|
1454
|
+
offerSdp,
|
|
1455
|
+
}));
|
|
1456
|
+
return resp.answerSdp;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Tears down the live video stream for the session's page. Safe to call even
|
|
1460
|
+
* if no stream is running.
|
|
1461
|
+
*
|
|
1462
|
+
* @throws UNKNOWN_ERROR - the stream could not be stopped
|
|
1463
|
+
*
|
|
1464
|
+
* @example
|
|
1465
|
+
* await browser.stopStream();
|
|
1466
|
+
*/
|
|
1467
|
+
async stopStream() {
|
|
1468
|
+
await this.client.stopStream(create(StopStreamRequestSchema, {
|
|
1469
|
+
sessionId: this.sessionId,
|
|
1470
|
+
apiKey: this.apiKey,
|
|
1471
|
+
}));
|
|
1472
|
+
}
|
|
1473
|
+
// ──────────────────────────────────────────────────────────────────
|
|
1474
|
+
// Reactions
|
|
1475
|
+
// ──────────────────────────────────────────────────────────────────
|
|
1476
|
+
/**
|
|
1477
|
+
* Registers a one-shot "reaction": a background poller (one shared loop per
|
|
1478
|
+
* page) watches for the `match` locator and, as soon as it matches, clicks it
|
|
1479
|
+
* with the full smart-click machinery (scroll, human path, occlusion gate,
|
|
1480
|
+
* evade) — then removes itself. The poller yields to any in-flight input
|
|
1481
|
+
* action and only fires while the pointer is idle, so a reaction naturally
|
|
1482
|
+
* slots into the gaps of a retrying foreground action (e.g. it dismisses a
|
|
1483
|
+
* newsletter modal blocking a {@link CloudBrowser.click}, after which the
|
|
1484
|
+
* click's own retry succeeds). Reactions are scoped to the page and torn
|
|
1485
|
+
* down automatically when the page/session ends.
|
|
1486
|
+
*
|
|
1487
|
+
* `match` must be a `css()` or `js()` {@link Locator} — `node()`/`at()` are
|
|
1488
|
+
* rejected. Use `.inAllFrames()` to watch every frame and `.visible(false)`
|
|
1489
|
+
* to opt out of the default visibility gate. Pass a `ReactionOpts` to click a
|
|
1490
|
+
* different target (`on`), change the button/click count, or the poll cadence.
|
|
1491
|
+
*
|
|
1492
|
+
* @param match - the css()/js() locator to watch for
|
|
1493
|
+
* @param opts - optional reaction customization; see {@link ReactionOpts}
|
|
1494
|
+
*
|
|
1495
|
+
* @returns the reactionId (pass to {@link CloudBrowser.removeReaction})
|
|
1496
|
+
*
|
|
1497
|
+
* @throws {@link BrowserScaleError} - `match` (or `opts.on`) is not a css()/js()
|
|
1498
|
+
* locator, or a server/transport error
|
|
1499
|
+
*
|
|
1500
|
+
* @example
|
|
1501
|
+
* // Auto-dismiss a consent button whenever it appears, in any frame.
|
|
1502
|
+
* const id = await browser.addReaction(css("button#accept").inAllFrames());
|
|
1503
|
+
*
|
|
1504
|
+
* @example
|
|
1505
|
+
* // Watch for a newsletter modal, but click its close "X" instead.
|
|
1506
|
+
* const id = await browser.addReaction(css("#newsletter-modal"), {
|
|
1507
|
+
* on: css(".modal-close"),
|
|
1508
|
+
* });
|
|
1509
|
+
*/
|
|
1510
|
+
async addReaction(match, opts) {
|
|
1511
|
+
this.assertReactionLocator("addReaction", match, "match");
|
|
1512
|
+
const req = create(AddReactionRequestSchema, {
|
|
1513
|
+
sessionId: this.sessionId,
|
|
1514
|
+
apiKey: this.apiKey,
|
|
1515
|
+
});
|
|
1516
|
+
if (match.selector)
|
|
1517
|
+
req.matchSelector = match.selector;
|
|
1518
|
+
if (match.jsExpression)
|
|
1519
|
+
req.matchJsExpression = match.jsExpression;
|
|
1520
|
+
if (match.frameId)
|
|
1521
|
+
req.frameId = match.frameId;
|
|
1522
|
+
if (match.visibleFlag !== undefined)
|
|
1523
|
+
req.visible = match.visibleFlag;
|
|
1524
|
+
if (opts?.on) {
|
|
1525
|
+
this.assertReactionLocator("addReaction", opts.on, "on");
|
|
1526
|
+
if (opts.on.selector)
|
|
1527
|
+
req.actionSelector = opts.on.selector;
|
|
1528
|
+
if (opts.on.jsExpression)
|
|
1529
|
+
req.actionJsExpression = opts.on.jsExpression;
|
|
1530
|
+
}
|
|
1531
|
+
if (opts?.button)
|
|
1532
|
+
req.button = opts.button;
|
|
1533
|
+
if (opts?.clickCount)
|
|
1534
|
+
req.clickCount = opts.clickCount;
|
|
1535
|
+
if (opts?.intervalMs)
|
|
1536
|
+
req.interval = opts.intervalMs;
|
|
1537
|
+
const resp = await this.client.addReaction(req);
|
|
1538
|
+
return resp.reactionId;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Removes a pending reaction by id. Returns `false` if the reaction had
|
|
1542
|
+
* already fired (one-shot) or was never registered.
|
|
1543
|
+
*
|
|
1544
|
+
* @param reactionId - id returned by {@link CloudBrowser.addReaction}
|
|
1545
|
+
*
|
|
1546
|
+
* @returns true if a pending reaction with this id existed and was removed
|
|
1547
|
+
*
|
|
1548
|
+
* @example
|
|
1549
|
+
* const removed = await browser.removeReaction(id);
|
|
1550
|
+
*/
|
|
1551
|
+
async removeReaction(reactionId) {
|
|
1552
|
+
const resp = await this.client.removeReaction(create(RemoveReactionRequestSchema, {
|
|
1553
|
+
sessionId: this.sessionId,
|
|
1554
|
+
apiKey: this.apiKey,
|
|
1555
|
+
reactionId,
|
|
1556
|
+
}));
|
|
1557
|
+
return resp.removed;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Returns the still-pending reactions registered for the current page.
|
|
1561
|
+
* Reactions that have already fired (one-shot) are not included.
|
|
1562
|
+
*
|
|
1563
|
+
* @returns the pending reactions for the page
|
|
1564
|
+
*
|
|
1565
|
+
* @example
|
|
1566
|
+
* const pending = await browser.listReactions();
|
|
1567
|
+
* for (const r of pending) console.log(r.reactionId, r.matchSelector);
|
|
1568
|
+
*/
|
|
1569
|
+
async listReactions() {
|
|
1570
|
+
const resp = await this.client.listReactions(create(ListReactionsRequestSchema, {
|
|
1571
|
+
sessionId: this.sessionId,
|
|
1572
|
+
apiKey: this.apiKey,
|
|
1573
|
+
}));
|
|
1574
|
+
return resp.reactions.map((r) => ({
|
|
1575
|
+
reactionId: r.reactionId,
|
|
1576
|
+
matchSelector: r.matchSelector ?? "",
|
|
1577
|
+
matchJsExpression: r.matchJsExpression ?? "",
|
|
1578
|
+
actionSelector: r.actionSelector ?? "",
|
|
1579
|
+
actionJsExpression: r.actionJsExpression ?? "",
|
|
1580
|
+
frameId: r.frameId,
|
|
1581
|
+
visible: r.visible,
|
|
1582
|
+
}));
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* @internal — a reaction match/action must be a css()/js() locator: node()
|
|
1586
|
+
* and at() are rejected (a reaction watches for a condition, like a wait).
|
|
1587
|
+
*/
|
|
1588
|
+
assertReactionLocator(cmd, l, role) {
|
|
1589
|
+
if (l.backendNodeId !== 0 || l.x !== undefined || l.y !== undefined) {
|
|
1590
|
+
throw new BrowserScaleError(`browserscale.${cmd}: ${role} must be a css()/js() locator (node()/at() are not valid)`);
|
|
1591
|
+
}
|
|
1592
|
+
if (!l.selector && !l.jsExpression) {
|
|
1593
|
+
throw new BrowserScaleError(`browserscale.${cmd}: ${role} must have a CSS selector or JS expression`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1361
1596
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DragResult, ElementResult, OccluderInfo, SelectOptionResult, WaitConditionStatus, WaitResult } from "./types.ts";
|
|
1
|
+
import type { DragResult, ElementRef, ElementResult, OccluderInfo, SelectOptionResult, WaitConditionStatus, WaitResult } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* BrowserScaleError is the base class for every error thrown by the SDK. It
|
|
4
4
|
* wraps either:
|
|
@@ -60,21 +60,48 @@ export declare class ClickError extends BrowserScaleError {
|
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* FillError is thrown by {@link CloudBrowser.fill} when the field could not be
|
|
63
|
-
* focused/typed.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* focused/typed. The click-phase codes (`"not_found"`,
|
|
64
|
+
* `"occluded_no_reachable_point"`, `"occluded_after_evade"`) mirror the
|
|
65
|
+
* underlying focus click, with diagnostics under `clickError`. The focus codes
|
|
66
|
+
* are `"focus_stolen"` (another element took focus — `focusedElement` names it;
|
|
67
|
+
* fill is strictly target-bound and will not type into the thief) and
|
|
68
|
+
* `"focus_lost"` (focus left the target and nothing is focused). For untargeted
|
|
69
|
+
* stream typing that lets focus move (e.g. OTP), use {@link CloudBrowser.type}.
|
|
66
70
|
*/
|
|
67
71
|
export declare class FillError extends BrowserScaleError {
|
|
68
|
-
/**
|
|
72
|
+
/** Machine-stable failure code (see the class doc for the full set). */
|
|
69
73
|
readonly code: string;
|
|
70
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The underlying click-core failure that prevented focusing/typing. Present
|
|
76
|
+
* for the click-phase codes; undefined for `"focus_stolen"`/`"focus_lost"`.
|
|
77
|
+
*/
|
|
71
78
|
readonly clickError?: ClickError;
|
|
79
|
+
/**
|
|
80
|
+
* Node that held focus when fill gave up (0 if nothing was focused), for the
|
|
81
|
+
* `"focus_stolen"`/`"focus_lost"` codes.
|
|
82
|
+
*/
|
|
83
|
+
readonly focusedBackendNodeId?: number;
|
|
84
|
+
/**
|
|
85
|
+
* The element that grabbed focus instead of the target (`"focus_stolen"`), so
|
|
86
|
+
* you can act on it (e.g. a consent button).
|
|
87
|
+
*/
|
|
88
|
+
readonly focusedElement?: ElementRef;
|
|
89
|
+
/**
|
|
90
|
+
* The fill target's own state at the point of failure (the focus codes):
|
|
91
|
+
* whether it is still an editable text sink and its current text length.
|
|
92
|
+
*/
|
|
93
|
+
readonly targetEditable?: boolean;
|
|
94
|
+
readonly targetValueLength?: number;
|
|
72
95
|
/** Resolved element + coordinates at the failed action (success is false). */
|
|
73
96
|
readonly result: ElementResult;
|
|
74
97
|
constructor(init: {
|
|
75
98
|
code: string;
|
|
76
99
|
message: string;
|
|
77
100
|
clickError?: ClickError;
|
|
101
|
+
focusedBackendNodeId?: number;
|
|
102
|
+
focusedElement?: ElementRef;
|
|
103
|
+
targetEditable?: boolean;
|
|
104
|
+
targetValueLength?: number;
|
|
78
105
|
result: ElementResult;
|
|
79
106
|
});
|
|
80
107
|
}
|
package/dist/errors.js
CHANGED
|
@@ -48,9 +48,13 @@ export class ClickError extends BrowserScaleError {
|
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* FillError is thrown by {@link CloudBrowser.fill} when the field could not be
|
|
51
|
-
* focused/typed.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* focused/typed. The click-phase codes (`"not_found"`,
|
|
52
|
+
* `"occluded_no_reachable_point"`, `"occluded_after_evade"`) mirror the
|
|
53
|
+
* underlying focus click, with diagnostics under `clickError`. The focus codes
|
|
54
|
+
* are `"focus_stolen"` (another element took focus — `focusedElement` names it;
|
|
55
|
+
* fill is strictly target-bound and will not type into the thief) and
|
|
56
|
+
* `"focus_lost"` (focus left the target and nothing is focused). For untargeted
|
|
57
|
+
* stream typing that lets focus move (e.g. OTP), use {@link CloudBrowser.type}.
|
|
54
58
|
*/
|
|
55
59
|
export class FillError extends BrowserScaleError {
|
|
56
60
|
constructor(init) {
|
|
@@ -58,6 +62,10 @@ export class FillError extends BrowserScaleError {
|
|
|
58
62
|
this.name = "FillError";
|
|
59
63
|
this.code = init.code;
|
|
60
64
|
this.clickError = init.clickError;
|
|
65
|
+
this.focusedBackendNodeId = init.focusedBackendNodeId;
|
|
66
|
+
this.focusedElement = init.focusedElement;
|
|
67
|
+
this.targetEditable = init.targetEditable;
|
|
68
|
+
this.targetValueLength = init.targetValueLength;
|
|
61
69
|
this.result = init.result;
|
|
62
70
|
}
|
|
63
71
|
}
|