@allwright.dev/core 0.0.35 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PageImpl } from "./page.js";
2
+ import { formatActionError } from "./errors.js";
2
3
  export class BrowserTypeImpl {
3
4
  #browserKind;
4
5
  constructor(browserKind = "chromium") {
@@ -61,7 +62,7 @@ export class BrowserImpl {
61
62
  return this.#createPage(event.tabOpened.tabSessionId);
62
63
  }
63
64
  if (event.error?.message) {
64
- throw new Error(`browser session error while opening tab: ${event.error.message}`);
65
+ throw formatActionError("open tab", event.error.message);
65
66
  }
66
67
  }
67
68
  }
@@ -80,7 +81,7 @@ export class BrowserImpl {
80
81
  return;
81
82
  }
82
83
  if (event.error?.message) {
83
- throw new Error(`browser session error while closing: ${event.error.message}`);
84
+ throw formatActionError("close browser", event.error.message);
84
85
  }
85
86
  }
86
87
  }
@@ -97,7 +98,7 @@ export class BrowserImpl {
97
98
  return event.pong.message;
98
99
  }
99
100
  if (event.error?.message) {
100
- throw new Error(`browser session error while pinging: ${event.error.message}`);
101
+ throw formatActionError("ping browser", event.error.message);
101
102
  }
102
103
  }
103
104
  }
@@ -131,7 +132,7 @@ export class BrowserImpl {
131
132
  }
132
133
  #ensureOpen() {
133
134
  if (this.#closed) {
134
- throw new Error(`browser session ${this.sessionId} is closed`);
135
+ throw formatActionError("use browser", `browser session ${this.sessionId} is closed`);
135
136
  }
136
137
  }
137
138
  }
