@allwright.dev/core 0.0.26 → 0.0.28
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 +37 -2
- package/dist/index.d.ts +57 -303
- package/dist/index.js +210 -19
- 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
|
@@ -2,11 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
High-level TypeScript client for the allwright automation engine.
|
|
4
4
|
|
|
5
|
+
Install:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @allwright.dev/core
|
|
9
|
+
```
|
|
10
|
+
|
|
5
11
|
```ts
|
|
6
|
-
import {
|
|
12
|
+
import { firefox } from "@allwright.dev/core";
|
|
7
13
|
|
|
8
|
-
const browser = await
|
|
14
|
+
const browser = await firefox.launch();
|
|
9
15
|
const page = browser.page();
|
|
10
16
|
await page.goto("https://example.com");
|
|
11
17
|
await browser.close();
|
|
12
18
|
```
|
|
19
|
+
|
|
20
|
+
A small runnable example also lives in [examples/basic.ts](./examples/basic.ts), and the fuller end-to-end playground lives in [examples/playground.ts](./examples/playground.ts).
|
|
21
|
+
|
|
22
|
+
Shared config files are stack-agnostic and can live in `allwright.config.yaml` or `allwright.config.json`.
|
|
23
|
+
The shared schema lives at the repo root in `allwright.schema.json`.
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
schemaVersion: 1
|
|
27
|
+
|
|
28
|
+
server:
|
|
29
|
+
addr: 127.0.0.1:50051
|
|
30
|
+
|
|
31
|
+
browser:
|
|
32
|
+
name: firefox
|
|
33
|
+
binary: /Applications/Firefox.app/Contents/MacOS/firefox
|
|
34
|
+
launchOptions:
|
|
35
|
+
timeoutMs: 30000
|
|
36
|
+
|
|
37
|
+
expect:
|
|
38
|
+
timeoutMs: 5000
|
|
39
|
+
intervalMs: 100
|
|
40
|
+
|
|
41
|
+
suites:
|
|
42
|
+
smoke:
|
|
43
|
+
browser:
|
|
44
|
+
name: chromium
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
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
|
}
|
|
@@ -80,315 +80,24 @@ export interface LocatorInfo {
|
|
|
80
80
|
page: Page;
|
|
81
81
|
selector: string;
|
|
82
82
|
}
|
|
83
|
-
interface
|
|
84
|
-
message?: string;
|
|
85
|
-
}
|
|
86
|
-
interface ChromeLaunchedPayload {
|
|
87
|
-
browser?: string;
|
|
88
|
-
note?: string;
|
|
89
|
-
cdpWebsocketUrl?: string;
|
|
90
|
-
userDataDir?: string;
|
|
91
|
-
initialTabSessionId?: string;
|
|
92
|
-
}
|
|
93
|
-
interface BrowserSessionEvent {
|
|
94
|
-
sessionId?: string;
|
|
95
|
-
event?: string;
|
|
96
|
-
chromeLaunched?: ChromeLaunchedPayload;
|
|
97
|
-
tabOpened?: {
|
|
98
|
-
tabSessionId?: string;
|
|
99
|
-
note?: string;
|
|
100
|
-
};
|
|
101
|
-
pong?: {
|
|
102
|
-
message?: string;
|
|
103
|
-
};
|
|
104
|
-
closed?: {
|
|
105
|
-
reason?: string;
|
|
106
|
-
};
|
|
107
|
-
error?: {
|
|
108
|
-
message?: string;
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
interface TabSessionEvent {
|
|
112
|
-
tabSessionId?: string;
|
|
113
|
-
event?: string;
|
|
114
|
-
attached?: {
|
|
115
|
-
note?: string;
|
|
116
|
-
};
|
|
117
|
-
pong?: {
|
|
118
|
-
message?: string;
|
|
119
|
-
};
|
|
120
|
-
closed?: {
|
|
121
|
-
reason?: string;
|
|
122
|
-
};
|
|
123
|
-
error?: {
|
|
124
|
-
message?: string;
|
|
125
|
-
};
|
|
126
|
-
navigated?: {
|
|
127
|
-
url?: string;
|
|
128
|
-
note?: string;
|
|
129
|
-
};
|
|
130
|
-
chromiumBidiInjection?: {
|
|
131
|
-
note?: string;
|
|
132
|
-
bidiSessionId?: string;
|
|
133
|
-
mapperTargetId?: string;
|
|
134
|
-
mapperSessionId?: string;
|
|
135
|
-
packageVersion?: string;
|
|
136
|
-
};
|
|
137
|
-
elementClicked?: {
|
|
138
|
-
cssSelector?: string;
|
|
139
|
-
note?: string;
|
|
140
|
-
bidiSessionId?: string;
|
|
141
|
-
};
|
|
142
|
-
elementCounted?: {
|
|
143
|
-
cssSelector?: string;
|
|
144
|
-
count?: number;
|
|
145
|
-
note?: string;
|
|
146
|
-
};
|
|
147
|
-
elementsHighlighted?: {
|
|
148
|
-
cssSelector?: string;
|
|
149
|
-
count?: number;
|
|
150
|
-
note?: string;
|
|
151
|
-
};
|
|
152
|
-
elementFocused?: {
|
|
153
|
-
cssSelector?: string;
|
|
154
|
-
note?: string;
|
|
155
|
-
};
|
|
156
|
-
elementFilled?: {
|
|
157
|
-
cssSelector?: string;
|
|
158
|
-
value?: string;
|
|
159
|
-
note?: string;
|
|
160
|
-
};
|
|
161
|
-
elementHovered?: {
|
|
162
|
-
cssSelector?: string;
|
|
163
|
-
note?: string;
|
|
164
|
-
};
|
|
165
|
-
keyPressed?: {
|
|
166
|
-
cssSelector?: string;
|
|
167
|
-
key?: string;
|
|
168
|
-
note?: string;
|
|
169
|
-
};
|
|
170
|
-
textContentResolved?: {
|
|
171
|
-
cssSelector?: string;
|
|
172
|
-
text?: string;
|
|
173
|
-
note?: string;
|
|
174
|
-
};
|
|
175
|
-
innerTextResolved?: {
|
|
176
|
-
cssSelector?: string;
|
|
177
|
-
text?: string;
|
|
178
|
-
note?: string;
|
|
179
|
-
};
|
|
180
|
-
selectorWaitSatisfied?: {
|
|
181
|
-
cssSelector?: string;
|
|
182
|
-
visible?: boolean;
|
|
183
|
-
note?: string;
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
interface LaunchChromeRequest {
|
|
187
|
-
launchChrome: {
|
|
188
|
-
chromeBinary?: string;
|
|
189
|
-
retryOptions?: {
|
|
190
|
-
timeoutMs?: number;
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
interface OpenTabRequest {
|
|
195
|
-
openTab: {
|
|
196
|
-
retryOptions?: {
|
|
197
|
-
timeoutMs?: number;
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
interface BrowserPingRequest {
|
|
202
|
-
ping: {
|
|
203
|
-
message: string;
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
interface CloseBrowserRequest {
|
|
207
|
-
close: Record<string, never>;
|
|
208
|
-
}
|
|
209
|
-
interface TabPingRequest {
|
|
210
|
-
browserSessionId: string;
|
|
211
|
-
tabSessionId: string;
|
|
212
|
-
ping: {
|
|
213
|
-
message: string;
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
interface NavigateRequest {
|
|
217
|
-
browserSessionId: string;
|
|
218
|
-
tabSessionId: string;
|
|
219
|
-
navigate: {
|
|
220
|
-
url: string;
|
|
221
|
-
retryOptions?: {
|
|
222
|
-
timeoutMs?: number;
|
|
223
|
-
};
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
interface ClickRequest {
|
|
227
|
-
browserSessionId: string;
|
|
228
|
-
tabSessionId: string;
|
|
229
|
-
clickElement: {
|
|
230
|
-
cssSelector: string;
|
|
231
|
-
retryOptions?: {
|
|
232
|
-
timeoutMs?: number;
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
interface CountRequest {
|
|
237
|
-
browserSessionId: string;
|
|
238
|
-
tabSessionId: string;
|
|
239
|
-
countElements: {
|
|
240
|
-
cssSelector: string;
|
|
241
|
-
retryOptions?: {
|
|
242
|
-
timeoutMs?: number;
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
interface HighlightRequest {
|
|
247
|
-
browserSessionId: string;
|
|
248
|
-
tabSessionId: string;
|
|
249
|
-
highlightElements: {
|
|
250
|
-
cssSelector: string;
|
|
251
|
-
durationMs?: number;
|
|
252
|
-
retryOptions?: {
|
|
253
|
-
timeoutMs?: number;
|
|
254
|
-
};
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
interface FocusRequest {
|
|
258
|
-
browserSessionId: string;
|
|
259
|
-
tabSessionId: string;
|
|
260
|
-
focusElement: {
|
|
261
|
-
cssSelector: string;
|
|
262
|
-
retryOptions?: {
|
|
263
|
-
timeoutMs?: number;
|
|
264
|
-
};
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
interface FillRequest {
|
|
268
|
-
browserSessionId: string;
|
|
269
|
-
tabSessionId: string;
|
|
270
|
-
fillElement: {
|
|
271
|
-
cssSelector: string;
|
|
272
|
-
value: string;
|
|
273
|
-
retryOptions?: {
|
|
274
|
-
timeoutMs?: number;
|
|
275
|
-
};
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
interface HoverRequest {
|
|
279
|
-
browserSessionId: string;
|
|
280
|
-
tabSessionId: string;
|
|
281
|
-
hoverElement: {
|
|
282
|
-
cssSelector: string;
|
|
283
|
-
retryOptions?: {
|
|
284
|
-
timeoutMs?: number;
|
|
285
|
-
};
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
interface PressRequest {
|
|
289
|
-
browserSessionId: string;
|
|
290
|
-
tabSessionId: string;
|
|
291
|
-
pressKey: {
|
|
292
|
-
cssSelector: string;
|
|
293
|
-
key: string;
|
|
294
|
-
text?: string;
|
|
295
|
-
retryOptions?: {
|
|
296
|
-
timeoutMs?: number;
|
|
297
|
-
};
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
interface TextContentRequest {
|
|
301
|
-
browserSessionId: string;
|
|
302
|
-
tabSessionId: string;
|
|
303
|
-
getTextContent: {
|
|
304
|
-
cssSelector: string;
|
|
305
|
-
retryOptions?: {
|
|
306
|
-
timeoutMs?: number;
|
|
307
|
-
};
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
interface InnerTextRequest {
|
|
311
|
-
browserSessionId: string;
|
|
312
|
-
tabSessionId: string;
|
|
313
|
-
getInnerText: {
|
|
314
|
-
cssSelector: string;
|
|
315
|
-
retryOptions?: {
|
|
316
|
-
timeoutMs?: number;
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
interface WaitForSelectorRequest {
|
|
321
|
-
browserSessionId: string;
|
|
322
|
-
tabSessionId: string;
|
|
323
|
-
waitForSelector: {
|
|
324
|
-
cssSelector: string;
|
|
325
|
-
visible?: boolean;
|
|
326
|
-
retryOptions?: {
|
|
327
|
-
timeoutMs?: number;
|
|
328
|
-
};
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
interface CloseTabRequest {
|
|
332
|
-
browserSessionId: string;
|
|
333
|
-
tabSessionId: string;
|
|
334
|
-
close: Record<string, never>;
|
|
335
|
-
}
|
|
336
|
-
type BrowserSessionRequest = LaunchChromeRequest | OpenTabRequest | BrowserPingRequest | CloseBrowserRequest;
|
|
337
|
-
type TabSessionRequest = TabPingRequest | NavigateRequest | ClickRequest | CountRequest | HighlightRequest | FocusRequest | FillRequest | HoverRequest | PressRequest | TextContentRequest | InnerTextRequest | WaitForSelectorRequest | CloseTabRequest;
|
|
338
|
-
type BrowserSessionStream = grpc.ClientDuplexStream<BrowserSessionRequest, BrowserSessionEvent>;
|
|
339
|
-
type PageSessionStream = grpc.ClientDuplexStream<TabSessionRequest, TabSessionEvent>;
|
|
340
|
-
interface EngineServiceClientShape {
|
|
341
|
-
Ping(request: Record<string, never>, callback: (error: grpc.ServiceError | null, response: PingResponse) => void): void;
|
|
342
|
-
BrowserSession(): BrowserSessionStream;
|
|
343
|
-
TabSession(): PageSessionStream;
|
|
344
|
-
close(): void;
|
|
345
|
-
}
|
|
346
|
-
interface RuntimeClient {
|
|
347
|
-
client: EngineServiceClientShape;
|
|
348
|
-
}
|
|
349
|
-
interface BrowserLaunchState {
|
|
350
|
-
runtime: RuntimeClient;
|
|
351
|
-
stream: BrowserSessionStream;
|
|
352
|
-
queue: EventQueue<BrowserSessionEvent>;
|
|
353
|
-
sessionId: string;
|
|
354
|
-
chromeLaunched: ChromeLaunchedPayload;
|
|
355
|
-
}
|
|
356
|
-
declare class EventQueue<T> {
|
|
357
|
-
#private;
|
|
358
|
-
push(item: T): void;
|
|
359
|
-
fail(error: Error): void;
|
|
360
|
-
next(): Promise<T>;
|
|
361
|
-
}
|
|
362
|
-
export declare class BrowserType {
|
|
83
|
+
export interface BrowserType {
|
|
363
84
|
launch(options?: LaunchOptions): Promise<Browser>;
|
|
364
85
|
}
|
|
365
|
-
export
|
|
366
|
-
#private;
|
|
367
|
-
constructor(state: BrowserLaunchState);
|
|
368
|
-
readonly sessionId: string;
|
|
369
|
-
readonly browserName: string;
|
|
370
|
-
readonly launchNote: string;
|
|
371
|
-
readonly cdpWebSocketURL: string;
|
|
372
|
-
readonly userDataDir: string;
|
|
86
|
+
export interface Browser extends BrowserInfo {
|
|
373
87
|
page(): Page;
|
|
374
88
|
initialPage(): Page;
|
|
89
|
+
initialTab(): Page;
|
|
375
90
|
pages(): Page[];
|
|
376
91
|
newPage(options?: CommandOptions): Promise<Page>;
|
|
92
|
+
newTab(options?: CommandOptions): Promise<Page>;
|
|
377
93
|
close(): Promise<void>;
|
|
378
94
|
ping(message?: string): Promise<string>;
|
|
379
95
|
browserInfo(): BrowserInfo;
|
|
380
|
-
initialTab(): Page;
|
|
381
|
-
newTab(options?: CommandOptions): Promise<Page>;
|
|
382
96
|
}
|
|
383
|
-
export
|
|
384
|
-
#private;
|
|
385
|
-
constructor(input: PageInfo & {
|
|
386
|
-
runtime: RuntimeClient;
|
|
387
|
-
});
|
|
388
|
-
readonly sessionId: string;
|
|
389
|
-
readonly browserSessionId: string;
|
|
97
|
+
export interface Page extends PageInfo {
|
|
390
98
|
locator(selector: string): Locator;
|
|
391
99
|
goto(url: string, options?: CommandOptions): Promise<NavigateResult>;
|
|
100
|
+
navigate(url: string, options?: CommandOptions): Promise<NavigateResult>;
|
|
392
101
|
click(selector: string, options?: CommandOptions): Promise<ClickResult>;
|
|
393
102
|
count(selector: string, options?: CommandOptions): Promise<CountResult>;
|
|
394
103
|
highlight(selector: string, options?: HighlightOptions): Promise<HighlightResult>;
|
|
@@ -402,12 +111,10 @@ export declare class Page {
|
|
|
402
111
|
close(): Promise<void>;
|
|
403
112
|
ping(message?: string): Promise<string>;
|
|
404
113
|
pageInfo(): PageInfo;
|
|
405
|
-
navigate(url: string, options?: CommandOptions): Promise<NavigateResult>;
|
|
406
114
|
}
|
|
407
|
-
export
|
|
115
|
+
export interface Locator {
|
|
408
116
|
readonly page: Page;
|
|
409
117
|
readonly selector: string;
|
|
410
|
-
constructor(input: LocatorInfo);
|
|
411
118
|
click(options?: CommandOptions): Promise<ClickResult>;
|
|
412
119
|
count(options?: CommandOptions): Promise<CountResult>;
|
|
413
120
|
highlight(options?: HighlightOptions): Promise<HighlightResult>;
|
|
@@ -420,10 +127,57 @@ export declare class Locator {
|
|
|
420
127
|
waitFor(options?: WaitForSelectorOptions): Promise<WaitForSelectorResult>;
|
|
421
128
|
locator(selector: string): Locator;
|
|
422
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;
|
|
171
|
+
}
|
|
423
172
|
export declare const chromium: BrowserType;
|
|
173
|
+
export declare const firefox: BrowserType;
|
|
424
174
|
export type Tab = Page;
|
|
425
175
|
export declare function ping(): Promise<string>;
|
|
426
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>;
|
|
427
179
|
export declare function setServerAddr(serverAddr: string): void;
|
|
428
180
|
export declare function shutdown(): Promise<void>;
|
|
429
|
-
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) {
|
|
@@ -186,7 +199,7 @@ export class Page {
|
|
|
186
199
|
sessionId;
|
|
187
200
|
browserSessionId;
|
|
188
201
|
locator(selector) {
|
|
189
|
-
return new
|
|
202
|
+
return new LocatorImpl({ page: this, selector });
|
|
190
203
|
}
|
|
191
204
|
async goto(url, options = {}) {
|
|
192
205
|
const handle = await this.#getHandle();
|
|
@@ -581,7 +594,7 @@ export class Page {
|
|
|
581
594
|
return this.#handlePromise;
|
|
582
595
|
}
|
|
583
596
|
}
|
|
584
|
-
|
|
597
|
+
class LocatorImpl {
|
|
585
598
|
page;
|
|
586
599
|
selector;
|
|
587
600
|
constructor(input) {
|
|
@@ -619,13 +632,14 @@ export class Locator {
|
|
|
619
632
|
return this.page.waitForSelector(this.selector, options);
|
|
620
633
|
}
|
|
621
634
|
locator(selector) {
|
|
622
|
-
return new
|
|
635
|
+
return new LocatorImpl({
|
|
623
636
|
page: this.page,
|
|
624
637
|
selector: `${this.selector} ${selector}`,
|
|
625
638
|
});
|
|
626
639
|
}
|
|
627
640
|
}
|
|
628
|
-
export const chromium = new
|
|
641
|
+
export const chromium = new BrowserTypeImpl("chromium");
|
|
642
|
+
export const firefox = new BrowserTypeImpl("firefox");
|
|
629
643
|
export async function ping() {
|
|
630
644
|
const runtime = await getRuntime();
|
|
631
645
|
return new Promise((resolve, reject) => {
|
|
@@ -639,24 +653,34 @@ export async function ping() {
|
|
|
639
653
|
});
|
|
640
654
|
}
|
|
641
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 = {}) {
|
|
642
665
|
const runtime = await getRuntime();
|
|
643
666
|
const stream = runtime.client.BrowserSession();
|
|
644
667
|
const queue = bindStreamQueue(stream);
|
|
645
668
|
stream.write({
|
|
646
|
-
|
|
647
|
-
|
|
669
|
+
launchBrowser: {
|
|
670
|
+
browserKind: browserKind === "firefox" ? 2 : 1,
|
|
671
|
+
browserBinary: options.browserBinary,
|
|
648
672
|
retryOptions: options.timeoutMs ? { timeoutMs: options.timeoutMs } : undefined,
|
|
649
673
|
},
|
|
650
674
|
});
|
|
651
675
|
while (true) {
|
|
652
676
|
const event = await queue.next();
|
|
653
|
-
if (event.
|
|
654
|
-
return new
|
|
677
|
+
if (event.browserLaunched) {
|
|
678
|
+
return new BrowserImpl({
|
|
655
679
|
runtime,
|
|
656
680
|
stream,
|
|
657
681
|
queue,
|
|
658
682
|
sessionId: event.sessionId ?? "",
|
|
659
|
-
|
|
683
|
+
launched: event.browserLaunched,
|
|
660
684
|
});
|
|
661
685
|
}
|
|
662
686
|
if (event.error?.message) {
|
|
@@ -676,6 +700,55 @@ export async function shutdown() {
|
|
|
676
700
|
runtime.client.close();
|
|
677
701
|
runtimePromise = null;
|
|
678
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
|
+
}
|
|
679
752
|
async function getRuntime() {
|
|
680
753
|
if (!runtimePromise) {
|
|
681
754
|
runtimePromise = Promise.resolve(createRuntime());
|
|
@@ -702,6 +775,124 @@ function configuredServerAddr() {
|
|
|
702
775
|
}
|
|
703
776
|
return normalizeServerAddr(process.env[SERVER_ADDR_ENV_VAR] ?? DEFAULT_SERVER_ADDR);
|
|
704
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
|
+
}
|
|
705
896
|
function normalizeServerAddr(raw) {
|
|
706
897
|
const trimmed = raw.trim();
|
|
707
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;
|