@handstage/core 0.0.3 → 0.0.4

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.
Files changed (54) hide show
  1. package/README.md +1 -0
  2. package/dist/v3/index.js +5 -7
  3. package/dist/v3/logger.js +1 -1
  4. package/dist/v3/shutdown/supervisor.js +3 -3
  5. package/dist/v3/shutdown/supervisorClient.js +2 -2
  6. package/dist/v3/types/public/logs.js +1 -1
  7. package/dist/v3/types/public/sdkErrors.js +47 -47
  8. package/dist/v3/understudy/a11y/snapshot/domTree.js +5 -5
  9. package/dist/v3/understudy/a11y/snapshot/focusSelectors.js +5 -5
  10. package/dist/v3/understudy/cdp.js +12 -2
  11. package/dist/v3/understudy/context.js +4 -6
  12. package/dist/v3/understudy/deepLocator.js +2 -2
  13. package/dist/v3/understudy/fileUploadUtils.js +5 -5
  14. package/dist/v3/understudy/frame.js +6 -6
  15. package/dist/v3/understudy/frameLocator.js +2 -2
  16. package/dist/v3/understudy/initScripts.js +4 -4
  17. package/dist/v3/understudy/locator.js +16 -18
  18. package/dist/v3/understudy/navigationResponseTracker.js +1 -1
  19. package/dist/v3/understudy/page.js +20 -15
  20. package/dist/v3/understudy/response.js +2 -2
  21. package/dist/v3/understudy/screenshotUtils.js +14 -14
  22. package/dist/v3/v3.js +3 -2
  23. package/dist/version.js +1 -1
  24. package/package.json +3 -3
  25. package/src/v3/index.ts +5 -9
  26. package/src/v3/logger.ts +1 -1
  27. package/src/v3/shutdown/supervisor.ts +3 -3
  28. package/src/v3/shutdown/supervisorClient.ts +2 -2
  29. package/src/v3/types/private/internal.ts +1 -1
  30. package/src/v3/types/public/index.ts +6 -1
  31. package/src/v3/types/public/logs.ts +1 -1
  32. package/src/v3/types/public/options.ts +5 -5
  33. package/src/v3/types/public/page.ts +1 -2
  34. package/src/v3/types/public/sdkErrors.ts +47 -47
  35. package/src/v3/understudy/a11y/snapshot/domTree.ts +5 -5
  36. package/src/v3/understudy/a11y/snapshot/focusSelectors.ts +5 -5
  37. package/src/v3/understudy/a11y/snapshot/treeFormatUtils.ts +2 -2
  38. package/src/v3/understudy/a11y/snapshot/xpathUtils.ts +1 -1
  39. package/src/v3/understudy/cdp.ts +33 -7
  40. package/src/v3/understudy/context.ts +19 -19
  41. package/src/v3/understudy/deepLocator.ts +2 -2
  42. package/src/v3/understudy/fileUploadUtils.ts +5 -5
  43. package/src/v3/understudy/frame.ts +6 -6
  44. package/src/v3/understudy/frameLocator.ts +2 -2
  45. package/src/v3/understudy/initScripts.ts +4 -4
  46. package/src/v3/understudy/locator.ts +18 -20
  47. package/src/v3/understudy/navigationResponseTracker.ts +1 -1
  48. package/src/v3/understudy/page.ts +27 -22
  49. package/src/v3/understudy/piercer.ts +1 -1
  50. package/src/v3/understudy/response.ts +2 -2
  51. package/src/v3/understudy/screenshotUtils.ts +14 -16
  52. package/src/v3/understudy/selectorResolver.ts +1 -1
  53. package/src/v3/v3.ts +11 -11
  54. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -1,2 +1,3 @@
1
1
  # @handstage/core
2
2
 