@@ -0,0 +1,6 @@
1
+ export declare class AllwrightError extends Error {
2
+ readonly debugDetails?: string;
3
+ constructor(message: string, debugDetails?: string);
4
+ }
5
+ export declare function formatStreamError(raw: string): AllwrightError;
6
+ export declare function formatActionError(action: string, raw: string, locator?: string): AllwrightError;
package/dist/errors.js ADDED
@@ -0,0 +1,120 @@
1
+ export class AllwrightError extends Error {
2
+ debugDetails;
3
+ constructor(message, debugDetails) {
4
+ super(message);
5
+ this.name = "AllwrightError";
6
+ this.debugDetails = debugDetails;
7
+ }
8
+ }
9
+ export function formatStreamError(raw) {
10
+ const normalized = stripTransportPrefix(raw);
11
+ const evaluated = extractEvaluateMessage(normalized);
12
+ return createError(evaluated.userMessage, raw, evaluated.debugDetails);
13
+ }
14
+ export function formatActionError(action, raw, locator) {
15
+ const normalized = stripTransportPrefix(raw);
16
+ const evaluated = extractEvaluateMessage(normalized);
17
+ const locatorLabel = locator ? ` for locator ${formatLocator(locator)}` : "";
18
+ const message = evaluated.userMessage
19
+ ? `${capitalize(action)} failed${locatorLabel}: ${evaluated.userMessage}`
20
+ : `${capitalize(action)} failed${locatorLabel}.`;
21
+ return createError(message, raw, evaluated.debugDetails);
22
+ }
23
+ function createError(message, raw, debugDetails) {
24
+ if (debugEnabled()) {
25
+ return new AllwrightError(`${message}\n\nDebug details: ${debugDetails ?? raw}`, debugDetails ?? raw);
26
+ }
27
+ return new AllwrightError(message, debugDetails ?? raw);
28
+ }
29
+ function extractEvaluateMessage(raw) {
30
+ const exceptionMarker = "Runtime.evaluate failed with exception details:";
31
+ const mapperMarker = "mapper Runtime.evaluate raised exception details:";
32
+ const marker = raw.includes(exceptionMarker)
33
+ ? exceptionMarker
34
+ : raw.includes(mapperMarker)
35
+ ? mapperMarker
36
+ : null;
37
+ if (!marker) {
38
+ return { userMessage: cleanupUserMessage(raw) };
39
+ }
40
+ const payload = raw.slice(raw.indexOf(marker) + marker.length).trim();
41
+ const parsed = tryParseJson(payload);
42
+ const message = pickString(parsed, ["exception", "message"]) ??
43
+ pickPreviewProperty(parsed, "message") ??
44
+ pickString(parsed, ["exception", "description"]) ??
45
+ raw;
46
+ return {
47
+ userMessage: cleanupUserMessage(message),
48
+ debugDetails: payload,
49
+ };
50
+ }
51
+ function cleanupUserMessage(message) {
52
+ const compact = message.replace(/\s+/g, " ").trim();
53
+ const invalidQuerySelector = compact.match(/Failed to execute 'querySelector(All)?' on 'Document': '(.+?)' is not a valid selector\.?/);
54
+ if (invalidQuerySelector) {
55
+ return `invalid selector ${invalidQuerySelector[2]}`;
56
+ }
57
+ return compact
58
+ .replace(/^SyntaxError:\s*/i, "")
59
+ .replace(/^DOMException:\s*/i, "")
60
+ .replace(/^Error:\s*/i, "");
61
+ }
62
+ function stripTransportPrefix(raw) {
63
+ return raw
64
+ .replace(/^grpc stream error:\s*/i, "")
65
+ .replace(/^\d+\s+INTERNAL:\s*/i, "")
66
+ .trim();
67
+ }
68
+ function capitalize(value) {
69
+ return value.charAt(0).toUpperCase() + value.slice(1);
70
+ }
71
+ function formatLocator(locator) {
72
+ return JSON.stringify(locator.trim());
73
+ }
74
+ function debugEnabled() {
75
+ const raw = process.env.ALLWRIGHT_DEBUG?.trim().toLowerCase();
76
+ return raw === "1" || raw === "true" || raw === "yes";
77
+ }
78
+ function tryParseJson(value) {
79
+ try {
80
+ return JSON.parse(value);
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ function pickString(root, path) {
87
+ let current = root;
88
+ for (const segment of path) {
89
+ if (!current || typeof current !== "object" || !(segment in current)) {
90
+ return null;
91
+ }
92
+ current = current[segment];
93
+ }
94
+ return typeof current === "string" && current.trim() ? current.trim() : null;
95
+ }
96
+ function pickPreviewProperty(root, name) {
97
+ const properties = pickUnknown(root, ["exception", "preview", "properties"]);
98
+ if (!Array.isArray(properties)) {
99
+ return null;
100
+ }
101
+ for (const property of properties) {
102
+ if (property &&
103
+ typeof property === "object" &&
104
+ property.name === name &&
105
+ typeof property.value === "string") {
106
+ return (property.value ?? "").trim() || null;
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+ function pickUnknown(root, path) {
112
+ let current = root;
113
+ for (const segment of path) {
114
+ if (!current || typeof current !== "object" || !(segment in current)) {
115
+ return null;
116
+ }
117
+ current = current[segment];
118
+ }
119
+ return current;
120
+ }
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BrowserImpl, BrowserTypeImpl } from "./browser.js";
2
+ import { formatActionError } from "./errors.js";
2
3
  import { findConfigFile, loadConfigFile, resolveConfig } from "./config.js";
3
4
  import { PageImpl } from "./page.js";
4
5
  import { createBrowserSessionHandle, getRuntime, launchConfiguredBrowser as launchConfiguredBrowserWithResolver, ping as runtimePing, resolveLaunchBrowserArgs, setServerAddr, shutdown, } from "./runtime.js";
@@ -42,7 +43,7 @@ export async function launchBrowser(browserKindOrOptions, options = {}) {
42
43
  });
43
44
  }
44
45
  if (event.error?.message) {
45
- throw new Error(`browser session error during launch: ${event.error.message}`);
46
+ throw formatActionError("launch browser", event.error.message);
46
47
  }
47
48
  }
48
49
  }
package/dist/page.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LocatorImpl } from "./locator.js";
2
+ import { formatActionError } from "./errors.js";
2
3
  import { normalizeSelectorForTransport } from "./selectors.js";
3
4
  import { createPageHandle } from "./runtime.js";
