@allwright.dev/core 0.0.35 → 0.0.37
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 +5 -4
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +120 -0
- package/dist/index.js +2 -1
- package/dist/page.js +14 -13
- package/dist/runtime.js +2 -1
- package/dist/selectors.js +87 -2
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
135
|
+
throw formatActionError("use browser", `browser session ${this.sessionId} is closed`);
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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)
|
|
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)
|
|
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
package/dist/types.js
CHANGED