wrc-ts 0.1.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.
@@ -0,0 +1,250 @@
1
+ // Internal proto ↔ TS helpers used by client.ts. Anything in here is
2
+ // implementation detail and is NOT exported from the package entrypoints.
3
+ import { create } from "@bufbuild/protobuf";
4
+ import { HeaderModificationSchema, CookiePartitionKeySchema, CookieParamSchema, HeaderSchema, StorageItemSchema, StorageOriginEntrySchema, } from "../gen/wrc_pb.js";
5
+ import { pickFrame } from "../locator.js";
6
+ export function elementFields(target, optsInFrame) {
7
+ const out = {};
8
+ if (target.selector)
9
+ out.selector = target.selector;
10
+ if (target.jsExpression)
11
+ out.jsExpression = target.jsExpression;
12
+ if (target.backendNodeId)
13
+ out.backendNodeId = target.backendNodeId;
14
+ const fid = pickFrame(optsInFrame, target);
15
+ if (fid)
16
+ out.frameId = fid;
17
+ if (target.x !== undefined)
18
+ out.x = target.x;
19
+ if (target.y !== undefined)
20
+ out.y = target.y;
21
+ return out;
22
+ }
23
+ // ──────────────────────────────────────────────────────────────────────
24
+ // Rect
25
+ // ──────────────────────────────────────────────────────────────────────
26
+ export function rectFromProto(r) {
27
+ if (!r)
28
+ return { x: 0, y: 0, width: 0, height: 0 };
29
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
30
+ }
31
+ // ──────────────────────────────────────────────────────────────────────
32
+ // Element / Drag / Wait results
33
+ // ──────────────────────────────────────────────────────────────────────
34
+ export function elementResultFromProto(r) {
35
+ return {
36
+ success: r.success,
37
+ frameId: r.frameId,
38
+ backendNodeId: r.backendNodeId,
39
+ isVisible: r.isVisible,
40
+ bounds: rectFromProto(r.bounds),
41
+ rootX: r.rootX,
42
+ rootY: r.rootY,
43
+ };
44
+ }
45
+ export function dragResultFromProto(r) {
46
+ return {
47
+ success: r.success,
48
+ frameId: r.frameId,
49
+ backendNodeId: r.backendNodeId,
50
+ startX: r.startX,
51
+ startY: r.startY,
52
+ endX: r.endX,
53
+ endY: r.endY,
54
+ };
55
+ }
56
+ export function waitResultFromProto(r) {
57
+ return {
58
+ index: r.index,
59
+ frameId: r.frameId,
60
+ backendNodeId: r.backendNodeId,
61
+ isVisible: r.isVisible,
62
+ bounds: rectFromProto(r.bounds),
63
+ };
64
+ }
65
+ // ──────────────────────────────────────────────────────────────────────
66
+ // FrameInfo / PageInfo
67
+ // ──────────────────────────────────────────────────────────────────────
68
+ export function frameInfoFromProto(f) {
69
+ return {
70
+ frameId: f.frameId,
71
+ url: f.url,
72
+ isOOPIF: f.isOopif,
73
+ hasJSContext: f.hasJsContext,
74
+ isLoading: f.isLoading,
75
+ isVisible: f.isVisible,
76
+ absoluteRect: rectFromProto(f.absoluteRect),
77
+ relativeRect: rectFromProto(f.relativeRect),
78
+ children: f.children.map(frameInfoFromProto),
79
+ };
80
+ }
81
+ export function pageInfoFromProto(p) {
82
+ return {
83
+ pageId: p.pageId,
84
+ browserContextId: p.browserContextId,
85
+ url: p.url,
86
+ title: p.title,
87
+ viewport: rectFromProto(p.viewport),
88
+ frameTree: p.frameTree
89
+ ? frameInfoFromProto(p.frameTree)
90
+ : {},
91
+ };
92
+ }
93
+ // ──────────────────────────────────────────────────────────────────────
94
+ // Headers
95
+ // ──────────────────────────────────────────────────────────────────────
96
+ export function headerFromProto(h) {
97
+ return { name: h.name, value: h.value };
98
+ }
99
+ export function headersToProto(headers) {
100
+ if (!headers)
101
+ return [];
102
+ return headers.map(h => {
103
+ const msg = create(HeaderSchema);
104
+ msg.name = h.name;
105
+ msg.value = h.value;
106
+ return msg;
107
+ });
108
+ }
109
+ // ──────────────────────────────────────────────────────────────────────
110
+ // Intercepted request / response
111
+ // ──────────────────────────────────────────────────────────────────────
112
+ export function interceptedRequestFromProto(r) {
113
+ if (!r)
114
+ return null;
115
+ return {
116
+ method: r.method,
117
+ url: r.url,
118
+ headers: r.headers.map(headerFromProto),
119
+ body: r.body,
120
+ resourceType: r.resourceType,
121
+ };
122
+ }
123
+ export function interceptedResponseFromProto(r) {
124
+ if (!r)
125
+ return null;
126
+ return {
127
+ url: r.url,
128
+ statusCode: r.statusCode,
129
+ headers: r.headers.map(headerFromProto),
130
+ body: r.body,
131
+ };
132
+ }
133
+ // ──────────────────────────────────────────────────────────────────────
134
+ // Cookies
135
+ // ──────────────────────────────────────────────────────────────────────
136
+ export function cookieParamFromProto(c) {
137
+ return {
138
+ name: c.name,
139
+ value: c.value,
140
+ url: c.url,
141
+ domain: c.domain,
142
+ path: c.path,
143
+ secure: c.secure,
144
+ httpOnly: c.httpOnly,
145
+ sameSite: c.sameSite,
146
+ expires: c.expires,
147
+ priority: c.priority,
148
+ sourceScheme: c.sourceScheme,
149
+ sourcePort: c.sourcePort,
150
+ partitionKey: c.partitionKey
151
+ ? {
152
+ topLevelSite: c.partitionKey.topLevelSite,
153
+ hasCrossSiteAncestor: c.partitionKey.hasCrossSiteAncestor,
154
+ }
155
+ : undefined,
156
+ };
157
+ }
158
+ export function cookieParamsToProto(cookies) {
159
+ return cookies.map(c => {
160
+ const msg = create(CookieParamSchema);
161
+ msg.name = c.name;
162
+ msg.value = c.value;
163
+ if (c.url !== undefined)
164
+ msg.url = c.url;
165
+ msg.domain = c.domain;
166
+ msg.path = c.path;
167
+ if (c.secure !== undefined)
168
+ msg.secure = c.secure;
169
+ if (c.httpOnly !== undefined)
170
+ msg.httpOnly = c.httpOnly;
171
+ if (c.sameSite !== undefined)
172
+ msg.sameSite = c.sameSite;
173
+ if (c.expires !== undefined)
174
+ msg.expires = c.expires;
175
+ if (c.priority !== undefined)
176
+ msg.priority = c.priority;
177
+ if (c.sourceScheme !== undefined)
178
+ msg.sourceScheme = c.sourceScheme;
179
+ if (c.sourcePort !== undefined)
180
+ msg.sourcePort = c.sourcePort;
181
+ if (c.partitionKey !== undefined) {
182
+ msg.partitionKey = create(CookiePartitionKeySchema);
183
+ msg.partitionKey.topLevelSite = c.partitionKey.topLevelSite;
184
+ msg.partitionKey.hasCrossSiteAncestor = c.partitionKey.hasCrossSiteAncestor;
185
+ }
186
+ return msg;
187
+ });
188
+ }
189
+ // ──────────────────────────────────────────────────────────────────────
190
+ // Storage (localStorage)
191
+ // ──────────────────────────────────────────────────────────────────────
192
+ export function storageEntryFromProto(e) {
193
+ return {
194
+ origin: e.origin,
195
+ items: e.items.map(it => ({ key: it.key, value: it.value })),
196
+ };
197
+ }
198
+ export function storageEntriesToProto(storage) {
199
+ return storage.map(e => {
200
+ const msg = create(StorageOriginEntrySchema);
201
+ msg.origin = e.origin;
202
+ msg.items = e.items.map(it => {
203
+ const item = create(StorageItemSchema);
204
+ item.key = it.key;
205
+ item.value = it.value;
206
+ return item;
207
+ });
208
+ return msg;
209
+ });
210
+ }
211
+ // ──────────────────────────────────────────────────────────────────────
212
+ // Network — patterns + header modifications
213
+ // ──────────────────────────────────────────────────────────────────────
214
+ /**
215
+ * splitRequestPatterns turns a RequestPattern[] into the proto's parallel
216
+ * URL + abort-flag slices. When no pattern has abort set, the aborts
217
+ * slice is returned as an empty array so it stays off the wire.
218
+ */
219
+ export function splitRequestPatterns(patterns) {
220
+ const urls = new Array(patterns.length);
221
+ let any = false;
222
+ for (let i = 0; i < patterns.length; i++) {
223
+ urls[i] = patterns[i].url;
224
+ if (patterns[i].abort)
225
+ any = true;
226
+ }
227
+ if (!any)
228
+ return { urls, aborts: [] };
229
+ const aborts = new Array(patterns.length);
230
+ for (let i = 0; i < patterns.length; i++) {
231
+ aborts[i] = patterns[i].abort ? 1 : 0;
232
+ }
233
+ return { urls, aborts };
234
+ }
235
+ export function headerModsToProto(mods) {
236
+ if (!mods)
237
+ return [];
238
+ return mods.map(m => {
239
+ const msg = create(HeaderModificationSchema);
240
+ msg.name = m.name;
241
+ if (m.value !== undefined)
242
+ msg.value = m.value;
243
+ msg.action = m.action;
244
+ if (m.before)
245
+ msg.before = m.before;
246
+ if (m.after)
247
+ msg.after = m.after;
248
+ return msg;
249
+ });
250
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * AllFrames is the WRC.pdl sentinel for "match in every frame on the page".
3
+ * Use it for any field documented as accepting a frameId — both
4
+ * Locator.inFrame and *Opts.inFrame.
5
+ */
6
+ export declare const AllFrames = "ALL_FRAMES";
7
+ /**
8
+ * Locator is the universal "what element / what condition" type. It is used
9
+ * both as a wait condition (passed to wait()/waitAny()) and as a target for
10
+ * element actions (passed to click(), fill(), …).
11
+ *
12
+ * Not every field is meaningful in every context:
13
+ * - selector / jsExpression → both wait and actions
14
+ * - backendNodeId → actions only (wait rejects it)
15
+ * - visible / steadyMs → wait only (silently ignored by actions)
16
+ * - x / y → actions only (wait rejects it)
17
+ * - frameId → both, may be overridden by call-level
18
+ * opts.inFrame
19
+ *
20
+ * Use the css() / js() / node() / at() constructors instead of building
21
+ * this class by hand.
22
+ *
23
+ * Modifiers (visible, steady, inFrame, inAllFrames) are immutable — they
24
+ * return a new Locator and leave the original untouched, so it's safe to
25
+ * share a base locator across calls.
26
+ */
27
+ export declare class Locator {
28
+ readonly selector: string;
29
+ readonly jsExpression: string;
30
+ readonly backendNodeId: number;
31
+ readonly frameId: string;
32
+ readonly visibleFlag?: boolean;
33
+ readonly steadyMs?: number;
34
+ readonly x?: number;
35
+ readonly y?: number;
36
+ private constructor();
37
+ /** Internal — clone the locator with one or more fields overridden. */
38
+ private with;
39
+ /**
40
+ * Enforces or disables the visibility check for this Locator's wait
41
+ * condition.
42
+ *
43
+ * Pass `false` to opt out of the default `DefaultVisible` (true). Has
44
+ * no effect when used as an action target — actions never check
45
+ * visibility before dispatching.
46
+ *
47
+ * @param v - true to require visibility, false to skip the check
48
+ *
49
+ * @returns a new Locator with the override applied
50
+ *
51
+ * @example
52
+ * await browser.wait(css("#hidden").visible(false));
53
+ */
54
+ visible(v: boolean): Locator;
55
+ /**
56
+ * Requires the element to keep a stable position and size for at least
57
+ * `ms` milliseconds before the wait matches.
58
+ *
59
+ * Pass 0 to disable the default `DefaultSteadyMs` (500). Has no effect
60
+ * for js() expressions that return a non-Element value, nor when used
61
+ * as an action target.
62
+ *
63
+ * @param ms - steady-state duration in milliseconds; 0 disables
64
+ *
65
+ * @returns a new Locator with the override applied
66
+ *
67
+ * @example
68
+ * await browser.wait(css(".banner").steady(0));
69
+ */
70
+ steady(ms: number): Locator;
71
+ /**
72
+ * Scopes this Locator to a specific frameId.
73
+ *
74
+ * Use the frameId from a previous result or {@link CloudBrowser.getPages}
75
+ * to target elements inside a known iframe.
76
+ *
77
+ * @param frameId - id of the frame to scope to
78
+ *
79
+ * @returns a new Locator scoped to that frame
80
+ *
81
+ * @example
82
+ * const pages = await browser.getPages();
83
+ * const iframeId = pages[0].frameTree.children[0].frameId;
84
+ * await browser.click(css("button").inFrame(iframeId));
85
+ */
86
+ inFrame(frameId: string): Locator;
87
+ /**
88
+ * Scopes this Locator to every frame.
89
+ *
90
+ * Equivalent to `.inFrame(AllFrames)`. Use this when an element might
91
+ * appear inside any of several frames and you do not want to enumerate
92
+ * them.
93
+ *
94
+ * @returns a new Locator scoped to all frames
95
+ *
96
+ * @example
97
+ * await browser.wait(css("button.consent").inAllFrames());
98
+ */
99
+ inAllFrames(): Locator;
100
+ /** @internal — used by action methods to validate at send time. */
101
+ validateTarget(cmd: string, allowCoords: boolean): void;
102
+ /** @internal */
103
+ static _css(selector: string): Locator;
104
+ /** @internal */
105
+ static _js(expression: string): Locator;
106
+ /** @internal */
107
+ static _node(backendNodeId: number): Locator;
108
+ /** @internal */
109
+ static _at(x: number, y: number): Locator;
110
+ }
111
+ /**
112
+ * css waits for / targets an element matching the given CSS selector.
113
+ *
114
+ * When used in {@link CloudBrowser.wait}/{@link CloudBrowser.waitAny}, the
115
+ * returned Locator carries the SDK defaults `DefaultVisible` (true) and
116
+ * `DefaultSteadyMs` (500). Override per call with `.visible(false)` /
117
+ * `.steady(ms)` (use `.steady(0)` to disable the steady check).
118
+ *
119
+ * When used as an action target (click, fill, …) the visible/steady
120
+ * fields are ignored — there are no corresponding fields on the action
121
+ * requests.
122
+ *
123
+ * @param selector - CSS selector matching the element
124
+ *
125
+ * @returns Locator usable as a wait condition or as an action target
126
+ *
127
+ * @example
128
+ * // As a wait condition.
129
+ * await browser.wait(css("button.submit"));
130
+ * // As an action target.
131
+ * await browser.click(css("button.submit"));
132
+ */
133
+ export declare function css(selector: string): Locator;
134
+ /**
135
+ * js waits for / targets the result of a JavaScript expression.
136
+ *
137
+ * Same wait defaults as {@link css} (`DefaultVisible`=true,
138
+ * `DefaultSteadyMs`=500); these only apply when the expression returns a
139
+ * DOM Element. For non-Element truthy values (boolean, string, number,
140
+ * plain object) both fields are no-ops and the condition matches as soon
141
+ * as the value is truthy. Use `.visible(false)` / `.steady(0)` to opt out.
142
+ *
143
+ * @param expression - JavaScript expression evaluated in the target frame
144
+ *
145
+ * @returns Locator usable as a wait condition or as an action target
146
+ *
147
+ * @example
148
+ * await browser.wait(js("window.__ready === true"));
149
+ */
150
+ export declare function js(expression: string): Locator;
151
+ /**
152
+ * node targets an element by its DevTools backendNodeId.
153
+ *
154
+ * Use this when you already have a backendNodeId from a previous result
155
+ * (e.g. a wait or evaluate result) and want to act on the exact same
156
+ * element without re-resolving by selector. Action-only — using it in
157
+ * wait() throws at send time.
158
+ *
159
+ * @param backendNodeId - DevTools backendNodeId of the target element
160
+ *
161
+ * @returns Locator usable only as an action target
162
+ *
163
+ * @example
164
+ * const r = await browser.click(css("button.open"));
165
+ * await browser.click(node(r.backendNodeId));
166
+ */
167
+ export declare function node(backendNodeId: number): Locator;
168
+ /**
169
+ * at targets viewport coordinates instead of an element.
170
+ *
171
+ * Useful for clicking inside a canvas, hovering decorative regions, or
172
+ * dispatching events at synthetic positions. Action-only — using it in
173
+ * wait() throws at send time. Note that only click and moveTo accept at;
174
+ * scrollTo, drag, fill and select all require a real element.
175
+ *
176
+ * @param x - viewport-relative x in CSS pixels
177
+ * @param y - viewport-relative y in CSS pixels
178
+ *
179
+ * @returns Locator usable only as an action target
180
+ *
181
+ * @example
182
+ * // Click at canvas-relative coordinates.
183
+ * await browser.click(at(120, 240));
184
+ */
185
+ export declare function at(x: number, y: number): Locator;
186
+ /**
187
+ * @internal — returns the frame to send on the wire: opts.inFrame wins
188
+ * over the locator's own frameId. Empty everywhere → undefined (server
189
+ * uses main frame).
190
+ */
191
+ export declare function pickFrame(optsFrame: string | undefined, locator: Locator): string | undefined;
@@ -0,0 +1,255 @@
1
+ import { DefaultSteadyMs, DefaultVisible } from "./defaults.js";
2
+ import { WRCError } from "./errors.js";
3
+ /**
4
+ * AllFrames is the WRC.pdl sentinel for "match in every frame on the page".
5
+ * Use it for any field documented as accepting a frameId — both
6
+ * Locator.inFrame and *Opts.inFrame.
7
+ */
8
+ export const AllFrames = "ALL_FRAMES";
9
+ /**
10
+ * Locator is the universal "what element / what condition" type. It is used
11
+ * both as a wait condition (passed to wait()/waitAny()) and as a target for
12
+ * element actions (passed to click(), fill(), …).
13
+ *
14
+ * Not every field is meaningful in every context:
15
+ * - selector / jsExpression → both wait and actions
16
+ * - backendNodeId → actions only (wait rejects it)
17
+ * - visible / steadyMs → wait only (silently ignored by actions)
18
+ * - x / y → actions only (wait rejects it)
19
+ * - frameId → both, may be overridden by call-level
20
+ * opts.inFrame
21
+ *
22
+ * Use the css() / js() / node() / at() constructors instead of building
23
+ * this class by hand.
24
+ *
25
+ * Modifiers (visible, steady, inFrame, inAllFrames) are immutable — they
26
+ * return a new Locator and leave the original untouched, so it's safe to
27
+ * share a base locator across calls.
28
+ */
29
+ export class Locator {
30
+ constructor(init) {
31
+ this.selector = "";
32
+ this.jsExpression = "";
33
+ this.backendNodeId = 0;
34
+ this.frameId = "";
35
+ Object.assign(this, init);
36
+ }
37
+ /** Internal — clone the locator with one or more fields overridden. */
38
+ with(patch) {
39
+ return new Locator({
40
+ selector: this.selector,
41
+ jsExpression: this.jsExpression,
42
+ backendNodeId: this.backendNodeId,
43
+ frameId: this.frameId,
44
+ visibleFlag: this.visibleFlag,
45
+ steadyMs: this.steadyMs,
46
+ x: this.x,
47
+ y: this.y,
48
+ ...patch,
49
+ });
50
+ }
51
+ // ── Modifiers (return new Locator) ──────────────────────────────────
52
+ /**
53
+ * Enforces or disables the visibility check for this Locator's wait
54
+ * condition.
55
+ *
56
+ * Pass `false` to opt out of the default `DefaultVisible` (true). Has
57
+ * no effect when used as an action target — actions never check
58
+ * visibility before dispatching.
59
+ *
60
+ * @param v - true to require visibility, false to skip the check
61
+ *
62
+ * @returns a new Locator with the override applied
63
+ *
64
+ * @example
65
+ * await browser.wait(css("#hidden").visible(false));
66
+ */
67
+ visible(v) {
68
+ return this.with({ visibleFlag: v });
69
+ }
70
+ /**
71
+ * Requires the element to keep a stable position and size for at least
72
+ * `ms` milliseconds before the wait matches.
73
+ *
74
+ * Pass 0 to disable the default `DefaultSteadyMs` (500). Has no effect
75
+ * for js() expressions that return a non-Element value, nor when used
76
+ * as an action target.
77
+ *
78
+ * @param ms - steady-state duration in milliseconds; 0 disables
79
+ *
80
+ * @returns a new Locator with the override applied
81
+ *
82
+ * @example
83
+ * await browser.wait(css(".banner").steady(0));
84
+ */
85
+ steady(ms) {
86
+ return this.with({ steadyMs: ms });
87
+ }
88
+ /**
89
+ * Scopes this Locator to a specific frameId.
90
+ *
91
+ * Use the frameId from a previous result or {@link CloudBrowser.getPages}
92
+ * to target elements inside a known iframe.
93
+ *
94
+ * @param frameId - id of the frame to scope to
95
+ *
96
+ * @returns a new Locator scoped to that frame
97
+ *
98
+ * @example
99
+ * const pages = await browser.getPages();
100
+ * const iframeId = pages[0].frameTree.children[0].frameId;
101
+ * await browser.click(css("button").inFrame(iframeId));
102
+ */
103
+ inFrame(frameId) {
104
+ return this.with({ frameId });
105
+ }
106
+ /**
107
+ * Scopes this Locator to every frame.
108
+ *
109
+ * Equivalent to `.inFrame(AllFrames)`. Use this when an element might
110
+ * appear inside any of several frames and you do not want to enumerate
111
+ * them.
112
+ *
113
+ * @returns a new Locator scoped to all frames
114
+ *
115
+ * @example
116
+ * await browser.wait(css("button.consent").inAllFrames());
117
+ */
118
+ inAllFrames() {
119
+ return this.with({ frameId: AllFrames });
120
+ }
121
+ /** @internal — used by action methods to validate at send time. */
122
+ validateTarget(cmd, allowCoords) {
123
+ const hasCoords = this.x !== undefined && this.y !== undefined;
124
+ const hasElement = this.selector !== "" || this.jsExpression !== "" || this.backendNodeId !== 0;
125
+ if (!hasElement && !hasCoords) {
126
+ throw new WRCError(`wrc.${cmd}: target must have selector, JS expression, backendNodeId, or at(x,y)`);
127
+ }
128
+ if (hasCoords && !allowCoords) {
129
+ throw new WRCError(`wrc.${cmd}: at(x,y) is not supported here — use an element locator`);
130
+ }
131
+ }
132
+ // ── Constructors (top-level functions re-export these) ──────────────
133
+ /** @internal */
134
+ static _css(selector) {
135
+ return new Locator({
136
+ selector,
137
+ visibleFlag: DefaultVisible,
138
+ steadyMs: DefaultSteadyMs,
139
+ });
140
+ }
141
+ /** @internal */
142
+ static _js(expression) {
143
+ return new Locator({
144
+ jsExpression: expression,
145
+ visibleFlag: DefaultVisible,
146
+ steadyMs: DefaultSteadyMs,
147
+ });
148
+ }
149
+ /** @internal */
150
+ static _node(backendNodeId) {
151
+ return new Locator({ backendNodeId });
152
+ }
153
+ /** @internal */
154
+ static _at(x, y) {
155
+ return new Locator({ x, y });
156
+ }
157
+ }
158
+ // ──────────────────────────────────────────────────────────────────────
159
+ // Top-level constructors (preferred over Locator.* statics)
160
+ // ──────────────────────────────────────────────────────────────────────
161
+ /**
162
+ * css waits for / targets an element matching the given CSS selector.
163
+ *
164
+ * When used in {@link CloudBrowser.wait}/{@link CloudBrowser.waitAny}, the
165
+ * returned Locator carries the SDK defaults `DefaultVisible` (true) and
166
+ * `DefaultSteadyMs` (500). Override per call with `.visible(false)` /
167
+ * `.steady(ms)` (use `.steady(0)` to disable the steady check).
168
+ *
169
+ * When used as an action target (click, fill, …) the visible/steady
170
+ * fields are ignored — there are no corresponding fields on the action
171
+ * requests.
172
+ *
173
+ * @param selector - CSS selector matching the element
174
+ *
175
+ * @returns Locator usable as a wait condition or as an action target
176
+ *
177
+ * @example
178
+ * // As a wait condition.
179
+ * await browser.wait(css("button.submit"));
180
+ * // As an action target.
181
+ * await browser.click(css("button.submit"));
182
+ */
183
+ export function css(selector) {
184
+ return Locator._css(selector);
185
+ }
186
+ /**
187
+ * js waits for / targets the result of a JavaScript expression.
188
+ *
189
+ * Same wait defaults as {@link css} (`DefaultVisible`=true,
190
+ * `DefaultSteadyMs`=500); these only apply when the expression returns a
191
+ * DOM Element. For non-Element truthy values (boolean, string, number,
192
+ * plain object) both fields are no-ops and the condition matches as soon
193
+ * as the value is truthy. Use `.visible(false)` / `.steady(0)` to opt out.
194
+ *
195
+ * @param expression - JavaScript expression evaluated in the target frame
196
+ *
197
+ * @returns Locator usable as a wait condition or as an action target
198
+ *
199
+ * @example
200
+ * await browser.wait(js("window.__ready === true"));
201
+ */
202
+ export function js(expression) {
203
+ return Locator._js(expression);
204
+ }
205
+ /**
206
+ * node targets an element by its DevTools backendNodeId.
207
+ *
208
+ * Use this when you already have a backendNodeId from a previous result
209
+ * (e.g. a wait or evaluate result) and want to act on the exact same
210
+ * element without re-resolving by selector. Action-only — using it in
211
+ * wait() throws at send time.
212
+ *
213
+ * @param backendNodeId - DevTools backendNodeId of the target element
214
+ *
215
+ * @returns Locator usable only as an action target
216
+ *
217
+ * @example
218
+ * const r = await browser.click(css("button.open"));
219
+ * await browser.click(node(r.backendNodeId));
220
+ */
221
+ export function node(backendNodeId) {
222
+ return Locator._node(backendNodeId);
223
+ }
224
+ /**
225
+ * at targets viewport coordinates instead of an element.
226
+ *
227
+ * Useful for clicking inside a canvas, hovering decorative regions, or
228
+ * dispatching events at synthetic positions. Action-only — using it in
229
+ * wait() throws at send time. Note that only click and moveTo accept at;
230
+ * scrollTo, drag, fill and select all require a real element.
231
+ *
232
+ * @param x - viewport-relative x in CSS pixels
233
+ * @param y - viewport-relative y in CSS pixels
234
+ *
235
+ * @returns Locator usable only as an action target
236
+ *
237
+ * @example
238
+ * // Click at canvas-relative coordinates.
239
+ * await browser.click(at(120, 240));
240
+ */
241
+ export function at(x, y) {
242
+ return Locator._at(x, y);
243
+ }
244
+ /**
245
+ * @internal — returns the frame to send on the wire: opts.inFrame wins
246
+ * over the locator's own frameId. Empty everywhere → undefined (server
247
+ * uses main frame).
248
+ */
249
+ export function pickFrame(optsFrame, locator) {
250
+ if (optsFrame)
251
+ return optsFrame;
252
+ if (locator.frameId)
253
+ return locator.frameId;
254
+ return undefined;
255
+ }