4
5
  export class PageImpl {
@@ -36,7 +37,7 @@ export class PageImpl {
36
37
  injection = event.chromiumBidiInjection;
37
38
  }
38
39
  if (event.error?.message) {
39
- throw new Error(`page session error while navigating: ${event.error.message}`);
40
+ throw formatActionError("navigate", event.error.message);
40
41
  }
41
42
  if (event.closed) {
42
43
  handle.closed = true;
@@ -76,7 +77,7 @@ export class PageImpl {
76
77
  };
77
78
  }
78
79
  if (event.error?.message) {
79
- throw new Error(`page session error while clicking: ${event.error.message}`);
80
+ throw formatActionError("click", event.error.message, selector);
80
81
  }
81
82
  if (event.closed) {
82
83
  handle.closed = true;
@@ -106,7 +107,7 @@ export class PageImpl {
106
107
  };
107
108
  }
108
109
  if (event.error?.message) {
109
- throw new Error(`page session error while counting elements: ${event.error.message}`);
110
+ throw formatActionError("count elements", event.error.message, selector);
110
111
  }
111
112
  if (event.closed) {
112
113
  handle.closed = true;
@@ -137,7 +138,7 @@ export class PageImpl {
137
138
  };
138
139
  }
139
140
  if (event.error?.message) {
140
- throw new Error(`page session error while highlighting elements: ${event.error.message}`);
141
+ throw formatActionError("highlight elements", event.error.message, selector);
141
142
  }
142
143
  if (event.closed) {
143
144
  handle.closed = true;
@@ -166,7 +167,7 @@ export class PageImpl {
166
167
  };
167
168
  }
168
169
  if (event.error?.message) {
169
- throw new Error(`page session error while focusing: ${event.error.message}`);
170
+ throw formatActionError("focus", event.error.message, selector);
170
171
  }
171
172
  if (event.closed) {
172
173
  handle.closed = true;
@@ -197,7 +198,7 @@ export class PageImpl {
197
198
  };
198
199
  }
199
200
  if (event.error?.message) {
200
- throw new Error(`page session error while filling: ${event.error.message}`);
201
+ throw formatActionError("fill", event.error.message, selector);
201
202
  }
202
203
  if (event.closed) {
203
204
  handle.closed = true;
@@ -226,7 +227,7 @@ export class PageImpl {
226
227
  };
227
228
  }
228
229
  if (event.error?.message) {
229
- throw new Error(`page session error while hovering: ${event.error.message}`);
230
+ throw formatActionError("hover", event.error.message, selector);
230
231
  }
231
232
  if (event.closed) {
232
233
  handle.closed = true;
@@ -258,7 +259,7 @@ export class PageImpl {
258
259
  };
259
260
  }
260
261
  if (event.error?.message) {
261
- throw new Error(`page session error while pressing key: ${event.error.message}`);
262
+ throw formatActionError("press key", event.error.message, selector);
262
263
  }
263
264
  if (event.closed) {
264
265
  handle.closed = true;
@@ -295,7 +296,7 @@ export class PageImpl {
295
296
  };
296
297
  }
297
298
  if (event.error?.message) {
298
- throw new Error(`page session error while waiting for selector: ${event.error.message}`);
299
+ throw formatActionError("wait for selector", event.error.message, selector);
299
300
  }
300
301
  if (event.closed) {
301
302
  handle.closed = true;
@@ -321,7 +322,7 @@ export class PageImpl {
321
322
  return;
322
323
  }
323
324
  if (event.error?.message) {
324
- throw new Error(`page session error while closing: ${event.error.message}`);
325
+ throw formatActionError("close page", event.error.message);
325
326
  }
326
327
  }
327
328
  }
@@ -341,7 +342,7 @@ export class PageImpl {
341
342
  return event.pong.message;
342
343
  }
343
344
  if (event.error?.message) {
344
- throw new Error(`page session error while pinging: ${event.error.message}`);
345
+ throw formatActionError("ping page", event.error.message);
345
346
  }
346
347
  if (event.closed) {
347
348
  handle.closed = true;
@@ -396,7 +397,7 @@ export class PageImpl {
396
397
  };
397
398
  }
398
399
  if (event.error?.message) {
399
- throw new Error(`page session error while reading text: ${event.error.message}`);
400
+ throw formatActionError("read text", event.error.message, selector);
400
401
  }
401
402
  if (event.closed) {
402
403
  handle.closed = true;
@@ -406,7 +407,7 @@ export class PageImpl {
406
407
  }
407
408
  #ensureOpen(handle) {
408
409
  if (handle.closed) {
409
- throw new Error(`page session ${this.sessionId} is closed`);
410
+ throw formatActionError("use page", `page session ${this.sessionId} is closed`);
410
411
  }
411
412
  }
412
413
  async #getHandle() {
package/dist/runtime.js CHANGED
@@ -2,6 +2,7 @@ import { fileURLToPath } from "node:url";
2
2
  import grpc from "@grpc/grpc-js";
3
3
  import protoLoader from "@grpc/proto-loader";
4
4
  import { ensureRuntimeReady, shutdownManagedServer } from "./bootstrap.js";
5
+ import { formatStreamError } from "./errors.js";
5
6
  import { EventQueue } from "./types.js";
6
7
  const DEFAULT_SERVER_ADDR = "127.0.0.1:50051";
7
8
  const SERVER_ADDR_ENV_VAR = "ALLWRIGHT_SERVER_ADDR";
@@ -103,7 +104,7 @@ function bindStreamQueue(stream) {
103
104
  queue.push(event);
104
105
  });
105
106
  stream.on("error", (error) => {
106
- queue.fail(new Error(`grpc stream error: ${error.message}`));
107
+ queue.fail(formatStreamError(`grpc stream error: ${error.message}`));
107
108
  });