3
+ Core browser automation engine for Handstage. Manages browser connections, Chrome DevTools Protocol (CDP) communication, page context, frame locators, and script injection for reliable browser automation.
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 Handstages } from "./v3";
7
- const HandstagesDefault = {
5
+ export { __internalMaybeRunShutdownSupervisorFromArgv, V3, V3 as Handstage };
6
+ export default {
8
7
  ...PublicApi,
9
8
  V3,
10
- Handstages: V3,
11
- __internalMaybeRunShutdownSupervisorFromArgv: maybeRunShutdownSupervisorFromArgv,
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
- * Handstages V3 per-instance log routing (AsyncLocalStorage).
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("Handstages process completed"));
142
- process.stdin.on("close", () => onLifelineClosed("Handstages process completed"));
143
- process.stdin.on("error", () => onLifelineClosed("Handstages process crashed or was killed"));
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 Handstages CLI entrypoint with supervisor args.
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 Handstages CLI entrypoint)."), "resolve");
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,5 +1,5 @@
1
1
  /**
2
- * Severity for Handstages log lines. Maps 1:1 to `console.error` / `console.info` / `console.debug`.
2
+ * Severity for Handstage log lines. Maps 1:1 to `console.error` / `console.info` / `console.debug`.
3
3
  */
4
4
  export var LogLevel;
