@allwright.dev/core 0.0.25 → 0.0.27
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 +29 -2
- package/dist/index.d.ts +75 -300
- package/dist/index.js +254 -16
- package/package.json +1 -1
- package/proto/core/v1/browser.proto +2 -0
- package/proto/surfaces/web/v1/web.proto +20 -0
package/README.md
CHANGED
|
@@ -3,10 +3,37 @@
|
|
|
3
3
|
High-level TypeScript client for the allwright automation engine.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
6
|
+
import { firefox } from "@allwright.dev/core";
|
|
7
7
|
|
|
8
|
-
const browser = await
|
|
8
|
+
const browser = await firefox.launch();
|
|
9
9
|
const page = browser.page();
|
|
10
10
|
await page.goto("https://example.com");
|
|
11
11
|
await browser.close();
|
|
12
12
|
```
|
|
13
|
+
|
|
14
|
+
Shared config files are stack-agnostic and can live in `allwright.config.yaml` or `allwright.config.json`.
|
|
15
|
+
The shared schema lives at the repo root in `allwright.schema.json`.
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
schemaVersion: 1
|
|
19
|
+
|
|
20
|
+
server:
|
|
21
|
+
addr: 127.0.0.1:50051
|
|
22
|
+
|
|
23
|
+
browser:
|
|
24
|
+
name: firefox
|
|
25
|
+
binary: /Applications/Firefox.app/Contents/MacOS/firefox
|
|
26
|
+
launchOptions:
|
|
27
|
+
timeoutMs: 30000
|
|
28
|
+
|
|
29
|
+
expect:
|
|
30
|
+
timeoutMs: 5000
|
|
31
|
+
intervalMs: 100
|
|
32
|
+
|
|
33
|
+
suites:
|
|
34
|
+
smoke:
|
|
35
|
+
browser:
|
|
36
|
+
name: chromium
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The TypeScript package exports `findConfigFile()`, `loadConfigFile()`, `resolveConfig()`, and `launchConfiguredBrowser()` so runner packages can consume the same config model without inventing language-specific config files.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import grpc from "@grpc/grpc-js";
|
|
2
1
|
export interface LaunchOptions {
|
|
3
|
-
|
|
2
|
+
browserBinary?: string;
|
|
4
3
|
timeoutMs?: number;
|
|
5
4
|
}
|
|
5
|
+
export type BrowserKind = "chromium" | "firefox";
|
|
6
6
|
export interface CommandOptions {
|
|
7
7
|
timeoutMs?: number;
|
|
8
8
|
}
|
|
@@ -76,314 +76,28 @@ export interface PageInfo {
|
|
|
76
76
|
sessionId: string;
|
|
77
77
|
browserSessionId: string;
|
|
78
78
|
}
|
|
79
|
-
interface
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
interface ChromeLaunchedPayload {
|
|
83
|
-
browser?: string;
|
|
84
|
-
note?: string;
|
|
85
|
-
cdpWebsocketUrl?: string;
|
|
86
|
-
userDataDir?: string;
|
|
87
|
-
initialTabSessionId?: string;
|
|
88
|
-
}
|
|
89
|
-
interface BrowserSessionEvent {
|
|
90
|
-
sessionId?: string;
|
|
91
|
-
event?: string;
|
|
92
|
-
chromeLaunched?: ChromeLaunchedPayload;
|
|
93
|
-
tabOpened?: {
|
|
94
|
-
tabSessionId?: string;
|
|
95
|
-
note?: string;
|
|
96
|
-
};
|
|
97
|
-
pong?: {
|
|
98
|
-
message?: string;
|
|
99
|
-
};
|
|
100
|
-
closed?: {
|
|
101
|
-
reason?: string;
|
|
102
|
-
};
|
|
103
|
-
error?: {
|
|
104
|
-
message?: string;
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
interface TabSessionEvent {
|
|
108
|
-
tabSessionId?: string;
|
|
109
|
-
event?: string;
|
|
110
|
-
attached?: {
|
|
111
|
-
note?: string;
|
|
112
|
-
};
|
|
113
|
-
pong?: {
|
|
114
|
-
message?: string;
|
|
115
|
-
};
|
|
116
|
-
closed?: {
|
|
117
|
-
reason?: string;
|
|
118
|
-
};
|
|
119
|
-
error?: {
|
|
120
|
-
message?: string;
|
|
121
|
-
};
|
|
122
|
-
navigated?: {
|
|
123
|
-
url?: string;
|
|
124
|
-
note?: string;
|
|
125
|
-
};
|
|
126
|
-
chromiumBidiInjection?: {
|
|
127
|
-
note?: string;
|
|
128
|
-
bidiSessionId?: string;
|
|
129
|
-
mapperTargetId?: string;
|
|
130
|
-
mapperSessionId?: string;
|
|
131
|
-
packageVersion?: string;
|
|
132
|
-
};
|
|
133
|
-
elementClicked?: {
|
|
134
|
-
cssSelector?: string;
|
|
135
|
-
note?: string;
|
|
136
|
-
bidiSessionId?: string;
|
|
137
|
-
};
|
|
138
|
-
elementCounted?: {
|
|
139
|
-
cssSelector?: string;
|
|
140
|
-
count?: number;
|
|
141
|
-
note?: string;
|
|
142
|
-
};
|
|
143
|
-
elementsHighlighted?: {
|
|
144
|
-
cssSelector?: string;
|
|
145
|
-
count?: number;
|
|
146
|
-
note?: string;
|
|
147
|
-
};
|
|
148
|
-
elementFocused?: {
|
|
149
|
-
cssSelector?: string;
|
|
150
|
-
note?: string;
|
|
151
|
-
};
|
|
152
|
-
elementFilled?: {
|
|
153
|
-
cssSelector?: string;
|
|
154
|
-
value?: string;
|
|
155
|
-
note?: string;
|
|
156
|
-
};
|
|
157
|
-
elementHovered?: {
|
|
158
|
-
cssSelector?: string;
|
|
159
|
-
note?: string;
|
|
160
|
-
};
|
|
161
|
-
keyPressed?: {
|
|
162
|
-
cssSelector?: string;
|
|
163
|
-
key?: string;
|
|
164
|
-
note?: string;
|
|
165
|
-
};
|
|
166
|
-
textContentResolved?: {
|
|
167
|
-
cssSelector?: string;
|
|
168
|
-
text?: string;
|
|
169
|
-
note?: string;
|
|
170
|
-
};
|
|
171
|
-
innerTextResolved?: {
|
|
172
|
-
cssSelector?: string;
|
|
173
|
-
text?: string;
|
|
174
|
-
note?: string;
|
|
175
|
-
};
|
|
176
|
-
selectorWaitSatisfied?: {
|
|
177
|
-
cssSelector?: string;
|
|
178
|
-
visible?: boolean;
|
|
179
|
-
note?: string;
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
interface LaunchChromeRequest {
|
|
183
|
-
launchChrome: {
|
|
184
|
-
chromeBinary?: string;
|
|
185
|
-
retryOptions?: {
|
|
186
|
-
timeoutMs?: number;
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
interface OpenTabRequest {
|
|
191
|
-
openTab: {
|
|
192
|
-
retryOptions?: {
|
|
193
|
-
timeoutMs?: number;
|
|
194
|
-
};
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
interface BrowserPingRequest {
|
|
198
|
-
ping: {
|
|
199
|
-
message: string;
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
interface CloseBrowserRequest {
|
|
203
|
-
close: Record<string, never>;
|
|
204
|
-
}
|
|
205
|
-
interface TabPingRequest {
|
|
206
|
-
browserSessionId: string;
|
|
207
|
-
tabSessionId: string;
|
|
208
|
-
ping: {
|
|
209
|
-
message: string;
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
interface NavigateRequest {
|
|
213
|
-
browserSessionId: string;
|
|
214
|
-
tabSessionId: string;
|
|
215
|
-
navigate: {
|
|
216
|
-
url: string;
|
|
217
|
-
retryOptions?: {
|
|
218
|
-
timeoutMs?: number;
|
|
219
|
-
};
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
interface ClickRequest {
|
|
223
|
-
browserSessionId: string;
|
|
224
|
-
tabSessionId: string;
|
|
225
|
-
clickElement: {
|
|
226
|
-
cssSelector: string;
|
|
227
|
-
retryOptions?: {
|
|
228
|
-
timeoutMs?: number;
|
|
229
|
-
};
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
interface CountRequest {
|
|
233
|
-
browserSessionId: string;
|
|
234
|
-
tabSessionId: string;
|
|
235
|
-
countElements: {
|
|
236
|
-
cssSelector: string;
|
|
237
|
-
retryOptions?: {
|
|
238
|
-
timeoutMs?: number;
|
|
239
|
-
};
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
interface HighlightRequest {
|
|
243
|
-
browserSessionId: string;
|
|
244
|
-
tabSessionId: string;
|
|
245
|
-
highlightElements: {
|
|
246
|
-
cssSelector: string;
|
|
247
|
-
durationMs?: number;
|
|
248
|
-
retryOptions?: {
|
|
249
|
-
timeoutMs?: number;
|
|
250
|
-
};
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
interface FocusRequest {
|
|
254
|
-
browserSessionId: string;
|
|
255
|
-
tabSessionId: string;
|
|
256
|
-
focusElement: {
|
|
257
|
-
cssSelector: string;
|
|
258
|
-
retryOptions?: {
|
|
259
|
-
timeoutMs?: number;
|
|
260
|
-
};
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
interface FillRequest {
|
|
264
|
-
browserSessionId: string;
|
|
265
|
-
tabSessionId: string;
|
|
266
|
-
fillElement: {
|
|
267
|
-
cssSelector: string;
|
|
268
|
-
value: string;
|
|
269
|
-
retryOptions?: {
|
|
270
|
-
timeoutMs?: number;
|
|
271
|
-
};
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
interface HoverRequest {
|
|
275
|
-
browserSessionId: string;
|
|
276
|
-
tabSessionId: string;
|
|
277
|
-
hoverElement: {
|
|
278
|
-
cssSelector: string;
|
|
279
|
-
retryOptions?: {
|
|
280
|
-
timeoutMs?: number;
|
|
281
|
-
};
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
interface PressRequest {
|
|
285
|
-
browserSessionId: string;
|
|
286
|
-
tabSessionId: string;
|
|
287
|
-
pressKey: {
|
|
288
|
-
cssSelector: string;
|
|
289
|
-
key: string;
|
|
290
|
-
text?: string;
|
|
291
|
-
retryOptions?: {
|
|
292
|
-
timeoutMs?: number;
|
|
293
|
-
};
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
interface TextContentRequest {
|
|
297
|
-
browserSessionId: string;
|
|
298
|
-
tabSessionId: string;
|
|
299
|
-
getTextContent: {
|
|
300
|
-
cssSelector: string;
|
|
301
|
-
retryOptions?: {
|
|
302
|
-
timeoutMs?: number;
|
|
303
|
-
};
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
interface InnerTextRequest {
|
|
307
|
-
browserSessionId: string;
|
|
308
|
-
tabSessionId: string;
|
|
309
|
-
getInnerText: {
|
|
310
|
-
cssSelector: string;
|
|
311
|
-
retryOptions?: {
|
|
312
|
-
timeoutMs?: number;
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
interface WaitForSelectorRequest {
|
|
317
|
-
browserSessionId: string;
|
|
318
|
-
tabSessionId: string;
|
|
319
|
-
waitForSelector: {
|
|
320
|
-
cssSelector: string;
|
|
321
|
-
visible?: boolean;
|
|
322
|
-
retryOptions?: {
|
|
323
|
-
timeoutMs?: number;
|
|
324
|
-
};
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
interface CloseTabRequest {
|
|
328
|
-
browserSessionId: string;
|
|
329
|
-
tabSessionId: string;
|
|
330
|
-
close: Record<string, never>;
|
|
331
|
-
}
|
|
332
|
-
type BrowserSessionRequest = LaunchChromeRequest | OpenTabRequest | BrowserPingRequest | CloseBrowserRequest;
|
|
333
|
-
type TabSessionRequest = TabPingRequest | NavigateRequest | ClickRequest | CountRequest | HighlightRequest | FocusRequest | FillRequest | HoverRequest | PressRequest | TextContentRequest | InnerTextRequest | WaitForSelectorRequest | CloseTabRequest;
|
|
334
|
-
type BrowserSessionStream = grpc.ClientDuplexStream<BrowserSessionRequest, BrowserSessionEvent>;
|
|
335
|
-
type PageSessionStream = grpc.ClientDuplexStream<TabSessionRequest, TabSessionEvent>;
|
|
336
|
-
interface EngineServiceClientShape {
|
|
337
|
-
Ping(request: Record<string, never>, callback: (error: grpc.ServiceError | null, response: PingResponse) => void): void;
|
|
338
|
-
BrowserSession(): BrowserSessionStream;
|
|
339
|
-
TabSession(): PageSessionStream;
|
|
340
|
-
close(): void;
|
|
341
|
-
}
|
|
342
|
-
interface RuntimeClient {
|
|
343
|
-
client: EngineServiceClientShape;
|
|
344
|
-
}
|
|
345
|
-
interface BrowserLaunchState {
|
|
346
|
-
runtime: RuntimeClient;
|
|
347
|
-
stream: BrowserSessionStream;
|
|
348
|
-
queue: EventQueue<BrowserSessionEvent>;
|
|
349
|
-
sessionId: string;
|
|
350
|
-
chromeLaunched: ChromeLaunchedPayload;
|
|
351
|
-
}
|
|
352
|
-
declare class EventQueue<T> {
|
|
353
|
-
#private;
|
|
354
|
-
push(item: T): void;
|
|
355
|
-
fail(error: Error): void;
|
|
356
|
-
next(): Promise<T>;
|
|
79
|
+
export interface LocatorInfo {
|
|
80
|
+
page: Page;
|
|
81
|
+
selector: string;
|
|
357
82
|
}
|
|
358
|
-
export
|
|
83
|
+
export interface BrowserType {
|
|
359
84
|
launch(options?: LaunchOptions): Promise<Browser>;
|
|
360
85
|
}
|
|
361
|
-
export
|
|
362
|
-
#private;
|
|
363
|
-
constructor(state: BrowserLaunchState);
|
|
364
|
-
readonly sessionId: string;
|
|
365
|
-
readonly browserName: string;
|
|
366
|
-
readonly launchNote: string;
|
|
367
|
-
readonly cdpWebSocketURL: string;
|
|
368
|
-
readonly userDataDir: string;
|
|
86
|
+
export interface Browser extends BrowserInfo {
|
|
369
87
|
page(): Page;
|
|
370
88
|
initialPage(): Page;
|
|
89
|
+
initialTab(): Page;
|
|
371
90
|
pages(): Page[];
|
|
372
91
|
newPage(options?: CommandOptions): Promise<Page>;
|
|
92
|
+
newTab(options?: CommandOptions): Promise<Page>;
|
|
373
93
|
close(): Promise<void>;
|
|
374
94
|
ping(message?: string): Promise<string>;
|
|
375
95
|
browserInfo(): BrowserInfo;
|
|
376
|
-
initialTab(): Page;
|
|
377
|
-
newTab(options?: CommandOptions): Promise<Page>;
|
|
378
96
|
}
|
|
379
|
-
export
|
|
380
|
-
|
|
381
|
-
constructor(input: PageInfo & {
|
|
382
|
-
runtime: RuntimeClient;
|
|
383
|
-
});
|
|
384
|
-
readonly sessionId: string;
|
|
385
|
-
readonly browserSessionId: string;
|
|
97
|
+
export interface Page extends PageInfo {
|
|
98
|
+
locator(selector: string): Locator;
|
|
386
99
|
goto(url: string, options?: CommandOptions): Promise<NavigateResult>;
|
|
100
|
+
navigate(url: string, options?: CommandOptions): Promise<NavigateResult>;
|
|
387
101
|
click(selector: string, options?: CommandOptions): Promise<ClickResult>;
|
|
388
102
|
count(selector: string, options?: CommandOptions): Promise<CountResult>;
|
|
389
103
|
highlight(selector: string, options?: HighlightOptions): Promise<HighlightResult>;
|
|
@@ -397,12 +111,73 @@ export declare class Page {
|
|
|
397
111
|
close(): Promise<void>;
|
|
398
112
|
ping(message?: string): Promise<string>;
|
|
399
113
|
pageInfo(): PageInfo;
|
|
400
|
-
|
|
114
|
+
}
|
|
115
|
+
export interface Locator {
|
|
116
|
+
readonly page: Page;
|
|
117
|
+
readonly selector: string;
|
|
118
|
+
click(options?: CommandOptions): Promise<ClickResult>;
|
|
119
|
+
count(options?: CommandOptions): Promise<CountResult>;
|
|
120
|
+
highlight(options?: HighlightOptions): Promise<HighlightResult>;
|
|
121
|
+
focus(options?: CommandOptions): Promise<ElementResult>;
|
|
122
|
+
fill(value: string, options?: CommandOptions): Promise<FillResult>;
|
|
123
|
+
hover(options?: CommandOptions): Promise<ElementResult>;
|
|
124
|
+
press(key: string, options?: PressOptions): Promise<PressResult>;
|
|
125
|
+
textContent(options?: CommandOptions): Promise<TextResult>;
|
|
126
|
+
innerText(options?: CommandOptions): Promise<TextResult>;
|
|
127
|
+
waitFor(options?: WaitForSelectorOptions): Promise<WaitForSelectorResult>;
|
|
128
|
+
locator(selector: string): Locator;
|
|
129
|
+
}
|
|
130
|
+
export interface AllwrightConfig {
|
|
131
|
+
schemaVersion?: 1;
|
|
132
|
+
server?: {
|
|
133
|
+
addr?: string;
|
|
134
|
+
};
|
|
135
|
+
browser?: {
|
|
136
|
+
name?: BrowserKind;
|
|
137
|
+
binary?: string;
|
|
138
|
+
launchOptions?: LaunchOptions;
|
|
139
|
+
};
|
|
140
|
+
expect?: RetryConfig;
|
|
141
|
+
suites?: Record<string, AllwrightSuiteConfig>;
|
|
142
|
+
}
|
|
143
|
+
export interface AllwrightSuiteConfig {
|
|
144
|
+
server?: {
|
|
145
|
+
addr?: string;
|
|
146
|
+
};
|
|
147
|
+
browser?: {
|
|
148
|
+
name?: BrowserKind;
|
|
149
|
+
binary?: string;
|
|
150
|
+
launchOptions?: LaunchOptions;
|
|
151
|
+
};
|
|
152
|
+
expect?: RetryConfig;
|
|
153
|
+
}
|
|
154
|
+
export interface RetryConfig {
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
intervalMs?: number;
|
|
157
|
+
}
|
|
158
|
+
export interface ResolvedAllwrightConfig {
|
|
159
|
+
configFilePath: string | null;
|
|
160
|
+
suiteName: string | null;
|
|
161
|
+
serverAddr?: string;
|
|
162
|
+
browserName: BrowserKind;
|
|
163
|
+
browserBinary?: string;
|
|
164
|
+
launchOptions: LaunchOptions;
|
|
165
|
+
expect: RetryConfig;
|
|
166
|
+
}
|
|
167
|
+
export interface ResolveConfigOptions {
|
|
168
|
+
cwd?: string;
|
|
169
|
+
configFile?: string;
|
|
170
|
+
suite?: string;
|
|
401
171
|
}
|
|
402
172
|
export declare const chromium: BrowserType;
|
|
173
|
+
export declare const firefox: BrowserType;
|
|
403
174
|
export type Tab = Page;
|
|
404
175
|
export declare function ping(): Promise<string>;
|
|
405
176
|
export declare function launchChrome(options?: LaunchOptions): Promise<Browser>;
|
|
177
|
+
export declare function launchConfiguredBrowser(config: ResolvedAllwrightConfig): Promise<Browser>;
|
|
178
|
+
export declare function launchBrowser(browserKind: BrowserKind, options?: LaunchOptions): Promise<Browser>;
|
|
406
179
|
export declare function setServerAddr(serverAddr: string): void;
|
|
407
180
|
export declare function shutdown(): Promise<void>;
|
|
408
|
-
export
|
|
181
|
+
export declare function findConfigFile(startDir?: string): string | null;
|
|
182
|
+
export declare function loadConfigFile(configFile: string): AllwrightConfig;
|
|
183
|
+
export declare function resolveConfig(options?: ResolveConfigOptions): ResolvedAllwrightConfig;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import grpc from "@grpc/grpc-js";
|
|
4
5
|
import protoLoader from "@grpc/proto-loader";
|
|
@@ -11,6 +12,14 @@ const PROTO_ROOT = path.join(PACKAGE_ROOT, "proto");
|
|
|
11
12
|
const ENGINE_PROTO_PATH = path.join(PROTO_ROOT, "engine", "v1", "engine.proto");
|
|
12
13
|
let runtimePromise = null;
|
|
13
14
|
let serverAddrOverride = null;
|
|
15
|
+
const CONFIG_FILENAMES = [
|
|
16
|
+
"allwright.config.yaml",
|
|
17
|
+
"allwright.config.yml",
|
|
18
|
+
"allwright.config.json",
|
|
19
|
+
".allwright/config.yaml",
|
|
20
|
+
".allwright/config.yml",
|
|
21
|
+
".allwright/config.json",
|
|
22
|
+
];
|
|
14
23
|
class EventQueue {
|
|
15
24
|
#items = [];
|
|
16
25
|
#waiters = [];
|
|
@@ -44,12 +53,16 @@ class EventQueue {
|
|
|
44
53
|
});
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
|
-
|
|
56
|
+
class BrowserTypeImpl {
|
|
57
|
+
#browserKind;
|
|
58
|
+
constructor(browserKind = "chromium") {
|
|
59
|
+
this.#browserKind = browserKind;
|
|
60
|
+
}
|
|
48
61
|
async launch(options = {}) {
|
|
49
|
-
return
|
|
62
|
+
return launchBrowser(this.#browserKind, options);
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
|
-
|
|
65
|
+
class BrowserImpl {
|
|
53
66
|
#closed = false;
|
|
54
67
|
#runtime;
|
|
55
68
|
#stream;
|
|
@@ -59,10 +72,10 @@ export class Browser {
|
|
|
59
72
|
constructor(state) {
|
|
60
73
|
const browserInfo = {
|
|
61
74
|
sessionId: state.sessionId,
|
|
62
|
-
browserName: state.
|
|
63
|
-
launchNote: state.
|
|
64
|
-
cdpWebSocketURL:
|
|
65
|
-
userDataDir: state.
|
|
75
|
+
browserName: state.launched.browser ?? "",
|
|
76
|
+
launchNote: state.launched.note ?? "",
|
|
77
|
+
cdpWebSocketURL: "",
|
|
78
|
+
userDataDir: state.launched.userDataDir ?? "",
|
|
66
79
|
};
|
|
67
80
|
this.#runtime = state.runtime;
|
|
68
81
|
this.#stream = state.stream;
|
|
@@ -72,7 +85,7 @@ export class Browser {
|
|
|
72
85
|
this.launchNote = browserInfo.launchNote;
|
|
73
86
|
this.cdpWebSocketURL = browserInfo.cdpWebSocketURL;
|
|
74
87
|
this.userDataDir = browserInfo.userDataDir;
|
|
75
|
-
this.#initialPage = this.#createPage(state.
|
|
88
|
+
this.#initialPage = this.#createPage(state.launched.initialTabSessionId ?? "");
|
|
76
89
|
}
|
|
77
90
|
sessionId;
|
|
78
91
|
browserName;
|
|
@@ -161,7 +174,7 @@ export class Browser {
|
|
|
161
174
|
if (existing) {
|
|
162
175
|
return existing;
|
|
163
176
|
}
|
|
164
|
-
const page = new
|
|
177
|
+
const page = new PageImpl({
|
|
165
178
|
runtime: this.#runtime,
|
|
166
179
|
browserSessionId: this.sessionId,
|
|
167
180
|
sessionId,
|
|
@@ -175,7 +188,7 @@ export class Browser {
|
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
190
|
}
|
|
178
|
-
|
|
191
|
+
class PageImpl {
|
|
179
192
|
#runtime;
|
|
180
193
|
#handlePromise = null;
|
|
181
194
|
constructor(input) {
|
|
@@ -185,6 +198,9 @@ export class Page {
|
|
|
185
198
|
}
|
|
186
199
|
sessionId;
|
|
187
200
|
browserSessionId;
|
|
201
|
+
locator(selector) {
|
|
202
|
+
return new LocatorImpl({ page: this, selector });
|
|
203
|
+
}
|
|
188
204
|
async goto(url, options = {}) {
|
|
189
205
|
const handle = await this.#getHandle();
|
|
190
206
|
this.#ensureOpen(handle);
|
|
@@ -578,7 +594,52 @@ export class Page {
|
|
|
578
594
|
return this.#handlePromise;
|
|
579
595
|
}
|
|
580
596
|
}
|
|
581
|
-
|
|
597
|
+
class LocatorImpl {
|
|
598
|
+
page;
|
|
599
|
+
selector;
|
|
600
|
+
constructor(input) {
|
|
601
|
+
this.page = input.page;
|
|
602
|
+
this.selector = input.selector;
|
|
603
|
+
}
|
|
604
|
+
async click(options = {}) {
|
|
605
|
+
return this.page.click(this.selector, options);
|
|
606
|
+
}
|
|
607
|
+
async count(options = {}) {
|
|
608
|
+
return this.page.count(this.selector, options);
|
|
609
|
+
}
|
|
610
|
+
async highlight(options = {}) {
|
|
611
|
+
return this.page.highlight(this.selector, options);
|
|
612
|
+
}
|
|
613
|
+
async focus(options = {}) {
|
|
614
|
+
return this.page.focus(this.selector, options);
|
|
615
|
+
}
|
|
616
|
+
async fill(value, options = {}) {
|
|
617
|
+
return this.page.fill(this.selector, value, options);
|
|
618
|
+
}
|
|
619
|
+
async hover(options = {}) {
|
|
620
|
+
return this.page.hover(this.selector, options);
|
|
621
|
+
}
|
|
622
|
+
async press(key, options = {}) {
|
|
623
|
+
return this.page.press(this.selector, key, options);
|
|
624
|
+
}
|
|
625
|
+
async textContent(options = {}) {
|
|
626
|
+
return this.page.textContent(this.selector, options);
|
|
627
|
+
}
|
|
628
|
+
async innerText(options = {}) {
|
|
629
|
+
return this.page.innerText(this.selector, options);
|
|
630
|
+
}
|
|
631
|
+
async waitFor(options = {}) {
|
|
632
|
+
return this.page.waitForSelector(this.selector, options);
|
|
633
|
+
}
|
|
634
|
+
locator(selector) {
|
|
635
|
+
return new LocatorImpl({
|
|
636
|
+
page: this.page,
|
|
637
|
+
selector: `${this.selector} ${selector}`,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
export const chromium = new BrowserTypeImpl("chromium");
|
|
642
|
+
export const firefox = new BrowserTypeImpl("firefox");
|
|
582
643
|
export async function ping() {
|
|
583
644
|
const runtime = await getRuntime();
|
|
584
645
|
return new Promise((resolve, reject) => {
|
|
@@ -592,24 +653,34 @@ export async function ping() {
|
|
|
592
653
|
});
|
|
593
654
|
}
|
|
594
655
|
export async function launchChrome(options = {}) {
|
|
656
|
+
return launchBrowser("chromium", options);
|
|
657
|
+
}
|
|
658
|
+
export async function launchConfiguredBrowser(config) {
|
|
659
|
+
return launchBrowser(config.browserName, {
|
|
660
|
+
...config.launchOptions,
|
|
661
|
+
browserBinary: config.browserBinary ?? config.launchOptions.browserBinary,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
export async function launchBrowser(browserKind, options = {}) {
|
|
595
665
|
const runtime = await getRuntime();
|
|
596
666
|
const stream = runtime.client.BrowserSession();
|
|
597
667
|
const queue = bindStreamQueue(stream);
|
|
598
668
|
stream.write({
|
|
599
|
-
|
|
600
|
-
|
|
669
|
+
launchBrowser: {
|
|
670
|
+
browserKind: browserKind === "firefox" ? 2 : 1,
|
|
671
|
+
browserBinary: options.browserBinary,
|
|
601
672
|
retryOptions: options.timeoutMs ? { timeoutMs: options.timeoutMs } : undefined,
|
|
602
673
|
},
|
|
603
674
|
});
|
|
604
675
|
while (true) {
|
|
605
676
|
const event = await queue.next();
|
|
606
|
-
if (event.
|
|
607
|
-
return new
|
|
677
|
+
if (event.browserLaunched) {
|
|
678
|
+
return new BrowserImpl({
|
|
608
679
|
runtime,
|
|
609
680
|
stream,
|
|
610
681
|
queue,
|
|
611
682
|
sessionId: event.sessionId ?? "",
|
|
612
|
-
|
|
683
|
+
launched: event.browserLaunched,
|
|
613
684
|
});
|
|
614
685
|
}
|
|
615
686
|
if (event.error?.message) {
|
|
@@ -629,6 +700,55 @@ export async function shutdown() {
|
|
|
629
700
|
runtime.client.close();
|
|
630
701
|
runtimePromise = null;
|
|
631
702
|
}
|
|
703
|
+
export function findConfigFile(startDir = process.cwd()) {
|
|
704
|
+
let currentDir = path.resolve(startDir);
|
|
705
|
+
while (true) {
|
|
706
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
707
|
+
const candidate = path.join(currentDir, filename);
|
|
708
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
709
|
+
return candidate;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const parentDir = path.dirname(currentDir);
|
|
713
|
+
if (parentDir === currentDir) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
currentDir = parentDir;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
export function loadConfigFile(configFile) {
|
|
720
|
+
const resolved = path.resolve(configFile);
|
|
721
|
+
const raw = fs.readFileSync(resolved, "utf8");
|
|
722
|
+
const parsed = parseConfigContents(raw, resolved);
|
|
723
|
+
validateConfigShape(parsed, resolved);
|
|
724
|
+
return parsed;
|
|
725
|
+
}
|
|
726
|
+
export function resolveConfig(options = {}) {
|
|
727
|
+
const configFilePath = options.configFile ? path.resolve(options.configFile) : findConfigFile(options.cwd);
|
|
728
|
+
const fileConfig = configFilePath ? loadConfigFile(configFilePath) : {};
|
|
729
|
+
const suiteName = options.suite?.trim() || null;
|
|
730
|
+
const suiteConfig = suiteName ? fileConfig.suites?.[suiteName] : undefined;
|
|
731
|
+
if (suiteName && !suiteConfig) {
|
|
732
|
+
throw new Error(`allwright config suite "${suiteName}" was not found in ${configFilePath ?? "the resolved config file"}`);
|
|
733
|
+
}
|
|
734
|
+
const serverAddr = suiteConfig?.server?.addr ?? fileConfig.server?.addr;
|
|
735
|
+
const browserName = suiteConfig?.browser?.name ?? fileConfig.browser?.name ?? "chromium";
|
|
736
|
+
const browserBinary = suiteConfig?.browser?.binary ?? fileConfig.browser?.binary;
|
|
737
|
+
const launchOptions = mergeLaunchOptions(fileConfig.browser?.launchOptions, suiteConfig?.browser?.launchOptions);
|
|
738
|
+
const expect = {
|
|
739
|
+
...(fileConfig.expect ?? {}),
|
|
740
|
+
...(suiteConfig?.expect ?? {}),
|
|
741
|
+
};
|
|
742
|
+
return {
|
|
743
|
+
configFilePath,
|
|
744
|
+
suiteName,
|
|
745
|
+
serverAddr,
|
|
746
|
+
browserName,
|
|
747
|
+
browserBinary,
|
|
748
|
+
launchOptions: browserBinary ? { ...launchOptions, browserBinary } : launchOptions,
|
|
749
|
+
expect,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
632
752
|
async function getRuntime() {
|
|
633
753
|
if (!runtimePromise) {
|
|
634
754
|
runtimePromise = Promise.resolve(createRuntime());
|
|
@@ -655,6 +775,124 @@ function configuredServerAddr() {
|
|
|
655
775
|
}
|
|
656
776
|
return normalizeServerAddr(process.env[SERVER_ADDR_ENV_VAR] ?? DEFAULT_SERVER_ADDR);
|
|
657
777
|
}
|
|
778
|
+
function mergeLaunchOptions(base, override) {
|
|
779
|
+
return {
|
|
780
|
+
...(base ?? {}),
|
|
781
|
+
...(override ?? {}),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function validateConfigShape(value, source) {
|
|
785
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
786
|
+
throw new Error(`allwright config ${source} must contain a top-level object`);
|
|
787
|
+
}
|
|
788
|
+
const config = value;
|
|
789
|
+
if (config.schemaVersion !== undefined && config.schemaVersion !== 1) {
|
|
790
|
+
throw new Error(`allwright config ${source} has unsupported schemaVersion ${String(config.schemaVersion)}; expected 1`);
|
|
791
|
+
}
|
|
792
|
+
const browserName = config.browser?.name;
|
|
793
|
+
if (browserName !== undefined && browserName !== "chromium" && browserName !== "firefox") {
|
|
794
|
+
throw new Error(`allwright config ${source} has unsupported browser.name ${String(browserName)}; use "chromium" or "firefox"`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function parseConfigContents(raw, source) {
|
|
798
|
+
const extension = path.extname(source).toLowerCase();
|
|
799
|
+
if (extension === ".json") {
|
|
800
|
+
return JSON.parse(raw);
|
|
801
|
+
}
|
|
802
|
+
if (extension === ".yaml" || extension === ".yml") {
|
|
803
|
+
return parseSimpleYaml(raw, source);
|
|
804
|
+
}
|
|
805
|
+
throw new Error(`unsupported allwright config file extension ${extension || "<none>"} for ${source}`);
|
|
806
|
+
}
|
|
807
|
+
function parseSimpleYaml(raw, source) {
|
|
808
|
+
const root = {};
|
|
809
|
+
const stack = [
|
|
810
|
+
{ indent: -1, value: root },
|
|
811
|
+
];
|
|
812
|
+
for (const [index, originalLine] of raw.split(/\r?\n/).entries()) {
|
|
813
|
+
const lineNumber = index + 1;
|
|
814
|
+
const line = stripYamlComment(originalLine);
|
|
815
|
+
if (!line.trim()) {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const indent = countLeadingSpaces(line);
|
|
819
|
+
if (indent % 2 !== 0) {
|
|
820
|
+
throw new Error(`invalid YAML indentation in ${source}:${lineNumber}; use multiples of 2 spaces`);
|
|
821
|
+
}
|
|
822
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
823
|
+
stack.pop();
|
|
824
|
+
}
|
|
825
|
+
const current = stack[stack.length - 1];
|
|
826
|
+
const trimmed = line.trim();
|
|
827
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
828
|
+
if (separatorIndex <= 0) {
|
|
829
|
+
throw new Error(`invalid YAML mapping in ${source}:${lineNumber}`);
|
|
830
|
+
}
|
|
831
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
832
|
+
const rawValue = trimmed.slice(separatorIndex + 1).trim();
|
|
833
|
+
if (!key) {
|
|
834
|
+
throw new Error(`empty YAML key in ${source}:${lineNumber}`);
|
|
835
|
+
}
|
|
836
|
+
if (!rawValue) {
|
|
837
|
+
const child = {};
|
|
838
|
+
current.value[key] = child;
|
|
839
|
+
stack.push({ indent, value: child });
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
current.value[key] = parseYamlScalar(rawValue, source, lineNumber);
|
|
843
|
+
}
|
|
844
|
+
return root;
|
|
845
|
+
}
|
|
846
|
+
function stripYamlComment(line) {
|
|
847
|
+
let inSingleQuote = false;
|
|
848
|
+
let inDoubleQuote = false;
|
|
849
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
850
|
+
const char = line[index];
|
|
851
|
+
if (char === "'" && !inDoubleQuote) {
|
|
852
|
+
inSingleQuote = !inSingleQuote;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (char === "\"" && !inSingleQuote) {
|
|
856
|
+
inDoubleQuote = !inDoubleQuote;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
if (char === "#" && !inSingleQuote && !inDoubleQuote) {
|
|
860
|
+
return line.slice(0, index);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return line;
|
|
864
|
+
}
|
|
865
|
+
function countLeadingSpaces(line) {
|
|
866
|
+
let count = 0;
|
|
867
|
+
while (count < line.length && line[count] === " ") {
|
|
868
|
+
count += 1;
|
|
869
|
+
}
|
|
870
|
+
return count;
|
|
871
|
+
}
|
|
872
|
+
function parseYamlScalar(value, source, lineNumber) {
|
|
873
|
+
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
874
|
+
return value.slice(1, -1);
|
|
875
|
+
}
|
|
876
|
+
if (value === "true") {
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
if (value === "false") {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
if (value === "null") {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
if (/^-?\d+$/.test(value)) {
|
|
886
|
+
return Number.parseInt(value, 10);
|
|
887
|
+
}
|
|
888
|
+
if (/^-?\d+\.\d+$/.test(value)) {
|
|
889
|
+
return Number.parseFloat(value);
|
|
890
|
+
}
|
|
891
|
+
if (value.startsWith("[") || value.startsWith("{")) {
|
|
892
|
+
throw new Error(`unsupported YAML collection syntax in ${source}:${lineNumber}; use nested mappings instead`);
|
|
893
|
+
}
|
|
894
|
+
return value;
|
|
895
|
+
}
|
|
658
896
|
function normalizeServerAddr(raw) {
|
|
659
897
|
const trimmed = raw.trim();
|
|
660
898
|
if (trimmed.startsWith("dns:") || trimmed.startsWith("unix:")) {
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ message BrowserSessionCommand {
|
|
|
12
12
|
OpenTabCommand open_tab = 2;
|
|
13
13
|
SessionPingCommand ping = 3;
|
|
14
14
|
CloseBrowserSessionCommand close = 4;
|
|
15
|
+
LaunchBrowserCommand launch_browser = 5;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -34,6 +35,7 @@ message BrowserSessionEvent {
|
|
|
34
35
|
SessionPongEvent pong = 4;
|
|
35
36
|
BrowserSessionClosedEvent closed = 5;
|
|
36
37
|
BrowserSessionErrorEvent error = 6;
|
|
38
|
+
BrowserLaunchedEvent browser_launched = 7;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -5,6 +5,26 @@ option go_package = "allwright.dev/gen/allwright/engine/v1;enginev1";
|
|
|
5
5
|
|
|
6
6
|
import "core/v1/common.proto";
|
|
7
7
|
|
|
8
|
+
enum BrowserKind {
|
|
9
|
+
BROWSER_KIND_UNSPECIFIED = 0;
|
|
10
|
+
BROWSER_KIND_CHROMIUM = 1;
|
|
11
|
+
BROWSER_KIND_FIREFOX = 2;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
message LaunchBrowserCommand {
|
|
15
|
+
BrowserKind browser_kind = 1;
|
|
16
|
+
optional string browser_binary = 2;
|
|
17
|
+
optional CommandRetryOptions retry_options = 3;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
message BrowserLaunchedEvent {
|
|
21
|
+
BrowserKind browser_kind = 1;
|
|
22
|
+
string browser = 2;
|
|
23
|
+
string note = 3;
|
|
24
|
+
string user_data_dir = 4;
|
|
25
|
+
string initial_tab_session_id = 5;
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
message LaunchChromeCommand {
|
|
9
29
|
optional string chrome_binary = 1;
|
|
10
30
|
optional CommandRetryOptions retry_options = 2;
|