@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 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 { chromium } from "@allwright.dev/core";
12
+ import { firefox } from "@allwright.dev/core";
7
13
 
8
- const browser = await chromium.launch();
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
- chromeBinary?: string;
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 PingResponse {
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 declare class Browser {
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 declare class Page {
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 declare class Locator {
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
- export class BrowserType {
56
+ class BrowserTypeImpl {
57
+ #browserKind;
58
+ constructor(browserKind = "chromium") {
59
+ this.#browserKind = browserKind;
60
+ }
48
61
  async launch(options = {}) {
49
- return launchChrome(options);
62
+ return launchBrowser(this.#browserKind, options);
50
63
  }
51
64
  }
52
- export class Browser {
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.chromeLaunched.browser ?? "",
63
- launchNote: state.chromeLaunched.note ?? "",
64
- cdpWebSocketURL: state.chromeLaunched.cdpWebsocketUrl ?? "",
65
- userDataDir: state.chromeLaunched.userDataDir ?? "",
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.chromeLaunched.initialTabSessionId ?? "");
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 Page({
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
- export class Page {
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 Locator({ page: this, selector });
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
- export class Locator {
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 Locator({
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 BrowserType();
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
- launchChrome: {
647
- chromeBinary: options.chromeBinary,
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.chromeLaunched) {
654
- return new Browser({
677
+ if (event.browserLaunched) {
678
+ return new BrowserImpl({
655
679
  runtime,
656
680
  stream,
657
681
  queue,
658
682
  sessionId: event.sessionId ?? "",
659
- chromeLaunched: event.chromeLaunched,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allwright.dev/core",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "High-level TypeScript client for the allwright automation engine.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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;