@handstage/core 0.0.3 → 0.0.5
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 +1 -0
- package/dist/v3/index.js +5 -7
- package/dist/v3/logger.js +1 -1
- package/dist/v3/shutdown/supervisor.js +3 -3
- package/dist/v3/shutdown/supervisorClient.js +2 -2
- package/dist/v3/types/public/logs.js +1 -1
- package/dist/v3/types/public/sdkErrors.js +47 -47
- package/dist/v3/understudy/a11y/snapshot/domTree.js +5 -5
- package/dist/v3/understudy/a11y/snapshot/focusSelectors.js +5 -5
- package/dist/v3/understudy/cdp.js +12 -2
- package/dist/v3/understudy/context.js +4 -6
- package/dist/v3/understudy/deepLocator.js +2 -2
- package/dist/v3/understudy/fileUploadUtils.js +5 -5
- package/dist/v3/understudy/frame.js +6 -6
- package/dist/v3/understudy/frameLocator.js +2 -2
- package/dist/v3/understudy/initScripts.js +4 -4
- package/dist/v3/understudy/locator.js +16 -18
- package/dist/v3/understudy/navigationResponseTracker.js +1 -1
- package/dist/v3/understudy/page.js +20 -15
- package/dist/v3/understudy/response.js +2 -2
- package/dist/v3/understudy/screenshotUtils.js +14 -14
- package/dist/v3/v3.js +3 -2
- package/dist/version.js +1 -1
- package/package.json +3 -5
- package/src/v3/index.ts +2 -14
- package/src/v3/launch/local.ts +7 -4
- package/src/v3/logger.ts +1 -1
- package/src/v3/shutdown/supervisor.ts +3 -3
- package/src/v3/shutdown/supervisorClient.ts +2 -2
- package/src/v3/types/private/internal.ts +1 -1
- package/src/v3/types/public/index.ts +7 -1
- package/src/v3/types/public/logs.ts +1 -1
- package/src/v3/types/public/options.ts +5 -5
- package/src/v3/types/public/page.ts +1 -2
- package/src/v3/types/public/sdkErrors.ts +47 -47
- package/src/v3/understudy/a11y/snapshot/domTree.ts +5 -5
- package/src/v3/understudy/a11y/snapshot/focusSelectors.ts +5 -5
- package/src/v3/understudy/a11y/snapshot/treeFormatUtils.ts +2 -2
- package/src/v3/understudy/a11y/snapshot/xpathUtils.ts +1 -1
- package/src/v3/understudy/cdp.ts +46 -16
- package/src/v3/understudy/context.ts +19 -19
- package/src/v3/understudy/deepLocator.ts +2 -2
- package/src/v3/understudy/fileUploadUtils.ts +5 -5
- package/src/v3/understudy/frame.ts +6 -6
- package/src/v3/understudy/frameLocator.ts +2 -2
- package/src/v3/understudy/initScripts.ts +4 -4
- package/src/v3/understudy/locator.ts +18 -20
- package/src/v3/understudy/navigationResponseTracker.ts +1 -1
- package/src/v3/understudy/page.ts +27 -22
- package/src/v3/understudy/piercer.ts +1 -1
- package/src/v3/understudy/response.ts +2 -2
- package/src/v3/understudy/screenshotUtils.ts +14 -16
- package/src/v3/understudy/selectorResolver.ts +1 -1
- package/src/v3/v3.ts +11 -11
- package/src/version.ts +1 -1
package/README.md
CHANGED
package/dist/v3/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { maybeRunShutdownSupervisorFromArgv } from "./shutdown/supervisor";
|
|
1
|
+
import { maybeRunShutdownSupervisorFromArgv as __internalMaybeRunShutdownSupervisorFromArgv } from "./shutdown/supervisor";
|
|
2
2
|
import * as PublicApi from "./types/public/index";
|
|
3
3
|
import { V3 } from "./v3";
|
|
4
|
-
export { maybeRunShutdownSupervisorFromArgv as __internalMaybeRunShutdownSupervisorFromArgv } from "./shutdown/supervisor";
|
|
5
4
|
export * from "./types/public/index";
|
|
6
|
-
export { V3, V3 as
|
|
7
|
-
|
|
5
|
+
export { __internalMaybeRunShutdownSupervisorFromArgv, V3, V3 as Handstage };
|
|
6
|
+
export default {
|
|
8
7
|
...PublicApi,
|
|
9
8
|
V3,
|
|
10
|
-
|
|
11
|
-
__internalMaybeRunShutdownSupervisorFromArgv
|
|
9
|
+
Handstage: V3,
|
|
10
|
+
__internalMaybeRunShutdownSupervisorFromArgv,
|
|
12
11
|
};
|
|
13
|
-
export default HandstagesDefault;
|
package/dist/v3/logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { createConsoleLogger } from "./types/public/consoleLogger";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Handstage V3 per-instance log routing (AsyncLocalStorage).
|
|
5
5
|
*
|
|
6
6
|
* - `bindInstanceLogger` / `unbindInstanceLogger`: register the effective logger for an instance id.
|
|
7
7
|
* - `withInstanceLogContext`: run a function with that instance id on the async context.
|
|
@@ -138,9 +138,9 @@ export const runShutdownSupervisor = (initialConfig) => {
|
|
|
138
138
|
// Stdin is the lifeline; losing it means parent is gone.
|
|
139
139
|
try {
|
|
140
140
|
process.stdin.resume();
|
|
141
|
-
process.stdin.on("end", () => onLifelineClosed("
|
|
142
|
-
process.stdin.on("close", () => onLifelineClosed("
|
|
143
|
-
process.stdin.on("error", () => onLifelineClosed("
|
|
141
|
+
process.stdin.on("end", () => onLifelineClosed("Handstage process completed"));
|
|
142
|
+
process.stdin.on("close", () => onLifelineClosed("Handstage process completed"));
|
|
143
|
+
process.stdin.on("error", () => onLifelineClosed("Handstage process crashed or was killed"));
|
|
144
144
|
}
|
|
145
145
|
catch { }
|
|
146
146
|
};
|
|
@@ -24,7 +24,7 @@ const isSeaRuntime = () => {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
// SEA: re-exec current binary with supervisor args.
|
|
27
|
-
// Non-SEA: execute
|
|
27
|
+
// Non-SEA: execute Handstage CLI entrypoint with supervisor args.
|
|
28
28
|
const resolveCliPath = () => `${moduleDir}/../cli.js`;
|
|
29
29
|
const resolveSupervisorCommand = (config) => {
|
|
30
30
|
const baseArgs = ["--supervisor", serializeConfigArg(config)];
|
|
@@ -55,7 +55,7 @@ const serializeConfigArg = (config) => `--supervisor-config=${JSON.stringify({
|
|
|
55
55
|
export function startShutdownSupervisor(config, opts) {
|
|
56
56
|
const resolved = resolveSupervisorCommand(config);
|
|
57
57
|
if (!resolved) {
|
|
58
|
-
opts?.onError?.(new ShutdownSupervisorResolveError("Shutdown supervisor entry missing (expected
|
|
58
|
+
opts?.onError?.(new ShutdownSupervisorResolveError("Shutdown supervisor entry missing (expected Handstage CLI entrypoint)."), "resolve");
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
const child = spawn(resolved.command, resolved.args, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Avoid .js extension so bundlers resolve TS source
|
|
2
|
-
import {
|
|
3
|
-
export class
|
|
2
|
+
import { HANDSTAGE_VERSION } from "../../../version";
|
|
3
|
+
export class HandstageError extends Error {
|
|
4
4
|
cause;
|
|
5
5
|
constructor(message, cause) {
|
|
6
6
|
super(message);
|
|
@@ -10,139 +10,139 @@ export class HandstagesError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export class
|
|
13
|
+
export class HandstageDefaultError extends HandstageError {
|
|
14
14
|
constructor(error) {
|
|
15
|
-
if (error instanceof Error || error instanceof
|
|
16
|
-
super(`\nHey! We're sorry you ran into an error. \
|
|
15
|
+
if (error instanceof Error || error instanceof HandstageError) {
|
|
16
|
+
super(`\nHey! We're sorry you ran into an error. \nHandstage version: ${HANDSTAGE_VERSION} \nIf you need help, please open a Github issue or reach out to us on Discord: https://handstage.dev/discord\n\nFull error:\n${error.message}`);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
export class
|
|
20
|
+
export class HandstageEnvironmentError extends HandstageError {
|
|
21
21
|
constructor(currentEnvironment, requiredEnvironment, feature) {
|
|
22
22
|
super(`You seem to be setting the current environment to ${currentEnvironment}.` +
|
|
23
23
|
`Ensure the environment is set to ${requiredEnvironment} if you want to use ${feature}.`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export class MissingEnvironmentVariableError extends
|
|
26
|
+
export class MissingEnvironmentVariableError extends HandstageError {
|
|
27
27
|
constructor(missingEnvironmentVariable, feature) {
|
|
28
28
|
super(`${missingEnvironmentVariable} is required to use ${feature}.` +
|
|
29
29
|
`Please set ${missingEnvironmentVariable} in your environment.`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
export class UnsupportedModelError extends
|
|
32
|
+
export class UnsupportedModelError extends HandstageError {
|
|
33
33
|
constructor(supportedModels, feature) {
|
|
34
34
|
const message = feature
|
|
35
35
|
? `${feature} requires a valid model.`
|
|
36
36
|
: `Unsupported model.`;
|
|
37
37
|
const guidance = `\n\nPlease use the provider/model format (e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5", "google/gemini-3-flash-preview").` +
|
|
38
|
-
`\n\nFor a complete list of supported models and providers, see: https://docs.
|
|
38
|
+
`\n\nFor a complete list of supported models and providers, see: https://docs.handstage.dev/v3/configuration/models#configuration-setup`;
|
|
39
39
|
super(`${message}${guidance}`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
export class UnsupportedModelProviderError extends
|
|
42
|
+
export class UnsupportedModelProviderError extends HandstageError {
|
|
43
43
|
constructor(supportedProviders, feature) {
|
|
44
44
|
super(feature
|
|
45
45
|
? `${feature} requires one of the following model providers: ${supportedProviders}`
|
|
46
46
|
: `please use one of the supported model providers: ${supportedProviders}`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
export class UnsupportedAISDKModelProviderError extends
|
|
49
|
+
export class UnsupportedAISDKModelProviderError extends HandstageError {
|
|
50
50
|
constructor(provider, supportedProviders) {
|
|
51
51
|
super(`${provider} is not currently supported for aiSDK. please use one of the supported model providers: ${supportedProviders}`);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
export class InvalidAISDKModelFormatError extends
|
|
54
|
+
export class InvalidAISDKModelFormatError extends HandstageError {
|
|
55
55
|
constructor(modelName) {
|
|
56
56
|
super(`${modelName} does not follow correct format for specifying aiSDK models. Please define your model as 'provider/model-name'. For example: \`model: 'openai/gpt-4o-mini'\``);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
export class CaptchaTimeoutError extends
|
|
59
|
+
export class CaptchaTimeoutError extends HandstageError {
|
|
60
60
|
constructor() {
|
|
61
61
|
super("Captcha timeout");
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
export class MissingLLMConfigurationError extends
|
|
64
|
+
export class MissingLLMConfigurationError extends HandstageError {
|
|
65
65
|
constructor() {
|
|
66
66
|
super("No LLM API key or LLM Client configured. An LLM API key or a custom LLM Client " +
|
|
67
67
|
"is required to use act, extract, or observe.");
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
export class HandlerNotInitializedError extends
|
|
70
|
+
export class HandlerNotInitializedError extends HandstageError {
|
|
71
71
|
constructor(handlerType) {
|
|
72
72
|
super(`${handlerType} handler not initialized`);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
export class
|
|
75
|
+
export class HandstageInvalidArgumentError extends HandstageError {
|
|
76
76
|
constructor(message) {
|
|
77
77
|
super(`InvalidArgumentError: ${message}`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
export class CookieValidationError extends
|
|
80
|
+
export class CookieValidationError extends HandstageError {
|
|
81
81
|
constructor(message) {
|
|
82
82
|
super(message);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
export class CookieSetError extends
|
|
85
|
+
export class CookieSetError extends HandstageError {
|
|
86
86
|
constructor(message) {
|
|
87
87
|
super(message);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
export class
|
|
90
|
+
export class HandstageElementNotFoundError extends HandstageError {
|
|
91
91
|
constructor(xpaths) {
|
|
92
92
|
super(`Could not find an element for the given xPath(s): ${xpaths}`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
export class AgentScreenshotProviderError extends
|
|
95
|
+
export class AgentScreenshotProviderError extends HandstageError {
|
|
96
96
|
constructor(message) {
|
|
97
97
|
super(`ScreenshotProviderError: ${message}`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
export class
|
|
100
|
+
export class HandstageMissingArgumentError extends HandstageError {
|
|
101
101
|
constructor(message) {
|
|
102
102
|
super(`MissingArgumentError: ${message}`);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
export class CreateChatCompletionResponseError extends
|
|
105
|
+
export class CreateChatCompletionResponseError extends HandstageError {
|
|
106
106
|
constructor(message) {
|
|
107
107
|
super(`CreateChatCompletionResponseError: ${message}`);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
export class
|
|
110
|
+
export class HandstageEvalError extends HandstageError {
|
|
111
111
|
constructor(message) {
|
|
112
|
-
super(`
|
|
112
|
+
super(`HandstageEvalError: ${message}`);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
export class
|
|
115
|
+
export class HandstageDomProcessError extends HandstageError {
|
|
116
116
|
constructor(message) {
|
|
117
117
|
super(`Error Processing Dom: ${message}`);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
export class
|
|
120
|
+
export class HandstageLocatorError extends HandstageError {
|
|
121
121
|
constructor(action, selector, message) {
|
|
122
122
|
super(`Error ${action} Element with selector: ${selector} Reason: ${message}`);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
export class
|
|
125
|
+
export class HandstageClickError extends HandstageError {
|
|
126
126
|
constructor(message, selector) {
|
|
127
127
|
super(`Error Clicking Element with selector: ${selector} Reason: ${message}`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
-
export class LLMResponseError extends
|
|
130
|
+
export class LLMResponseError extends HandstageError {
|
|
131
131
|
constructor(primitive, message) {
|
|
132
132
|
super(`${primitive} LLM response error: ${message}`);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
export class
|
|
135
|
+
export class HandstageIframeError extends HandstageError {
|
|
136
136
|
constructor(frameUrl, message) {
|
|
137
137
|
super(`Unable to resolve frameId for iframe with URL: ${frameUrl} Full error: ${message}`);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
export class ContentFrameNotFoundError extends
|
|
140
|
+
export class ContentFrameNotFoundError extends HandstageError {
|
|
141
141
|
constructor(selector) {
|
|
142
142
|
super(`Unable to obtain a content frame for selector: ${selector}`);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
export class XPathResolutionError extends
|
|
145
|
+
export class XPathResolutionError extends HandstageError {
|
|
146
146
|
constructor(xpath) {
|
|
147
147
|
super(`XPath "${xpath}" does not resolve in the current page or frames`);
|
|
148
148
|
}
|
|
@@ -163,76 +163,76 @@ ${JSON.stringify(issues, null, 2)}`);
|
|
|
163
163
|
this.name = "ZodSchemaValidationError";
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
-
export class
|
|
166
|
+
export class HandstageInitError extends HandstageError {
|
|
167
167
|
constructor(message) {
|
|
168
168
|
super(message);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
export class
|
|
171
|
+
export class HandstageShadowRootMissingError extends HandstageError {
|
|
172
172
|
constructor(detail) {
|
|
173
173
|
super(`No shadow root present on the resolved host` +
|
|
174
174
|
(detail ? `: ${detail}` : ""));
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
export class
|
|
177
|
+
export class HandstageShadowSegmentEmptyError extends HandstageError {
|
|
178
178
|
constructor() {
|
|
179
179
|
super(`Empty selector segment after shadow-DOM hop ("//")`);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
export class
|
|
182
|
+
export class HandstageShadowSegmentNotFoundError extends HandstageError {
|
|
183
183
|
constructor(segment, hint) {
|
|
184
184
|
super(`Shadow segment '${segment}' matched no element inside shadow root` +
|
|
185
185
|
(hint ? ` ${hint}` : ""));
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
export class ElementNotVisibleError extends
|
|
188
|
+
export class ElementNotVisibleError extends HandstageError {
|
|
189
189
|
constructor(selector) {
|
|
190
190
|
super(`Element not visible (no box model): ${selector}`);
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
export class ResponseBodyError extends
|
|
193
|
+
export class ResponseBodyError extends HandstageError {
|
|
194
194
|
constructor(message) {
|
|
195
195
|
super(`Failed to retrieve response body: ${message}`);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
export class ResponseParseError extends
|
|
198
|
+
export class ResponseParseError extends HandstageError {
|
|
199
199
|
constructor(message) {
|
|
200
200
|
super(`Failed to parse response: ${message}`);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
export class TimeoutError extends
|
|
203
|
+
export class TimeoutError extends HandstageError {
|
|
204
204
|
constructor(operation, timeoutMs) {
|
|
205
205
|
super(`${operation} timed out after ${timeoutMs}ms`);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
export class PageNotFoundError extends
|
|
208
|
+
export class PageNotFoundError extends HandstageError {
|
|
209
209
|
constructor(identifier) {
|
|
210
210
|
super(`No Page found for ${identifier}`);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
export class ConnectionTimeoutError extends
|
|
213
|
+
export class ConnectionTimeoutError extends HandstageError {
|
|
214
214
|
constructor(message) {
|
|
215
215
|
super(`Connection timeout: ${message}`);
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
-
export class
|
|
218
|
+
export class HandstageClosedError extends HandstageError {
|
|
219
219
|
constructor() {
|
|
220
|
-
super("
|
|
220
|
+
super("Handstage session was closed");
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
|
-
export class CDPConnectionClosedError extends
|
|
223
|
+
export class CDPConnectionClosedError extends HandstageError {
|
|
224
224
|
constructor(reason) {
|
|
225
225
|
super(`CDP connection closed: ${reason}`);
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
export class
|
|
228
|
+
export class HandstageSetExtraHTTPHeadersError extends HandstageError {
|
|
229
229
|
failures;
|
|
230
230
|
constructor(failures) {
|
|
231
231
|
super(`setExtraHTTPHeaders failed for ${failures.length} session(s): ${failures.join(", ")}`);
|
|
232
232
|
this.failures = failures;
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
-
export class
|
|
235
|
+
export class HandstageSnapshotError extends HandstageError {
|
|
236
236
|
constructor(cause) {
|
|
237
237
|
const suffix = cause instanceof Error
|
|
238
238
|
? `: ${cause.message}`
|
|
@@ -242,7 +242,7 @@ export class HandstagesSnapshotError extends HandstagesError {
|
|
|
242
242
|
super(`error taking snapshot${suffix}`, cause);
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
export class UnderstudyCommandException extends
|
|
245
|
+
export class UnderstudyCommandException extends HandstageError {
|
|
246
246
|
constructor(message, cause) {
|
|
247
247
|
super(message, cause);
|
|
248
248
|
this.name = "UnderstudyCommandException";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HandstageDomProcessError } from "../../../types/public/sdkErrors";
|
|
2
2
|
import { buildChildXPathSegments, joinXPath, normalizeXPath, } from "./xpathUtils";
|
|
3
3
|
// starting from infinite depth (-1), exponentially shrink down to 1
|
|
4
4
|
const DOM_DEPTH_ATTEMPTS = [-1, 256, 128, 64, 32, 16, 8, 4, 2, 1];
|
|
@@ -37,7 +37,7 @@ export function collectDomTraversalTargets(node) {
|
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Rehydrate a truncated DOM tree by repeatedly calling DOM.describeNode with
|
|
40
|
-
* decreasing depths. Any non-CBOR failure is surfaced as a
|
|
40
|
+
* decreasing depths. Any non-CBOR failure is surfaced as a HandstageDomProcessError.
|
|
41
41
|
*/
|
|
42
42
|
export async function hydrateDomTree(session, root, pierce) {
|
|
43
43
|
const stack = [root];
|
|
@@ -86,12 +86,12 @@ export async function hydrateDomTree(session, root, pierce) {
|
|
|
86
86
|
continue;
|
|
87
87
|
}
|
|
88
88
|
const identifier = nodeId ?? backendId ?? "unknown";
|
|
89
|
-
throw new
|
|
89
|
+
throw new HandstageDomProcessError(`Failed to expand DOM node ${identifier}: ${String(err)}`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
if (!expanded) {
|
|
93
93
|
const identifier = nodeId ?? backendId ?? "unknown";
|
|
94
|
-
throw new
|
|
94
|
+
throw new HandstageDomProcessError(`Unable to expand DOM node ${identifier} after describeNode depth retries`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
for (const child of collectDomTraversalTargets(node)) {
|
|
@@ -123,7 +123,7 @@ export async function getDomTreeWithFallback(session, pierce) {
|
|
|
123
123
|
throw err;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
throw new
|
|
126
|
+
throw new HandstageDomProcessError(lastCborMessage
|
|
127
127
|
? `CDP DOM.getDocument failed after adaptive depth retries: ${lastCborMessage}`
|
|
128
128
|
: "CDP DOM.getDocument failed after adaptive depth retries.");
|
|
129
129
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HandstageIframeError } from "../../../types/public/sdkErrors";
|
|
2
2
|
import { executionContexts } from "../../executionContextRegistry";
|
|
3
3
|
import { buildLocatorInvocation } from "../../locatorInvocation";
|
|
4
4
|
import { prefixXPath } from "./xpathUtils";
|
|
@@ -59,7 +59,7 @@ export async function resolveFocusFrameAndTail(page, absoluteXPath, parentByFram
|
|
|
59
59
|
const parentSess = page.getSessionForFrame(ctxFrameId);
|
|
60
60
|
const objectId = await resolveObjectIdForXPath(parentSess, selectorForIframe, ctxFrameId);
|
|
61
61
|
if (!objectId)
|
|
62
|
-
throw new
|
|
62
|
+
throw new HandstageIframeError(selectorForIframe, "Failed to resolve iframe element by XPath");
|
|
63
63
|
try {
|
|
64
64
|
await parentSess.send("DOM.enable").catch(() => { });
|
|
65
65
|
const desc = await parentSess.send("DOM.describeNode", { objectId });
|
|
@@ -76,7 +76,7 @@ export async function resolveFocusFrameAndTail(page, absoluteXPath, parentByFram
|
|
|
76
76
|
catch { }
|
|
77
77
|
}
|
|
78
78
|
if (!childFrameId)
|
|
79
|
-
throw new
|
|
79
|
+
throw new HandstageIframeError(selectorForIframe, "Could not map iframe to child frameId");
|
|
80
80
|
absPrefix = prefixXPath(absPrefix || "/", selectorForIframe);
|
|
81
81
|
ctxFrameId = childFrameId;
|
|
82
82
|
}
|
|
@@ -108,7 +108,7 @@ export async function resolveCssFocusFrameAndTail(page, rawSelector, parentByFra
|
|
|
108
108
|
const parentSess = page.getSessionForFrame(ctxFrameId);
|
|
109
109
|
const objectId = await resolveObjectIdForCss(parentSess, parts[i], ctxFrameId);
|
|
110
110
|
if (!objectId)
|
|
111
|
-
throw new
|
|
111
|
+
throw new HandstageIframeError(parts[i], "Failed to resolve iframe via CSS hop");
|
|
112
112
|
try {
|
|
113
113
|
await parentSess.send("DOM.enable").catch(() => { });
|
|
114
114
|
const desc = await parentSess.send("DOM.describeNode", { objectId });
|
|
@@ -125,7 +125,7 @@ export async function resolveCssFocusFrameAndTail(page, rawSelector, parentByFra
|
|
|
125
125
|
catch { }
|
|
126
126
|
}
|
|
127
127
|
if (!childFrameId)
|
|
128
|
-
throw new
|
|
128
|
+
throw new HandstageIframeError(parts[i], "Could not map CSS iframe hop to child frameId");
|
|
129
129
|
ctxFrameId = childFrameId;
|
|
130
130
|
}
|
|
131
131
|
finally {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
-
import {
|
|
2
|
+
import { HANDSTAGE_VERSION } from "../../version";
|
|
3
3
|
import { CDPConnectionClosedError, PageNotFoundError, } from "../types/public/sdkErrors";
|
|
4
4
|
export class BaseCDPConnection {
|
|
5
5
|
// Memoize the in-flight enable so concurrent V3Contexts sharing the
|
|
@@ -55,6 +55,7 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
55
55
|
sessionDispatchWaiters = new Set();
|
|
56
56
|
id = null; // root
|
|
57
57
|
transportCloseHandlers = new Set();
|
|
58
|
+
_isClosed = false;
|
|
58
59
|
onTransportClosed(handler) {
|
|
59
60
|
this.transportCloseHandlers.add(handler);
|
|
60
61
|
}
|
|
@@ -73,11 +74,13 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
73
74
|
super();
|
|
74
75
|
this.transport = transport;
|
|
75
76
|
this.transport.onclose = (reason) => {
|
|
77
|
+
this._isClosed = true;
|
|
76
78
|
const why = `transport-close reason=${String(reason || "")}`;
|
|
77
79
|
this.rejectAllInflight(why);
|
|
78
80
|
this.emitTransportClosed(why);
|
|
79
81
|
};
|
|
80
82
|
this.transport.onerror = (err) => {
|
|
83
|
+
this._isClosed = true;
|
|
81
84
|
const why = `transport-error ${err?.message ?? String(err)}`;
|
|
82
85
|
this.rejectAllInflight(why);
|
|
83
86
|
this.emitTransportClosed(why);
|
|
@@ -88,7 +91,7 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
88
91
|
// Include User-Agent header for server-side observability and version tracking
|
|
89
92
|
// Merge user-provided headers, letting them override defaults
|
|
90
93
|
const headers = {
|
|
91
|
-
"User-Agent": `
|
|
94
|
+
"User-Agent": `Handstage/${HANDSTAGE_VERSION}`,
|
|
92
95
|
...options?.headers,
|
|
93
96
|
};
|
|
94
97
|
const ws = new WebSocket(wsUrl, { headers });
|
|
@@ -115,6 +118,9 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
115
118
|
return new CDPConnection(transport);
|
|
116
119
|
}
|
|
117
120
|
async send(method, params) {
|
|
121
|
+
if (this._isClosed) {
|
|
122
|
+
return Promise.reject(new CDPConnectionClosedError(`Cannot send ${method}: connection is closed`));
|
|
123
|
+
}
|
|
118
124
|
const id = this.nextId++;
|
|
119
125
|
const payload = { id, method, params };
|
|
120
126
|
const stack = new Error().stack?.split("\n").slice(1, 4).join("\n");
|
|
@@ -145,6 +151,7 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
145
151
|
set.delete(handler);
|
|
146
152
|
}
|
|
147
153
|
async close() {
|
|
154
|
+
this._isClosed = true;
|
|
148
155
|
this.transport.close();
|
|
149
156
|
}
|
|
150
157
|
rejectAllInflight(why) {
|
|
@@ -276,6 +283,9 @@ export class CDPConnection extends BaseCDPConnection {
|
|
|
276
283
|
}
|
|
277
284
|
}
|
|
278
285
|
_sendViaSession(sessionId, method, params) {
|
|
286
|
+
if (this._isClosed) {
|
|
287
|
+
return Promise.reject(new CDPConnectionClosedError(`Cannot send ${method}: connection is closed`));
|
|
288
|
+
}
|
|
279
289
|
const id = this.nextId++;
|
|
280
290
|
const payload = { id, method, params, sessionId };
|
|
281
291
|
const stack = new Error().stack?.split("\n").slice(1, 4).join("\n");
|
|
@@ -2,7 +2,7 @@ import { v3ScriptContent } from "@handstage/dom/build/scriptV3Content";
|
|
|
2
2
|
import { v3Logger } from "../logger";
|
|
3
3
|
import { getEnvTimeoutMs } from "../timeoutConfig";
|
|
4
4
|
import { LogLevel } from "../types/public/logs";
|
|
5
|
-
import { CookieSetError,
|
|
5
|
+
import { CookieSetError, HandstageSetExtraHTTPHeadersError, PageNotFoundError, TimeoutError, } from "../types/public/sdkErrors";
|
|
6
6
|
import { CDPConnection, } from "./cdp";
|
|
7
7
|
import { cookieMatchesFilter, filterCookies, normalizeCookieParams, toCDPCookieParam, } from "./cookies";
|
|
8
8
|
import { executionContexts } from "./executionContextRegistry";
|
|
@@ -41,7 +41,7 @@ function isTopLevelPage(info) {
|
|
|
41
41
|
}
|
|
42
42
|
const DEFAULT_FIRST_TOP_LEVEL_PAGE_TIMEOUT_MS = 5000;
|
|
43
43
|
const CI_FIRST_TOP_LEVEL_PAGE_TIMEOUT_MS = 30000;
|
|
44
|
-
const FIRST_TOP_LEVEL_PAGE_TIMEOUT_ENV = "
|
|
44
|
+
const FIRST_TOP_LEVEL_PAGE_TIMEOUT_ENV = "HANDSTAGE_FIRST_TOP_LEVEL_PAGE_TIMEOUT_MS";
|
|
45
45
|
const WAIT_FOR_FIRST_TOP_LEVEL_PAGE_OPERATION = "waitForFirstTopLevelPage (no top-level Page)";
|
|
46
46
|
function getFirstTopLevelPageTimeoutMs() {
|
|
47
47
|
return (getEnvTimeoutMs(FIRST_TOP_LEVEL_PAGE_TIMEOUT_ENV) ??
|
|
@@ -225,9 +225,7 @@ export class V3Context {
|
|
|
225
225
|
return probe.browserContextId;
|
|
226
226
|
}
|
|
227
227
|
finally {
|
|
228
|
-
await conn
|
|
229
|
-
.send("Target.closeTarget", { targetId })
|
|
230
|
-
.catch((err) => {
|
|
228
|
+
await conn.send("Target.closeTarget", { targetId }).catch((err) => {
|
|
231
229
|
v3Logger({
|
|
232
230
|
category: "ctx",
|
|
233
231
|
message: "Failed to close temporary discovery target; it will remain until the browser exits",
|
|
@@ -485,7 +483,7 @@ export class V3Context {
|
|
|
485
483
|
return `session=${sid} error=${message}`;
|
|
486
484
|
});
|
|
487
485
|
if (failures.length) {
|
|
488
|
-
throw new
|
|
486
|
+
throw new HandstageSetExtraHTTPHeadersError(failures);
|
|
489
487
|
}
|
|
490
488
|
}
|
|
491
489
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HandstageInvalidArgumentError } from "../types/public/sdkErrors";
|
|
2
2
|
import { IFRAME_STEP_RE } from "./a11y/snapshot/focusSelectors";
|
|
3
3
|
import { frameLocatorFromFrame } from "./frameLocator";
|
|
4
4
|
import { Locator } from "./locator";
|
|
@@ -151,7 +151,7 @@ export class DeepLocatorDelegate {
|
|
|
151
151
|
nth(index) {
|
|
152
152
|
const value = Number(index);
|
|
153
153
|
if (!Number.isFinite(value) || value < 0) {
|
|
154
|
-
throw new
|
|
154
|
+
throw new HandstageInvalidArgumentError("deepLocator().nth() expects a non-negative index");
|
|
155
155
|
}
|
|
156
156
|
const nextIndex = Math.floor(value);
|
|
157
157
|
if (nextIndex === this.nthIndex)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from "buffer";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { HandstageInvalidArgumentError } from "../types/public/sdkErrors";
|
|
5
5
|
const DEFAULT_MIME_TYPE = "application/octet-stream";
|
|
6
6
|
/**
|
|
7
7
|
* Normalize user-provided setInputFiles arguments into in-memory payloads.
|
|
@@ -26,7 +26,7 @@ export async function normalizeInputFiles(files, opts = {}) {
|
|
|
26
26
|
: path.resolve(baseDir, entry);
|
|
27
27
|
const stat = await statFile(absolutePath);
|
|
28
28
|
if (!stat.isFile()) {
|
|
29
|
-
throw new
|
|
29
|
+
throw new HandstageInvalidArgumentError(`setInputFiles(): expected a file but received directory or special entry at ${absolutePath}`);
|
|
30
30
|
}
|
|
31
31
|
const buffer = await fs.readFile(absolutePath);
|
|
32
32
|
normalized.push({
|
|
@@ -51,7 +51,7 @@ export async function normalizeInputFiles(files, opts = {}) {
|
|
|
51
51
|
});
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
|
-
throw new
|
|
54
|
+
throw new HandstageInvalidArgumentError("setInputFiles(): expected file path(s) or payload object(s)");
|
|
55
55
|
}
|
|
56
56
|
return normalized;
|
|
57
57
|
}
|
|
@@ -62,7 +62,7 @@ async function statFile(absolutePath) {
|
|
|
62
62
|
catch (error) {
|
|
63
63
|
const code = error?.code;
|
|
64
64
|
if (code === "ENOENT") {
|
|
65
|
-
throw new
|
|
65
|
+
throw new HandstageInvalidArgumentError(`setInputFiles(): file not found at ${absolutePath}`);
|
|
66
66
|
}
|
|
67
67
|
throw error;
|
|
68
68
|
}
|
|
@@ -76,5 +76,5 @@ export function toBuffer(data) {
|
|
|
76
76
|
return Buffer.from(data);
|
|
77
77
|
if (data instanceof ArrayBuffer)
|
|
78
78
|
return Buffer.from(new Uint8Array(data));
|
|
79
|
-
throw new
|
|
79
|
+
throw new HandstageInvalidArgumentError("Unsupported file payload buffer type");
|
|
80
80
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HandstageEvalError } from "../types/public/sdkErrors";
|
|
2
2
|
import { executionContexts } from "./executionContextRegistry";
|
|
3
3
|
import { Locator } from "./locator";
|
|
4
4
|
/**
|
|
@@ -45,10 +45,10 @@ export class Frame {
|
|
|
45
45
|
const { root } = await this.session.send("DOM.getDocument");
|
|
46
46
|
const { nodeId } = await this.session.send("DOM.querySelector", { nodeId: root.nodeId, selector });
|
|
47
47
|
const { model } = await this.session.send("DOM.getBoxModel", { nodeId });
|
|
48
|
-
const x = model.content[0];
|
|
49
|
-
const y = model.content[1];
|
|
50
|
-
const width = model.width;
|
|
51
|
-
const height = model.height;
|
|
48
|
+
const x = model.content[0] ?? 0;
|
|
49
|
+
const y = model.content[1] ?? 0;
|
|
50
|
+
const width = model.width ?? 0;
|
|
51
|
+
const height = model.height ?? 0;
|
|
52
52
|
return { x, y, width, height };
|
|
53
53
|
}
|
|
54
54
|
/** Accessibility.getFullAXTree (+ recurse into child frames if requested) */
|
|
@@ -128,7 +128,7 @@ export class Frame {
|
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
if (res.exceptionDetails) {
|
|
131
|
-
throw new
|
|
131
|
+
throw new HandstageEvalError(res.exceptionDetails.text ?? "Evaluation failed");
|
|
132
132
|
}
|
|
133
133
|
return res.result.value;
|
|
134
134
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContentFrameNotFoundError,
|
|
1
|
+
import { ContentFrameNotFoundError, HandstageInvalidArgumentError, } from "../types/public/sdkErrors";
|
|
2
2
|
import { executionContexts } from "./executionContextRegistry";
|
|
3
3
|
/**
|
|
4
4
|
* FrameLocator: resolves iframe elements to their child Frames and allows
|
|
@@ -117,7 +117,7 @@ class LocatorDelegate {
|
|
|
117
117
|
nth(index) {
|
|
118
118
|
const value = Number(index);
|
|
119
119
|
if (!Number.isFinite(value) || value < 0) {
|
|
120
|
-
throw new
|
|
120
|
+
throw new HandstageInvalidArgumentError("locator().nth() expects a non-negative index");
|
|
121
121
|
}
|
|
122
122
|
const nextIndex = Math.floor(value);
|
|
123
123
|
if (nextIndex === this.nthIndex)
|