5
5
  (function (LogLevel) {
@@ -1,6 +1,6 @@
1
1
  // Avoid .js extension so bundlers resolve TS source
2
- import { HANDSTAGES_VERSION } from "../../../version";
3
- export class HandstagesError extends Error {
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 HandstagesDefaultError extends HandstagesError {
13
+ export class HandstageDefaultError extends HandstageError {
14
14
  constructor(error) {
15
- if (error instanceof Error || error instanceof HandstagesError) {
16
- super(`\nHey! We're sorry you ran into an error. \nHandstages version: ${HANDSTAGES_VERSION} \nIf you need help, please open a Github issue or reach out to us on Discord: https://handstages.dev/discord\n\nFull error:\n${error.message}`);
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 HandstagesEnvironmentError extends HandstagesError {
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 HandstagesError {
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 HandstagesError {
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.handstages.dev/v3/configuration/models#configuration-setup`;
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
59
+ export class CaptchaTimeoutError extends HandstageError {
60
60
  constructor() {
61
61
  super("Captcha timeout");
62
62
  }
63
63
  }
64
- export class MissingLLMConfigurationError extends HandstagesError {
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 HandstagesError {
70
+ export class HandlerNotInitializedError extends HandstageError {
71
71
  constructor(handlerType) {
72
72
  super(`${handlerType} handler not initialized`);
73
73
  }
74
74
  }
75
- export class HandstagesInvalidArgumentError extends HandstagesError {
75
+ export class HandstageInvalidArgumentError extends HandstageError {
76
76
  constructor(message) {
77
77
  super(`InvalidArgumentError: ${message}`);
78
78
  }
79
79
  }
80
- export class CookieValidationError extends HandstagesError {
80
+ export class CookieValidationError extends HandstageError {
81
81
  constructor(message) {
82
82
  super(message);
83
83
  }
84
84
  }
85
- export class CookieSetError extends HandstagesError {
85
+ export class CookieSetError extends HandstageError {
86
86
  constructor(message) {
87
87
  super(message);
88
88
  }
89
89
  }
90
- export class HandstagesElementNotFoundError extends HandstagesError {
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 HandstagesError {
95
+ export class AgentScreenshotProviderError extends HandstageError {
96
96
  constructor(message) {
97
97
  super(`ScreenshotProviderError: ${message}`);
98
98
  }
99
99
  }
100
- export class HandstagesMissingArgumentError extends HandstagesError {
100
+ export class HandstageMissingArgumentError extends HandstageError {
101
101
  constructor(message) {
102
102
  super(`MissingArgumentError: ${message}`);
103
103
  }
104
104
  }
105
- export class CreateChatCompletionResponseError extends HandstagesError {
105
+ export class CreateChatCompletionResponseError extends HandstageError {
106
106
  constructor(message) {
107
107
  super(`CreateChatCompletionResponseError: ${message}`);
108
108
  }
109
109
  }
110
- export class HandstagesEvalError extends HandstagesError {
110
+ export class HandstageEvalError extends HandstageError {
111
111
  constructor(message) {
112
- super(`HandstagesEvalError: ${message}`);
112
+ super(`HandstageEvalError: ${message}`);
113
113
  }
114
114
  }
115
- export class HandstagesDomProcessError extends HandstagesError {
115
+ export class HandstageDomProcessError extends HandstageError {
116
116
  constructor(message) {
117
117
  super(`Error Processing Dom: ${message}`);
118
118
  }
119
119
  }
120
- export class HandstagesLocatorError extends HandstagesError {
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 HandstagesClickError extends HandstagesError {
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 HandstagesError {
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 HandstagesIframeError extends HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesInitError extends HandstagesError {
166
+ export class HandstageInitError extends HandstageError {
167
167
  constructor(message) {
168
168
  super(message);
169
169
  }
170
170
  }
171
- export class HandstagesShadowRootMissingError extends HandstagesError {
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 HandstagesShadowSegmentEmptyError extends HandstagesError {
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 HandstagesShadowSegmentNotFoundError extends HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
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 HandstagesError {
213
+ export class ConnectionTimeoutError extends HandstageError {
214
214
  constructor(message) {
215
215
  super(`Connection timeout: ${message}`);
216
216
  }
217
217
  }
218
- export class HandstagesClosedError extends HandstagesError {
218
+ export class HandstageClosedError extends HandstageError {
219
219
  constructor() {
220
- super("Handstages session was closed");
220
+ super("Handstage session was closed");
221
221
  }
222
222
  }
223
- export class CDPConnectionClosedError extends HandstagesError {
223
+ export class CDPConnectionClosedError extends HandstageError {
224
224
  constructor(reason) {
225
225
  super(`CDP connection closed: ${reason}`);
226
226
  }
227
227
  }
228
- export class HandstagesSetExtraHTTPHeadersError extends HandstagesError {
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 HandstagesSnapshotError extends HandstagesError {
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 HandstagesError {
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 { HandstagesDomProcessError } from "../../../types/public/sdkErrors";
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 HandstagesDomProcessError.
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 HandstagesDomProcessError(`Failed to expand DOM node ${identifier}: ${String(err)}`);
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 HandstagesDomProcessError(`Unable to expand DOM node ${identifier} after describeNode depth retries`);
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 HandstagesDomProcessError(lastCborMessage
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 { HandstagesIframeError } from "../../../types/public/sdkErrors";
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 HandstagesIframeError(selectorForIframe, "Failed to resolve iframe element by XPath");
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 HandstagesIframeError(selectorForIframe, "Could not map iframe to child frameId");
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 HandstagesIframeError(parts[i], "Failed to resolve iframe via CSS hop");
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 HandstagesIframeError(parts[i], "Could not map CSS iframe hop to child frameId");
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 { HANDSTAGES_VERSION } from "../../version";
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": `Handstages/${HANDSTAGES_VERSION}`,
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, HandstagesSetExtraHTTPHeadersError, PageNotFoundError, TimeoutError, } from "../types/public/sdkErrors";
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 = "HANDSTAGES_FIRST_TOP_LEVEL_PAGE_TIMEOUT_MS";
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 HandstagesSetExtraHTTPHeadersError(failures);
486
+ throw new HandstageSetExtraHTTPHeadersError(failures);
489
487
  }
490
488
  }
491
489
  /**
@@ -1,4 +1,4 @@
1
- import { HandstagesInvalidArgumentError } from "../types/public/sdkErrors";
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 HandstagesInvalidArgumentError("deepLocator().nth() expects a non-negative index");
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 { HandstagesInvalidArgumentError } from "../types/public/sdkErrors";
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 HandstagesInvalidArgumentError(`setInputFiles(): expected a file but received directory or special entry at ${absolutePath}`);
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 HandstagesInvalidArgumentError("setInputFiles(): expected file path(s) or payload object(s)");
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 HandstagesInvalidArgumentError(`setInputFiles(): file not found at ${absolutePath}`);
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 HandstagesInvalidArgumentError("Unsupported file payload buffer type");
79
+ throw new HandstageInvalidArgumentError("Unsupported file payload buffer type");
80
80
  }
@@ -1,4 +1,4 @@
1
- import { HandstagesEvalError } from "../types/public/sdkErrors";
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 HandstagesEvalError(res.exceptionDetails.text ?? "Evaluation failed");
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, HandstagesInvalidArgumentError, } from "../types/public/sdkErrors";
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 HandstagesInvalidArgumentError("locator().nth() expects a non-negative index");
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)