stagehand 4.1.0a0.dev1499__tar.gz → 4.2.0__tar.gz

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 (36) hide show
  1. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/PKG-INFO +16 -2
  2. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/pyproject.toml +40 -8
  3. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/__init__.py +15 -1
  4. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/content-script.js +106 -91
  5. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/manifest.json +1 -1
  6. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/service-worker.js +2456 -1439
  7. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_generated/input_types.py +93 -11
  8. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_generated/models.py +150 -12
  9. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_generated/protocol_version.py +1 -1
  10. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_validation.py +3 -1
  11. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/browser.py +51 -16
  12. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/browserbase_session.py +40 -18
  13. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/cdp_client.py +52 -12
  14. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/client_models.py +18 -3
  15. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/client_types.py +17 -0
  16. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/locator.py +62 -33
  17. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/page.py +128 -11
  18. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/rpc_client.py +25 -5
  19. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/README.md +0 -0
  20. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/blank.html +0 -0
  21. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/offscreen/service-worker-heartbeat.html +0 -0
  22. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/offscreen/service-worker-heartbeat.js +0 -0
  23. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/wake-service-worker.html +0 -0
  24. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_extension/wake-service-worker.js +0 -0
  25. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_generated/__init__.py +0 -0
  26. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/_sdk_identity.py +0 -0
  27. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/browser_clipboard.py +0 -0
  28. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/browser_context.py +0 -0
  29. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/browserbase_services.py +0 -0
  30. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/extension_assets.py +0 -0
  31. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/file_upload.py +0 -0
  32. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/py.typed +0 -0
  33. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/response.py +0 -0
  34. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/stagehand.py +0 -0
  35. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/timeouts.py +0 -0
  36. {stagehand-4.1.0a0.dev1499 → stagehand-4.2.0}/src/stagehand/webmcp.py +0 -0
@@ -1,13 +1,27 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stagehand
3
- Version: 4.1.0a0.dev1499
3
+ Version: 4.2.0
4
4
  Summary: Browserbase's SDK for building browser agents
5
- Keywords: ai,browser,automation,web-scraping,testing
5
+ Keywords: ai,agent,ai-agent,browser,browser-agent,browser-automation,computer-use,headless,playwright,cdp,llm,automation,web-scraping,testing
6
6
  Author: Browserbase
7
7
  License-Expression: MIT
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Typing :: Typed
19
+ Classifier: Framework :: AsyncIO
8
20
  Requires-Dist: browserbase>=1.15,<2
21
+ Requires-Dist: httpx>=0.23,<1
9
22
  Requires-Dist: opentelemetry-api>=1.35,<2
10
23
  Requires-Dist: pydantic>=2.12,<3
24
+ Requires-Dist: typing-extensions>=4.12,<5
11
25
  Requires-Dist: websockets>=16.1.1
12
26
  Requires-Python: >=3.11
13
27
  Project-URL: Homepage, https://stagehand.dev
@@ -1,18 +1,49 @@
1
1
  [project]
2
2
  name = "stagehand"
3
- version = "4.1.0a0.dev1499"
3
+ version = "4.2.0"
4
4
  description = "Browserbase's SDK for building browser agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
8
  "browserbase>=1.15,<2",
9
+ "httpx>=0.23,<1",
9
10
  "opentelemetry-api>=1.35,<2",
10
11
  "pydantic>=2.12,<3",
12
+ "typing_extensions>=4.12,<5",
11
13
  "websockets>=16.1.1",
12
14
  ]
13
15
  license = "MIT"
14
16
  authors = [{ name = "Browserbase" }]