108
109
  stream.on("end", () => {
109
110
  queue.fail(new Error("grpc stream ended"));
package/dist/selectors.js CHANGED
@@ -1,11 +1,92 @@
1
+ const SELECTOR_PREFIXES = ["xpath=", "xpath:", "css=", "css:"];
2
+ function decodeSelectorBody(body) {
3
+ const candidate = body.trim();
4
+ if (candidate.startsWith("\"") && candidate.endsWith("\"")) {
5
+ try {
6
+ return JSON.parse(candidate);
7
+ }
8
+ catch {
9
+ return candidate;
10
+ }
11
+ }
12
+ return candidate;
13
+ }
14
+ function parseExplicitSelectorPrefix(selector) {
15
+ const lowered = selector.toLowerCase();
16
+ if (lowered.startsWith("xpath=") || lowered.startsWith("xpath:")) {
17
+ return { flavor: "xpath", prefixLength: 6 };
18
+ }
19
+ if (lowered.startsWith("css=") || lowered.startsWith("css:")) {
20
+ return { flavor: "css", prefixLength: 4 };
21
+ }
22
+ return null;
23
+ }
24
+ function findJsonStringEnd(value) {
25
+ if (!value.startsWith("\"")) {
26
+ return null;
27
+ }
28
+ let escaped = false;
29
+ for (let index = 1; index < value.length; index += 1) {
30
+ const char = value[index];
31
+ if (escaped) {
32
+ escaped = false;
33
+ continue;
34
+ }
35
+ if (char === "\\") {
36
+ escaped = true;
37
+ continue;
38
+ }
39
+ if (char === "\"") {
40
+ return index + 1;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ function isNormalizedTransportSelector(selector) {
46
+ const trimmed = selector.trim();
47
+ if (!trimmed) {
48
+ return false;
49
+ }
50
+ let index = 0;
51
+ while (index < trimmed.length) {
52
+ const prefix = parseExplicitSelectorPrefix(trimmed.slice(index));
53
+ if (!prefix) {
54
+ return false;
55
+ }
56
+ index += prefix.prefixLength;
57
+ const remainder = trimmed.slice(index);
58
+ if (!remainder.startsWith("\"")) {
59
+ return false;
60
+ }
61
+ const jsonEnd = findJsonStringEnd(remainder);
62
+ if (!jsonEnd) {
63
+ return false;
64
+ }
65
+ index += jsonEnd;
66
+ const tail = trimmed.slice(index);
67
+ if (!tail) {
68
+ return true;
69
+ }
70
+ if (!/^\s+/.test(tail)) {
71
+ return false;
72
+ }
73
+ const nextIndex = index + tail.match(/^\s+/)?.[0].length;
74
+ const nextSegment = trimmed.slice(nextIndex).toLowerCase();
75
+ if (!SELECTOR_PREFIXES.some((prefixValue) => nextSegment.startsWith(prefixValue))) {
76
+ return false;
77
+ }
78
+ index = nextIndex;
79
+ }
80
+ return true;
81
+ }
1
82
  export function parseSelectorForTransport(selector) {
2
83
  const trimmed = selector.trim();
3
84
  const lower = trimmed.toLowerCase();
4
85
  if (lower.startsWith("xpath=") || lower.startsWith("xpath:")) {
5
- return { flavor: "xpath", body: trimmed.slice(6).trim() };
86
+ return { flavor: "xpath", body: decodeSelectorBody(trimmed.slice(6)) };
6
87
  }
7
88
  if (lower.startsWith("css=") || lower.startsWith("css:")) {
8
- return { flavor: "css", body: trimmed.slice(4).trim() };
89
+ return { flavor: "css", body: decodeSelectorBody(trimmed.slice(4)) };
9
90
  }
10
91
  if (trimmed.startsWith("//") ||
11
92
  trimmed.startsWith(".//") ||
@@ -17,6 +98,10 @@ export function parseSelectorForTransport(selector) {
17
98
  return { flavor: "css", body: trimmed };
18
99
  }
19
100
  export function normalizeSelectorForTransport(selector) {
101
+ const trimmed = selector.trim();
102
+ if (isNormalizedTransportSelector(trimmed)) {
103
+ return trimmed;
104
+ }
20
105
  const parsed = parseSelectorForTransport(selector);
21
106
  return `${parsed.flavor}=${JSON.stringify(parsed.body)}`;
22
107
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import grpc from "@grpc/grpc-js";
2
+ export { AllwrightError } from "./errors.js";
2
3
  export interface LaunchOptions {
3
4
  browserBinary?: string;
4
5
  timeoutMs?: number;
package/dist/types.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { AllwrightError } from "./errors.js";
1
2
  export class EventQueue {
2
3
  #items = [];
3
4
  #waiters = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allwright.dev/core",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "description": "High-level TypeScript client for the allwright automation engine.",
5
5
  "license": "MIT",
6
6
  "type": "module",