15
- keywords = ["ai", "browser", "automation", "web-scraping", "testing"]
17
+ keywords = [
18
+ "ai",
19
+ "agent",
20
+ "ai-agent",
21
+ "browser",
22
+ "browser-agent",
23
+ "browser-automation",
24
+ "computer-use",
25
+ "headless",
26
+ "playwright",
27
+ "cdp",
28
+ "llm",
29
+ "automation",
30
+ "web-scraping",
31
+ "testing",
32
+ ]
33
+ classifiers = [
34
+ "Development Status :: 5 - Production/Stable",
35
+ "Intended Audience :: Developers",
36
+ "Operating System :: OS Independent",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Programming Language :: Python :: 3.11",
40
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
41
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
42
+ "Topic :: Software Development :: Libraries :: Python Modules",
43
+ "Topic :: Software Development :: Testing",
44
+ "Typing :: Typed",
45
+ "Framework :: AsyncIO",
46
+ ]
16
47
 
17
48
  [project.urls]
18
49
  Homepage = "https://stagehand.dev"
@@ -39,12 +70,13 @@ select = ["ANN", "E", "F", "I", "PYI", "UP"]
39
70
  [tool.ty.environment]
40
71
  python-version = "3.11"
41
72
 
42
- [tool.ty.src]
43
- exclude = ["src/stagehand/_generated"]
44
-
45
73
  [tool.ty.rules]
46
74
  missing-type-argument = "error"
47
- possibly-unresolved-reference = "warn"
75
+ possibly-unresolved-reference = "error"
76
+ unsound-return-statement = "error"
77
+ dynamic-function-decorator-return = "error"
78
+ missing-override-decorator = "error"
79
+ unused-ignore-comment = "error"
48
80
 
49
81
  [tool.pytest.ini_options]
50
82
  asyncio_mode = "auto"
@@ -55,8 +87,8 @@ strict = true
55
87
  dev = [
56
88
  "datamodel-code-generator==0.68.1",
57
89
  "openai>=2,<3",
58
- "pytest==9.0.2",
90
+ "pytest==9.0.3",
59
91
  "pytest-asyncio==1.3.0",
60
92
  "ruff==0.15.7",
61
- "ty==0.0.56",
93
+ "ty==0.0.82",
62
94
  ]
@@ -11,6 +11,7 @@ from ._generated.input_types import (
11
11
  ModelConfig,
12
12
  PageDragAndDropRoutePoint,
13
13
  PageEventName,
14
+ PagePDFMargin,
14
15
  PageScreenshotClip,
15
16
  RgbaColor,
16
17
  TelemetryConfig,
@@ -67,6 +68,7 @@ from .client_models import (
67
68
  ExtractResult,
68
69
  )
69
70
  from .client_types import (
71
+ BrowserbaseClientOptions,
70
72
  CacheOptions,
71
73
  LLMGenerateCallback,
72
74
  LLMGenerateInput,
@@ -75,7 +77,14 @@ from .client_types import (
75
77
  )
76
78
  from .file_upload import FileInput, FilePayload
77
79
  from .locator import Locator
78
- from .page import CDPSubscription, Page, PageEventListener
80
+ from .page import (
81
+ CDPSubscription,
82
+ Page,
83
+ PageEventListener,
84
+ ToolsAddedListener,
85
+ ToolsRemovedListener,
86
+ WebMCPToolIdentity,
87
+ )
79
88
  from .response import Response
80
89
  from .stagehand import Stagehand
81
90
  from .webmcp import (
@@ -94,6 +103,7 @@ __all__ = [
94
103
  "BrowserClipboard",
95
104
  "BrowserContext",
96
105
  "BrowserbaseBrowserSettings",
106
+ "BrowserbaseClientOptions",
97
107
  "BrowserbaseFetchResult",
98
108
  "BrowserbaseProxyConfig",
99
109
  "BrowserbaseRegion",
@@ -136,7 +146,11 @@ __all__ = [
136
146
  "PageCDPEvent",
137
147
  "PageDragAndDropRoutePoint",
138
148
  "PageEventListener",
149
+ "ToolsAddedListener",
150
+ "ToolsRemovedListener",
151
+ "WebMCPToolIdentity",
139
152
  "PageEventName",
153
+ "PagePDFMargin",
140
154
  "PageScreenshotClip",
141
155
  "ProtocolLocator",
142
156
  "RgbaColor",
@@ -12,7 +12,8 @@
12
12
  * - Attribute equality predicates (`[@attr='value']`, `[@attr="value"]`)
13
13
  * - Attribute existence (`[@attr]`)
14
14
  * - Attribute contains/starts-with (`contains(@attr,'v')`, `starts-with(@attr,'v')`)
15
- * - Text equality/contains (`[text()='v']`, `[contains(text(),'v')]`, `[.='v']`)
15
+ * - Text equality/contains (`[text()='v']`, `[contains(text(),'v')]`, `[.='v']`),
16
+ * where `text()` reads direct child text nodes and `.` the whole subtree
16
17
  * - normalize-space on text/attributes (`[normalize-space(text())='v']`)
17
18
  * - Basic boolean predicates (`and`, `or`, `not(...)`)
18
19
  * - Multiple predicates per step (`[@class='foo'][2]`)
@@ -159,11 +160,12 @@ function parseAtomicPredicate(input) {
159
160
  value: normalizeAttrMatch[2] ?? normalizeAttrMatch[3] ?? "",
160
161
  normalize: true
161
162
  };
162
- const normalizeTextMatch = input.match(new RegExp(`^normalize-space\\(\\s*(?:text\\(\\)|\\.)\\s*\\)\\s*=\\s*${quoted}$`));
163
+ const normalizeTextMatch = input.match(new RegExp(`^normalize-space\\(\\s*(text\\(\\)|\\.)\\s*\\)\\s*=\\s*${quoted}$`));
163
164
  if (normalizeTextMatch) return {
164
165
  type: "textEquals",
165
- value: normalizeTextMatch[1] ?? normalizeTextMatch[2] ?? "",
166
- normalize: true
166
+ value: normalizeTextMatch[2] ?? normalizeTextMatch[3] ?? "",
167
+ normalize: true,
168
+ source: textSource(normalizeTextMatch[1])
167
169
  };
168
170
  const attrEqualsMatch = input.match(new RegExp(`^@(${attrName})\\s*=\\s*${quoted}$`));
169
171
  if (attrEqualsMatch) return {
@@ -188,15 +190,17 @@ function parseAtomicPredicate(input) {
188
190
  name: attrStartsMatch[1],
189
191
  value: attrStartsMatch[2] ?? attrStartsMatch[3] ?? ""
190
192
  };
191
- const textEqualsMatch = input.match(new RegExp(`^(?:text\\(\\)|\\.)\\s*=\\s*${quoted}$`));
193
+ const textEqualsMatch = input.match(new RegExp(`^(text\\(\\)|\\.)\\s*=\\s*${quoted}$`));
192
194
  if (textEqualsMatch) return {
193
195
  type: "textEquals",
194
- value: textEqualsMatch[1] ?? textEqualsMatch[2] ?? ""
196
+ value: textEqualsMatch[2] ?? textEqualsMatch[3] ?? "",
197
+ source: textSource(textEqualsMatch[1])
195
198
  };
196
- const textContainsMatch = input.match(new RegExp(`^contains\\(\\s*(?:text\\(\\)|\\.)\\s*,\\s*${quoted}\\s*\\)$`));
199
+ const textContainsMatch = input.match(new RegExp(`^contains\\(\\s*(text\\(\\)|\\.)\\s*,\\s*${quoted}\\s*\\)$`));
197
200
  if (textContainsMatch) return {
198
201
  type: "textContains",
199
- value: textContainsMatch[1] ?? textContainsMatch[2] ?? ""
202
+ value: textContainsMatch[2] ?? textContainsMatch[3] ?? "",
203
+ source: textSource(textContainsMatch[1])
200
204
  };
201
205
  if (valueMatch.test(input)) return null;
202
206
  return null;
@@ -276,9 +280,23 @@ function hasBalancedParens(input) {
276
280
  return depth === 0;
277
281
  }
278
282
  var normalizeSpace = (value) => value.replace(/\s+/g, " ").trim();
283
+ var TEXT_NODE = 3;
284
+ function textSource(token) {
285
+ return token === "text()" ? "text" : "self";
286
+ }
279
287
  function textValue(element) {
280
288
  return String(element.textContent ?? "");
281
289
  }
290
+ /** The node-set `text()` selects: direct child text nodes, in document order. */
291
+ function childTextValues(element) {
292
+ const values = [];
293
+ for (const node of Array.from(element.childNodes)) if (node.nodeType === TEXT_NODE) values.push(String(node.nodeValue ?? ""));
294
+ return values;
295
+ }
296
+ /** `string(node-set)` is the string-value of its first node, or `""` when empty. */
297
+ function firstChildTextValue(element) {
298
+ return childTextValues(element)[0] ?? "";
299
+ }
282
300
  function normalizeMaybe(value, normalize) {
283
301
  return normalize ? normalizeSpace(value) : value;
284
302
  }
@@ -303,8 +321,12 @@ function evaluatePredicate(element, predicate) {
303
321
  if (attr === null) return false;
304
322
  return normalizeMaybe(attr, predicate.normalize).startsWith(normalizeMaybe(predicate.value, predicate.normalize));
305
323
  }
306
- case "textEquals": return normalizeMaybe(textValue(element), predicate.normalize) === normalizeMaybe(predicate.value, predicate.normalize);
307
- case "textContains": return normalizeMaybe(textValue(element), predicate.normalize).includes(normalizeMaybe(predicate.value, predicate.normalize));
324
+ case "textEquals": {
325
+ const target = normalizeMaybe(predicate.value, predicate.normalize);
326
+ if (predicate.source === "text") return predicate.normalize ? normalizeSpace(firstChildTextValue(element)) === target : childTextValues(element).some((value) => value === target);
327
+ return normalizeMaybe(textValue(element), predicate.normalize) === target;
328
+ }
329
+ case "textContains": return normalizeMaybe(predicate.source === "text" ? firstChildTextValue(element) : textValue(element), predicate.normalize).includes(normalizeMaybe(predicate.value, predicate.normalize));
308
330
  case "index": return true;
309
331
  default: return true;
310
332
  }
@@ -968,7 +990,7 @@ var checkState = (el, state) => {
968
990
  /**
969
991
  * Set up MutationObservers on all shadow roots to detect changes.
970
992
  */
971
- var setupShadowObservers = (callback, observers) => {
993
+ var setupShadowObservers = (callback, observers, isSettled) => {
972
994
  const seenRoots = /* @__PURE__ */ new WeakSet();
973
995
  const observeShadowRoots = (node) => {
974
996
  const shadowRoot = getOpenOrClosedShadowRoot(node);
@@ -978,6 +1000,7 @@ var setupShadowObservers = (callback, observers) => {
978
1000
  callback();
979
1001
  scan();
980
1002
  });
1003
+ observers.push(shadowObserver);
981
1004
  shadowObserver.observe(shadowRoot, {
982
1005
  childList: true,
983
1006
  subtree: true,
@@ -989,14 +1012,13 @@ var setupShadowObservers = (callback, observers) => {
989
1012
  "disabled"
990
1013
  ]
991
1014
  });
992
- observers.push(shadowObserver);
993
1015
  for (const child of Array.from(shadowRoot.children)) observeShadowRoots(child);
994
1016
  }
995
1017
  for (const child of Array.from(node.children)) observeShadowRoots(child);
996
1018
  };
997
1019
  const root = document.documentElement || document.body;
998
1020
  const scan = () => {
999
- if (root) observeShadowRoots(root);
1021
+ if (!isSettled() && root) observeShadowRoots(root);
1000
1022
  };
1001
1023
  scan();
1002
1024
  return scan;
@@ -1007,106 +1029,99 @@ var setupShadowObservers = (callback, observers) => {
1007
1029
  *
1008
1030
  * @param selectorRaw - CSS selector or XPath expression to wait for
1009
1031
  * @param stateRaw - Element state: 'attached' | 'detached' | 'visible' | 'hidden'
1010
- * @param timeoutRaw - Maximum time to wait in milliseconds
1032
+ * @param timeoutRaw - Maximum time to wait in milliseconds; defaults to 30,000, zero is unlimited
1011
1033
  * @param pierceShadowRaw - Whether to search inside shadow DOM
1012
- * @returns Promise that resolves to true when condition is met, or rejects on timeout
1034
+ * @returns A disposable handle whose promise resolves when the condition is met
1013
1035
  */
1014
- function waitForSelector(selectorRaw, stateRaw, timeoutRaw, pierceShadowRaw) {
1036
+ function createSelectorWait(selectorRaw, stateRaw, timeoutRaw, pierceShadowRaw) {
1015
1037
  const selector = String(selectorRaw ?? "").trim();
1016
1038
  const state = String(stateRaw ?? "visible") || "visible";
1017
- const timeout = typeof timeoutRaw === "number" && timeoutRaw > 0 ? timeoutRaw : 3e4;
1039
+ const timeout = typeof timeoutRaw === "number" && timeoutRaw >= 0 ? timeoutRaw : 3e4;
1018
1040
  const pierceShadow = pierceShadowRaw !== false;
1019
- return new Promise((resolve, reject) => {
1020
- let timeoutId = null;
1021
- let domReadyHandler = null;
1041
+ let dispose = () => {};
1042
+ const promise = new Promise((resolve, reject) => {
1022
1043
  let settled = false;
1023
- const clearTimer = () => {
1024
- if (timeoutId !== null) {
1025
- clearTimeout(timeoutId);
1026
- timeoutId = null;
1027
- }
1028
- };
1029
- if (checkState(findElement(selector, pierceShadow), state)) {
1030
- settled = true;
1031
- resolve(true);
1032
- return;
1033
- }
1044
+ let timeoutId;
1045
+ let shadowScanInterval;
1046
+ let domReadyHandler;
1047
+ let rescanShadowRoots;
1034
1048
  const observers = [];
1035
- let shadowScanInterval = null;
1036
- let rescanShadowRoots = null;
1037
- const cleanup = () => {
1038
- for (const obs of observers) obs.disconnect();
1039
- if (shadowScanInterval !== null) {
1040
- clearInterval(shadowScanInterval);
1041
- shadowScanInterval = null;
1042
- }
1049
+ const settle = (error) => {
1050
+ if (settled) return;
1051
+ settled = true;
1052
+ clearTimeout(timeoutId);
1053
+ clearInterval(shadowScanInterval);
1054
+ for (const observer of observers) observer.disconnect();
1055
+ observers.length = 0;
1056
+ rescanShadowRoots = void 0;
1043
1057
  if (domReadyHandler) {
1044
1058
  document.removeEventListener("DOMContentLoaded", domReadyHandler);
1045
- domReadyHandler = null;
1059
+ domReadyHandler = void 0;
1046
1060
  }
1061
+ if (error) reject(error);
1062
+ else resolve(true);
1047
1063
  };
1064
+ dispose = () => settle(/* @__PURE__ */ new Error("Selector wait disposed"));
1048
1065
  const check = () => {
1049
1066
  if (settled) return;
1050
- if (checkState(findElement(selector, pierceShadow), state)) {
1051
- settled = true;
1052
- clearTimer();
1053
- cleanup();
1054
- resolve(true);
1067
+ try {
1068
+ if (checkState(findElement(selector, pierceShadow), state)) settle();
1069
+ } catch (error) {
1070
+ settle(error instanceof Error ? error : new Error(String(error)));
1055
1071
  }
1056
1072
  };
1057
- if (!(document.body || document.documentElement)) {
1073
+ const setupObservers = () => {
1074
+ if (settled) return;
1075
+ const root = document.body || document.documentElement;
1076
+ if (!root) return;
1077
+ try {
1078
+ const mainObserver = new MutationObserver(() => {
1079
+ check();
1080
+ rescanShadowRoots?.();
1081
+ });
1082
+ observers.push(mainObserver);
1083
+ mainObserver.observe(root, {
1084
+ childList: true,
1085
+ subtree: true,
1086
+ attributes: true,
1087
+ attributeFilter: [
1088
+ "style",
1089
+ "class",
1090
+ "hidden",
1091
+ "disabled"
1092
+ ]
1093
+ });
1094
+ if (pierceShadow) {
1095
+ rescanShadowRoots = setupShadowObservers(check, observers, () => settled);
1096
+ shadowScanInterval = setInterval(() => {
1097
+ rescanShadowRoots?.();
1098
+ check();
1099
+ }, 100);
1100
+ }
1101
+ } catch (error) {
1102
+ settle(error instanceof Error ? error : new Error(String(error)));
1103
+ }
1104
+ };
1105
+ check();
1106
+ if (settled) return;
1107
+ if (document.body || document.documentElement) setupObservers();
1108
+ else {
1058
1109
  domReadyHandler = () => {
1110
+ if (settled) return;
1059
1111
  document.removeEventListener("DOMContentLoaded", domReadyHandler);
1060
- domReadyHandler = null;
1112
+ domReadyHandler = void 0;
1061
1113
  check();
1062
1114
  setupObservers();
1063
1115
  };
1064
1116
  document.addEventListener("DOMContentLoaded", domReadyHandler);
1065
- timeoutId = setTimeout(() => {
1066
- if (settled) return;
1067
- settled = true;
1068
- clearTimer();
1069
- cleanup();
1070
- reject(/* @__PURE__ */ new Error(`waitForSelector: Timeout ${timeout}ms exceeded waiting for "${selector}" to be ${state}`));
1071
- }, timeout);
1072
- return;
1073
1117
  }
1074
- const setupObservers = () => {
1075
- const root = document.body || document.documentElement;
1076
- if (!root) return;
1077
- const mainObserver = new MutationObserver(() => {
1078
- check();
1079
- rescanShadowRoots?.();
1080
- });
1081
- mainObserver.observe(root, {
1082
- childList: true,
1083
- subtree: true,
1084
- attributes: true,
1085
- attributeFilter: [
1086
- "style",
1087
- "class",
1088
- "hidden",
1089
- "disabled"
1090
- ]
1091
- });
1092
- observers.push(mainObserver);
1093
- if (pierceShadow) {
1094
- rescanShadowRoots = setupShadowObservers(check, observers);
1095
- shadowScanInterval = setInterval(() => {
1096
- rescanShadowRoots?.();
1097
- check();
1098
- }, 100);
1099
- }
1100
- };
1101
- setupObservers();
1102
- timeoutId = setTimeout(() => {
1103
- if (settled) return;
1104
- settled = true;
1105
- clearTimer();
1106
- cleanup();
1107
- reject(/* @__PURE__ */ new Error(`waitForSelector: Timeout ${timeout}ms exceeded waiting for "${selector}" to be ${state}`));
1108
- }, timeout);
1118
+ if (!settled && timeout > 0) timeoutId = setTimeout(() => settle(/* @__PURE__ */ new Error(`waitForSelector: Timeout ${timeout}ms exceeded waiting for "${selector}" to be ${state}`)), timeout);
1109
1119
  });
1120
+ promise.catch(() => {});
1121
+ return {
1122
+ promise,
1123
+ dispose: () => dispose()
1124
+ };
1110
1125
  }
1111
1126
  //#endregion
1112
1127
  //#region dom/locatorScripts/registry.ts
@@ -1120,7 +1135,7 @@ var locatorScripts = Object.freeze({
1120
1135
  resolveCssSelector,
1121
1136
  resolveTextSelector,
1122
1137
  resolveXPathMainWorld,
1123
- waitForSelector
1138
+ createSelectorWait
1124
1139
  });
1125
1140
  //#endregion
1126
1141
  //#region content-script.ts
@@ -31,5 +31,5 @@
31
31
  }
32
32
  ],
33
33
  "options_page": "wake-service-worker.html",
34
- "version": "1.0.2"
34
+ "version": "1.1.0"
35
35
  }