@itmetsam/samurai-framework 0.9.0 → 0.12.0

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 (331) hide show
  1. package/README.md +4 -5
  2. package/dist/assert/assertion-error.d.ts +1 -1
  3. package/dist/assert/expect.d.ts +5 -50
  4. package/dist/assert/expect.js +5 -182
  5. package/dist/assert/locator-assertions.d.ts +22 -0
  6. package/dist/assert/locator-assertions.js +61 -0
  7. package/dist/assert/pending-assertions.d.ts +7 -0
  8. package/dist/assert/pending-assertions.js +40 -0
  9. package/dist/assert/text-match.d.ts +4 -0
  10. package/dist/assert/text-match.js +11 -0
  11. package/dist/assert/value-assertions.d.ts +23 -0
  12. package/dist/assert/value-assertions.js +82 -0
  13. package/dist/browser/browser-context.d.ts +0 -1
  14. package/dist/browser/browser-context.js +13 -5
  15. package/dist/browser/browser-finder.js +5 -9
  16. package/dist/browser/browser.d.ts +3 -31
  17. package/dist/browser/browser.js +36 -219
  18. package/dist/browser/chrome-launch.d.ts +14 -1
  19. package/dist/browser/chrome-launch.js +44 -1
  20. package/dist/browser/dialog-handling.d.ts +32 -0
  21. package/dist/browser/dialog-handling.js +85 -0
  22. package/dist/browser/document-watcher.d.ts +44 -0
  23. package/dist/browser/document-watcher.js +160 -0
  24. package/dist/browser/download-folders.d.ts +0 -1
  25. package/dist/browser/download-folders.js +7 -15
  26. package/dist/browser/download.js +5 -10
  27. package/dist/browser/downloads-dir.js +2 -2
  28. package/dist/browser/emit.d.ts +7 -0
  29. package/dist/browser/emit.js +16 -0
  30. package/dist/browser/emulation.d.ts +1 -1
  31. package/dist/browser/emulation.js +54 -18
  32. package/dist/browser/file-chooser.js +4 -11
  33. package/dist/browser/history.d.ts +6 -0
  34. package/dist/browser/history.js +89 -0
  35. package/dist/browser/index.d.ts +2 -0
  36. package/dist/browser/index.js +2 -0
  37. package/dist/browser/init-script.d.ts +2 -0
  38. package/dist/browser/init-script.js +16 -0
  39. package/dist/browser/launch-firefox.d.ts +20 -0
  40. package/dist/browser/launch-firefox.js +66 -0
  41. package/dist/browser/launch-flags.d.ts +18 -0
  42. package/dist/browser/launch-flags.js +100 -0
  43. package/dist/browser/lazy.d.ts +21 -0
  44. package/dist/browser/lazy.js +44 -0
  45. package/dist/browser/page-events.d.ts +44 -0
  46. package/dist/browser/page-events.js +58 -0
  47. package/dist/browser/page-logs.js +2 -9
  48. package/dist/browser/page-navigation.d.ts +58 -0
  49. package/dist/browser/page-navigation.js +108 -0
  50. package/dist/browser/page-network.d.ts +69 -0
  51. package/dist/browser/page-network.js +163 -0
  52. package/dist/browser/page-settings.d.ts +35 -0
  53. package/dist/browser/page-settings.js +51 -0
  54. package/dist/browser/page-transfers.d.ts +36 -0
  55. package/dist/browser/page-transfers.js +50 -0
  56. package/dist/browser/page.d.ts +39 -154
  57. package/dist/browser/page.js +134 -758
  58. package/dist/browser/pdf.d.ts +1 -1
  59. package/dist/browser/session.d.ts +16 -0
  60. package/dist/browser/session.js +32 -0
  61. package/dist/browser/viewport.d.ts +19 -0
  62. package/dist/browser/viewport.js +72 -0
  63. package/dist/browser/warm-up.d.ts +1 -1
  64. package/dist/browser/warm-up.js +2 -1
  65. package/dist/browsers/index.d.ts +1 -1
  66. package/dist/browsers/index.js +1 -1
  67. package/dist/browsers/pins.d.ts +0 -2
  68. package/dist/browsers/pins.js +0 -2
  69. package/dist/browsers/store.d.ts +0 -2
  70. package/dist/browsers/store.js +1 -2
  71. package/dist/cli/args.d.ts +19 -7
  72. package/dist/cli/args.js +18 -9
  73. package/dist/cli/browsers.d.ts +1 -2
  74. package/dist/cli/browsers.js +2 -2
  75. package/dist/cli/console-reporter.d.ts +4 -0
  76. package/dist/cli/console-reporter.js +15 -7
  77. package/dist/cli/flow-reporter.js +2 -1
  78. package/dist/cli/flow.d.ts +1 -2
  79. package/dist/cli/flow.js +25 -22
  80. package/dist/cli/init.js +3 -8
  81. package/dist/cli/io.d.ts +10 -0
  82. package/dist/cli/io.js +1 -0
  83. package/dist/cli/main.d.ts +3 -10
  84. package/dist/cli/main.js +11 -137
  85. package/dist/cli/paint.d.ts +2 -0
  86. package/dist/cli/paint.js +4 -0
  87. package/dist/cli/record.d.ts +4 -0
  88. package/dist/cli/record.js +93 -0
  89. package/dist/cli/usage.d.ts +1 -0
  90. package/dist/cli/usage.js +58 -0
  91. package/dist/cli/version.d.ts +2 -0
  92. package/dist/cli/version.js +7 -0
  93. package/dist/config/browsers.d.ts +11 -0
  94. package/dist/config/browsers.js +32 -0
  95. package/dist/config/config.js +8 -1
  96. package/dist/config/mask.d.ts +1 -1
  97. package/dist/config/mask.js +7 -2
  98. package/dist/config/run-settings.d.ts +1 -2
  99. package/dist/config/run-settings.js +1 -6
  100. package/dist/config/timeouts.d.ts +2 -0
  101. package/dist/config/timeouts.js +13 -0
  102. package/dist/config/workers.d.ts +14 -0
  103. package/dist/config/workers.js +29 -0
  104. package/dist/flows/run/branch-context.d.ts +14 -0
  105. package/dist/flows/run/branch-context.js +35 -0
  106. package/dist/flows/run/handlers/condition.d.ts +1 -1
  107. package/dist/flows/run/handlers/end.d.ts +1 -1
  108. package/dist/flows/run/handlers/group.d.ts +1 -1
  109. package/dist/flows/run/handlers/index.d.ts +2 -2
  110. package/dist/flows/run/handlers/parallel.d.ts +1 -1
  111. package/dist/flows/run/handlers/start.d.ts +1 -1
  112. package/dist/flows/run/handlers/support.d.ts +3 -24
  113. package/dist/flows/run/handlers/support.js +1 -1
  114. package/dist/flows/run/handlers/test.d.ts +1 -1
  115. package/dist/flows/run/handlers/types.d.ts +27 -0
  116. package/dist/flows/run/handlers/types.js +1 -0
  117. package/dist/flows/run/index.d.ts +1 -1
  118. package/dist/flows/run/prepare-flow.d.ts +14 -0
  119. package/dist/flows/run/prepare-flow.js +44 -0
  120. package/dist/flows/run/project.d.ts +33 -2
  121. package/dist/flows/run/project.js +139 -49
  122. package/dist/flows/run/run.d.ts +8 -4
  123. package/dist/flows/run/run.js +30 -194
  124. package/dist/flows/run/walker.d.ts +56 -0
  125. package/dist/flows/run/walker.js +225 -0
  126. package/dist/flows/steps/catalog.d.ts +1 -1
  127. package/dist/flows/steps/handler.d.ts +1 -1
  128. package/dist/flows/steps/set.d.ts +1 -1
  129. package/dist/index.js +1 -1
  130. package/dist/locator/actionability.d.ts +33 -0
  131. package/dist/locator/actionability.js +104 -0
  132. package/dist/locator/chain-resolver.d.ts +20 -0
  133. package/dist/locator/chain-resolver.js +102 -0
  134. package/dist/locator/document-swap.d.ts +1 -0
  135. package/dist/locator/document-swap.js +11 -0
  136. package/dist/locator/element-state.d.ts +3 -1
  137. package/dist/locator/element-state.js +11 -3
  138. package/dist/locator/file-actions.d.ts +35 -0
  139. package/dist/locator/file-actions.js +55 -0
  140. package/dist/{script/helpers.js → locator/helper-realm.js} +2 -2
  141. package/dist/locator/input-actions.d.ts +18 -0
  142. package/dist/locator/input-actions.js +80 -0
  143. package/dist/locator/label-locator.d.ts +1 -1
  144. package/dist/locator/label-locator.js +5 -12
  145. package/dist/locator/locator.d.ts +14 -37
  146. package/dist/locator/locator.js +68 -424
  147. package/dist/locator/page-scripts.d.ts +16 -0
  148. package/dist/locator/page-scripts.js +54 -0
  149. package/dist/locator/page-search.d.ts +10 -0
  150. package/dist/locator/page-search.js +19 -0
  151. package/dist/locator/selector-errors.d.ts +0 -4
  152. package/dist/locator/selector-errors.js +0 -7
  153. package/dist/locator/selector.d.ts +14 -13
  154. package/dist/locator/selector.js +10 -16
  155. package/dist/locator/text-locator.d.ts +1 -1
  156. package/dist/locator/text-locator.js +5 -9
  157. package/dist/logger/index.d.ts +6 -0
  158. package/dist/logger/index.js +14 -7
  159. package/dist/network/bidi-wire.d.ts +10 -0
  160. package/dist/network/bidi-wire.js +38 -0
  161. package/dist/network/intercept.d.ts +30 -0
  162. package/dist/network/intercept.js +130 -0
  163. package/dist/network/network-tracker.d.ts +10 -47
  164. package/dist/network/network-tracker.js +34 -68
  165. package/dist/network/network-types.d.ts +33 -0
  166. package/dist/network/network-types.js +1 -0
  167. package/dist/network/network-wait.d.ts +7 -2
  168. package/dist/network/network-wait.js +14 -4
  169. package/dist/network/response.d.ts +1 -1
  170. package/dist/network/response.js +1 -1
  171. package/dist/network/route.d.ts +92 -0
  172. package/dist/network/route.js +142 -0
  173. package/dist/network/router.d.ts +6 -91
  174. package/dist/network/router.js +25 -258
  175. package/dist/recorder/expectation.d.ts +5 -0
  176. package/dist/recorder/expectation.js +20 -0
  177. package/dist/recorder/files-recorder.d.ts +30 -0
  178. package/dist/recorder/files-recorder.js +88 -0
  179. package/dist/recorder/normalise.d.ts +17 -6
  180. package/dist/recorder/normalise.js +96 -91
  181. package/dist/recorder/options.d.ts +56 -0
  182. package/dist/recorder/options.js +1 -0
  183. package/dist/recorder/page-messages.d.ts +35 -0
  184. package/dist/recorder/page-messages.js +26 -0
  185. package/dist/recorder/ranking.js +1 -0
  186. package/dist/recorder/realms.d.ts +6 -0
  187. package/dist/recorder/realms.js +13 -0
  188. package/dist/recorder/recorder.d.ts +10 -74
  189. package/dist/recorder/recorder.js +39 -199
  190. package/dist/recorder/session-browser.d.ts +22 -0
  191. package/dist/recorder/session-browser.js +21 -0
  192. package/dist/recorder/session-secrets.d.ts +8 -0
  193. package/dist/recorder/session-secrets.js +45 -0
  194. package/dist/recorder/session.d.ts +7 -13
  195. package/dist/recorder/session.js +22 -117
  196. package/dist/recorder/spec-source.d.ts +39 -0
  197. package/dist/recorder/spec-source.js +84 -0
  198. package/dist/recorder/toolbar/action-guard.d.ts +21 -0
  199. package/dist/recorder/toolbar/action-guard.js +43 -0
  200. package/dist/recorder/toolbar/actions/assert.d.ts +4 -0
  201. package/dist/recorder/toolbar/actions/assert.js +52 -0
  202. package/dist/recorder/toolbar/actions/insert.d.ts +6 -0
  203. package/dist/recorder/toolbar/actions/insert.js +93 -0
  204. package/dist/recorder/toolbar/actions/pick.d.ts +8 -0
  205. package/dist/recorder/toolbar/actions/pick.js +26 -0
  206. package/dist/recorder/toolbar/actions/reply.d.ts +7 -0
  207. package/dist/recorder/toolbar/actions/reply.js +10 -0
  208. package/dist/recorder/toolbar/controller-fixture.d.ts +47 -0
  209. package/dist/recorder/toolbar/controller-fixture.js +129 -0
  210. package/dist/recorder/toolbar/controller.d.ts +12 -78
  211. package/dist/recorder/toolbar/controller.js +35 -247
  212. package/dist/recorder/toolbar/inspect.d.ts +1 -3
  213. package/dist/recorder/toolbar/inspect.js +2 -2
  214. package/dist/recorder/toolbar/page-scope.d.ts +19 -0
  215. package/dist/recorder/toolbar/page-scope.js +43 -0
  216. package/dist/recorder/toolbar/protocol.d.ts +2 -1
  217. package/dist/recorder/toolbar/source/attach.d.ts +2 -0
  218. package/dist/recorder/toolbar/source/attach.js +41 -0
  219. package/dist/recorder/toolbar/source/dom.d.ts +2 -0
  220. package/dist/recorder/toolbar/source/dom.js +47 -0
  221. package/dist/recorder/toolbar/source/drag.d.ts +2 -0
  222. package/dist/recorder/toolbar/source/drag.js +32 -0
  223. package/dist/recorder/toolbar/source/events-page.d.ts +2 -0
  224. package/dist/recorder/toolbar/source/events-page.js +67 -0
  225. package/dist/recorder/toolbar/source/events-toolbar.d.ts +2 -0
  226. package/dist/recorder/toolbar/source/events-toolbar.js +35 -0
  227. package/dist/recorder/toolbar/source/fields.d.ts +2 -0
  228. package/dist/recorder/toolbar/source/fields.js +44 -0
  229. package/dist/recorder/toolbar/source/handlers.d.ts +2 -0
  230. package/dist/recorder/toolbar/source/handlers.js +134 -0
  231. package/dist/recorder/toolbar/source/names.d.ts +2 -0
  232. package/dist/recorder/toolbar/source/names.js +12 -0
  233. package/dist/recorder/toolbar/source/picking.d.ts +2 -0
  234. package/dist/recorder/toolbar/source/picking.js +72 -0
  235. package/dist/recorder/toolbar/source/placement.d.ts +2 -0
  236. package/dist/recorder/toolbar/source/placement.js +45 -0
  237. package/dist/recorder/toolbar/source/recorder-side.d.ts +2 -0
  238. package/dist/recorder/toolbar/source/recorder-side.js +54 -0
  239. package/dist/recorder/toolbar/source/setup.d.ts +2 -0
  240. package/dist/recorder/toolbar/source/setup.js +50 -0
  241. package/dist/recorder/toolbar/source/views-panels.d.ts +2 -0
  242. package/dist/recorder/toolbar/source/views-panels.js +150 -0
  243. package/dist/recorder/toolbar/source/views-rail.d.ts +2 -0
  244. package/dist/recorder/toolbar/source/views-rail.js +40 -0
  245. package/dist/recorder/toolbar/source/views-render.d.ts +2 -0
  246. package/dist/recorder/toolbar/source/views-render.js +48 -0
  247. package/dist/recorder/toolbar/source.js +30 -856
  248. package/dist/recorder/toolbar/types.d.ts +70 -0
  249. package/dist/recorder/toolbar/types.js +1 -0
  250. package/dist/recorder/toolbar-bridge.d.ts +43 -0
  251. package/dist/recorder/toolbar-bridge.js +106 -0
  252. package/dist/runner/browsers.d.ts +10 -0
  253. package/dist/runner/browsers.js +12 -0
  254. package/dist/runner/groups.d.ts +5 -1
  255. package/dist/runner/groups.js +9 -2
  256. package/dist/runner/options.d.ts +31 -0
  257. package/dist/runner/options.js +1 -0
  258. package/dist/runner/pooled-run.d.ts +43 -0
  259. package/dist/runner/pooled-run.js +75 -0
  260. package/dist/runner/ports.d.ts +2 -0
  261. package/dist/runner/ports.js +2 -0
  262. package/dist/runner/registry.d.ts +1 -1
  263. package/dist/runner/registry.js +1 -1
  264. package/dist/runner/reporter.d.ts +40 -11
  265. package/dist/runner/reporter.js +69 -16
  266. package/dist/runner/run-test.d.ts +16 -0
  267. package/dist/runner/run-test.js +178 -0
  268. package/dist/runner/run.d.ts +5 -2
  269. package/dist/runner/run.js +28 -28
  270. package/dist/runner/select.d.ts +24 -0
  271. package/dist/runner/select.js +77 -0
  272. package/dist/runner/test-dir.d.ts +13 -0
  273. package/dist/runner/test-dir.js +27 -0
  274. package/dist/runner/test-runner.d.ts +27 -50
  275. package/dist/runner/test-runner.js +102 -264
  276. package/dist/runner/worker-pool.d.ts +67 -0
  277. package/dist/runner/worker-pool.js +275 -0
  278. package/dist/runner/worker-process.d.ts +6 -0
  279. package/dist/runner/worker-process.js +54 -0
  280. package/dist/runner/worker-protocol.d.ts +67 -0
  281. package/dist/runner/worker-protocol.js +1 -0
  282. package/dist/runner/worker-session.d.ts +7 -0
  283. package/dist/runner/worker-session.js +60 -0
  284. package/dist/runner/worker.d.ts +26 -0
  285. package/dist/runner/worker.js +121 -0
  286. package/dist/runner/workers-option.d.ts +15 -0
  287. package/dist/runner/workers-option.js +22 -0
  288. package/dist/script/call-function.d.ts +1 -9
  289. package/dist/script/call-function.js +2 -19
  290. package/dist/script/element-handle.d.ts +1 -2
  291. package/dist/script/element-handle.js +1 -2
  292. package/dist/script/serialize.d.ts +3 -12
  293. package/dist/script/serialize.js +53 -62
  294. package/dist/steps/parse/call.d.ts +3 -0
  295. package/dist/steps/parse/call.js +81 -0
  296. package/dist/steps/parse/expectation.d.ts +5 -0
  297. package/dist/steps/parse/expectation.js +71 -0
  298. package/dist/steps/parse/file-entry.d.ts +4 -0
  299. package/dist/steps/parse/file-entry.js +74 -0
  300. package/dist/steps/parse/literals.d.ts +4 -0
  301. package/dist/steps/parse/literals.js +28 -0
  302. package/dist/steps/parse/step.d.ts +4 -0
  303. package/dist/steps/parse/step.js +81 -0
  304. package/dist/steps/parse.js +2 -323
  305. package/dist/transport/bidi-connection.d.ts +4 -2
  306. package/dist/transport/bidi-connection.js +9 -43
  307. package/dist/transport/log-redaction.d.ts +13 -0
  308. package/dist/transport/log-redaction.js +51 -0
  309. package/dist/transport/subscriptions.js +18 -13
  310. package/dist/transport/unsupported.d.ts +7 -0
  311. package/dist/transport/unsupported.js +13 -0
  312. package/dist/types/bidi-modules/browser.d.ts +1 -12
  313. package/dist/types/bidi-modules/browsing-context.d.ts +8 -18
  314. package/dist/types/bidi-modules/emulation.d.ts +29 -21
  315. package/dist/types/bidi-modules/input.d.ts +1 -9
  316. package/dist/types/bidi-modules/network.d.ts +29 -24
  317. package/dist/types/bidi-modules/script.d.ts +0 -9
  318. package/dist/types/bidi-modules/session.d.ts +0 -8
  319. package/dist/types/bidi-modules/storage.d.ts +0 -5
  320. package/dist/types/bidi.d.ts +7 -37
  321. package/dist/types/config.d.ts +17 -3
  322. package/dist/types/test.d.ts +15 -2
  323. package/dist/wait/wait-until.d.ts +1 -5
  324. package/dist/wait/wait-until.js +7 -16
  325. package/package.json +3 -2
  326. package/dist/config/default.d.ts +0 -2
  327. package/dist/config/default.js +0 -3
  328. package/dist/types/bidi-modules/web-extensions.d.ts +0 -9
  329. package/dist/types/bidi-protocols/command.d.ts +0 -7
  330. package/dist/types/bidi-protocols/event.d.ts +0 -17
  331. /package/dist/{script/helpers.d.ts → locator/helper-realm.d.ts} +0 -0
@@ -0,0 +1,104 @@
1
+ import { BiDiError } from "../transport/bidi-error.js";
2
+ import { ScriptError } from "../script/call-function.js";
3
+ import { resolveTimeout } from "../config/timeouts.js";
4
+ import { waitUntil, WaitTimeoutError } from "../wait/wait-until.js";
5
+ import { ActionTimeoutError } from "./action-timeout-error.js";
6
+ import { isDocumentSwap } from "./document-swap.js";
7
+ import { allPass, DETACHED_STATE, evaluateChecks, parseElementState, PROBE_ELEMENT, } from "./element-state.js";
8
+ import { CALL_PROBE, HELPER_SANDBOX, HELPERS_MISSING, } from "./helper-realm.js";
9
+ import { ATTACHED_ONLY } from "./page-scripts.js";
10
+ /** Reads the element's actionability state: one search, one probe */
11
+ export async function probeState(ctx, options) {
12
+ const el = await ctx.resolveOne();
13
+ if (!el)
14
+ return DETACHED_STATE;
15
+ try {
16
+ return parseElementState(await callProbe(ctx, el, options));
17
+ }
18
+ catch (err) {
19
+ // The document changed between the search and the probe
20
+ if ((err instanceof BiDiError && err.code === "no such node") ||
21
+ isDocumentSwap(err))
22
+ return DETACHED_STATE;
23
+ throw err;
24
+ }
25
+ }
26
+ async function callProbe(ctx, el, options) {
27
+ const args = [el, { ...options }];
28
+ if (!ctx.helpers)
29
+ return ctx.call(PROBE_ELEMENT, args);
30
+ await ctx.helpers.ensureInstalled();
31
+ let result = await ctx.call(CALL_PROBE, args, HELPER_SANDBOX);
32
+ if (result === HELPERS_MISSING) {
33
+ // A document got past the registration (e.g. created while it was being set up): install again once
34
+ await ctx.helpers.reinstall();
35
+ result = await ctx.call(CALL_PROBE, args, HELPER_SANDBOX);
36
+ if (result === HELPERS_MISSING) {
37
+ throw new ScriptError("the samurai helpers are missing in the page after reinstalling", "probeElement");
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ /**
43
+ * Probes until every required check passes and returns that state.
44
+ * Throws ActionTimeoutError describing the last probe on timeout.
45
+ */
46
+ export async function waitForActionable(ctx, action, checks, probe, options) {
47
+ const timeout = await resolveTimeout(options?.timeout);
48
+ // Stability compares two probes; with timeout 0 there is only one
49
+ const required = options?.force
50
+ ? ATTACHED_ONLY
51
+ : timeout === 0
52
+ ? checks.filter((check) => check !== "stable")
53
+ : checks;
54
+ let lastChecks;
55
+ try {
56
+ return await waitUntil(() => probeState(ctx, probe), (current, previous) => {
57
+ lastChecks = evaluateChecks(required, current, previous);
58
+ return allPass(lastChecks);
59
+ }, { timeout });
60
+ }
61
+ catch (err) {
62
+ if (!(err instanceof WaitTimeoutError))
63
+ throw err;
64
+ const last = err.last;
65
+ throw new ActionTimeoutError({
66
+ action,
67
+ selector: ctx.selector,
68
+ timeout,
69
+ reason: !last
70
+ ? "unreadable"
71
+ : last.attached
72
+ ? "not-actionable"
73
+ : "not-attached",
74
+ checks: lastChecks,
75
+ coveredBy: lastChecks?.["hit target"] === "fail"
76
+ ? (last?.hitTarget ?? undefined)
77
+ : undefined,
78
+ });
79
+ }
80
+ }
81
+ /** Waits until the element reaches `state` (default "visible"); does not scroll */
82
+ export async function waitForState(ctx, { state = "visible", timeout }) {
83
+ const resolved = await resolveTimeout(timeout);
84
+ const reached = {
85
+ attached: (current) => current.attached,
86
+ detached: (current) => !current.attached,
87
+ visible: (current) => current.attached && current.visible,
88
+ hidden: (current) => !current.attached || !current.visible,
89
+ };
90
+ try {
91
+ await waitUntil(() => probeState(ctx, { scroll: false, hitTest: false }), reached[state], { timeout: resolved });
92
+ }
93
+ catch (err) {
94
+ if (!(err instanceof WaitTimeoutError))
95
+ throw err;
96
+ throw new ActionTimeoutError({
97
+ action: "waitFor",
98
+ selector: ctx.selector,
99
+ timeout: resolved,
100
+ reason: err.last === undefined ? "unreadable" : "wrong-state",
101
+ state,
102
+ });
103
+ }
104
+ }
@@ -0,0 +1,20 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
3
+ import { ElementHandle } from "../script/element-handle.js";
4
+ import type { Arg } from "../script/serialize.js";
5
+ import { type Selector } from "./selector.js";
6
+ /** Calls `fn` in the page; the locator turns a throwing function into a "Failed to locate element" error */
7
+ export type CallInPage = <T>(fn: string, args: Arg[], sandbox?: string) => Promise<T>;
8
+ /** Finds the elements a chain of selector steps matches: every step searches inside the previous step's matches */
9
+ export declare class ChainResolver {
10
+ private readonly connector;
11
+ private readonly contextId;
12
+ private readonly call;
13
+ constructor(connector: BiDiConnector, contextId: BrowsingContext, call: CallInPage);
14
+ /** In document order; `max` limits the final result. Nothing is cached: the first match can change between polls */
15
+ resolveChain(chain: readonly Selector[], max?: number): Promise<ElementHandle[]>;
16
+ private locateStep;
17
+ private locateNatively;
18
+ /** Runs a page-side search `(starts, value, match, ignoreCase) => elements` */
19
+ private locateInPage;
20
+ }
@@ -0,0 +1,102 @@
1
+ import { BiDiError } from "../transport/bidi-error.js";
2
+ import { UnsupportedOperationError } from "../transport/unsupported.js";
3
+ import { ElementHandle } from "../script/element-handle.js";
4
+ import { isDocumentSwap } from "./document-swap.js";
5
+ import { LABEL_LOCATE } from "./label-locator.js";
6
+ import { InvalidSelectorError } from "./selector-errors.js";
7
+ import { describeSelector, toBiDiLocator } from "./selector.js";
8
+ import { TEXT_LOCATE } from "./text-locator.js";
9
+ /** Locator types a connection's browser rejected, so they are not tried again on every poll */
10
+ const unsupportedByConnector = new WeakMap();
11
+ function unsupportedTypes(connector) {
12
+ let types = unsupportedByConnector.get(connector);
13
+ if (!types)
14
+ unsupportedByConnector.set(connector, (types = new Set()));
15
+ return types;
16
+ }
17
+ /** Finds the elements a chain of selector steps matches: every step searches inside the previous step's matches */
18
+ export class ChainResolver {
19
+ constructor(connector, contextId, call) {
20
+ this.connector = connector;
21
+ this.contextId = contextId;
22
+ this.call = call;
23
+ }
24
+ /** In document order; `max` limits the final result. Nothing is cached: the first match can change between polls */
25
+ async resolveChain(chain, max) {
26
+ let nodes;
27
+ for (const [index, selector] of chain.entries()) {
28
+ if (selector.kind === "nth") {
29
+ const picked = nodes?.[selector.index];
30
+ nodes = picked ? [picked] : [];
31
+ }
32
+ else {
33
+ try {
34
+ nodes = await this.locateStep(selector, index > 0, nodes, index === chain.length - 1 ? max : undefined);
35
+ }
36
+ catch (err) {
37
+ // The page is swapping documents: nothing matches right now, and the waits that poll try again
38
+ if (isDocumentSwap(err))
39
+ return [];
40
+ throw err;
41
+ }
42
+ }
43
+ if (nodes.length === 0)
44
+ return [];
45
+ }
46
+ return nodes ?? [];
47
+ }
48
+ async locateStep(selector, scoped, startNodes, max) {
49
+ if (selector.kind === "label")
50
+ return this.locateInPage(LABEL_LOCATE, selector, startNodes, max);
51
+ if (selector.kind === "text" &&
52
+ unsupportedTypes(this.connector).has("innerText")) {
53
+ return this.locateInPage(TEXT_LOCATE, selector, startNodes, max);
54
+ }
55
+ try {
56
+ return await this.locateNatively(selector, scoped, startNodes, max);
57
+ }
58
+ catch (err) {
59
+ if (err instanceof BiDiError && err.code === "invalid selector") {
60
+ throw new InvalidSelectorError(describeSelector(selector, scoped), err.message);
61
+ }
62
+ if (err instanceof BiDiError && err.code === "unsupported operation") {
63
+ if (selector.kind === "text") {
64
+ // The browser has no innerText locator (Firefox): remember that and search in the page instead
65
+ unsupportedTypes(this.connector).add("innerText");
66
+ return this.locateInPage(TEXT_LOCATE, selector, startNodes, max);
67
+ }
68
+ throw new UnsupportedOperationError(`${describeSelector(selector, scoped)}: this kind of locator is not supported by the browser (${err.message})`);
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+ async locateNatively(selector, scoped, startNodes, max) {
74
+ const { nodes } = await this.connector.send("browsingContext.locateNodes", {
75
+ context: this.contextId,
76
+ locator: toBiDiLocator(selector, scoped),
77
+ ...(max !== undefined && { maxNodeCount: max }),
78
+ // Only the references are needed; skip serializing the matched subtrees
79
+ serializationOptions: { maxDomDepth: 0 },
80
+ ...(startNodes && {
81
+ startNodes: startNodes.map(({ sharedId }) => ({ sharedId })),
82
+ }),
83
+ });
84
+ const found = new Map();
85
+ for (const node of nodes) {
86
+ if (node.sharedId !== undefined && !found.has(node.sharedId)) {
87
+ found.set(node.sharedId, new ElementHandle(node.sharedId));
88
+ }
89
+ }
90
+ return [...found.values()];
91
+ }
92
+ /** Runs a page-side search `(starts, value, match, ignoreCase) => elements` */
93
+ async locateInPage(fn, selector, startNodes, max) {
94
+ const found = await this.call(fn, [
95
+ startNodes ?? [],
96
+ selector.value,
97
+ selector.match,
98
+ selector.ignoreCase,
99
+ ]);
100
+ return max === undefined ? found : found.slice(0, max);
101
+ }
102
+ }
@@ -0,0 +1 @@
1
+ export declare function isDocumentSwap(err: unknown): boolean;
@@ -0,0 +1,11 @@
1
+ import { BiDiError } from "../transport/bidi-error.js";
2
+ /**
3
+ * Chrome answers a command sent while the page swaps documents with an unknown error that says the context
4
+ * is gone for the moment; the same call a moment later works.
5
+ */
6
+ const DOCUMENT_SWAP = /Cannot find context with specified id|execution contexts? (was |were )?(cleared|destroyed)/i;
7
+ export function isDocumentSwap(err) {
8
+ return (err instanceof BiDiError &&
9
+ err.code === "unknown error" &&
10
+ DOCUMENT_SWAP.test(err.message));
11
+ }
@@ -1,4 +1,4 @@
1
- export interface ElementBox {
1
+ interface ElementBox {
2
2
  x: number;
3
3
  y: number;
4
4
  width: number;
@@ -39,6 +39,8 @@ export declare const DETACHED_STATE: ElementState;
39
39
  export declare const PROBE_ELEMENT: string;
40
40
  /** Checks the probe result is a complete state; throws on anything else */
41
41
  export declare function parseElementState(value: unknown): ElementState;
42
+ export declare function describeType(value: unknown): string;
42
43
  /** Evaluates the required checks in order; after the first failure the rest are pending */
43
44
  export declare function evaluateChecks(required: readonly CheckName[], current: ElementState, previous: ElementState | undefined): CheckResults;
44
45
  export declare function allPass(results: CheckResults): boolean;
46
+ export {};
@@ -1,4 +1,12 @@
1
- const TEXT_INPUT_TYPES = ["text", "search", "email", "url", "tel", "password", "number"];
1
+ const TEXT_INPUT_TYPES = [
2
+ "text",
3
+ "search",
4
+ "email",
5
+ "url",
6
+ "tel",
7
+ "password",
8
+ "number",
9
+ ];
2
10
  /** State of an element that is not in the document (never matched, or gone) */
3
11
  export const DETACHED_STATE = {
4
12
  attached: false,
@@ -16,7 +24,7 @@ export const DETACHED_STATE = {
16
24
  */
17
25
  export const PROBE_ELEMENT = `(el, options) => {
18
26
  if (!el || !el.isConnected) {
19
- return { attached: false, visible: false, enabled: false, editable: false, box: null, hitTarget: null };
27
+ return ${JSON.stringify(DETACHED_STATE)};
20
28
  }
21
29
  if (options.scroll) {
22
30
  const before = el.getBoundingClientRect();
@@ -62,7 +70,7 @@ export function parseElementState(value) {
62
70
  }
63
71
  return state;
64
72
  }
65
- function describeType(value) {
73
+ export function describeType(value) {
66
74
  return value === null ? "null" : typeof value;
67
75
  }
68
76
  function sameBox(a, b) {
@@ -0,0 +1,35 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ import type { BrowsingContext } from "../types/bidi-modules/browsing-context.js";
3
+ import type { ElementHandle } from "../script/element-handle.js";
4
+ import type { Arg } from "../script/serialize.js";
5
+ import { type FileInput } from "../browser/generated-files.js";
6
+ import type { CheckName, ProbeOptions } from "./element-state.js";
7
+ import type { ActionOptions } from "./actionability.js";
8
+ /** What the file actions need of a locator */
9
+ export interface ElementContext {
10
+ readonly connector: BiDiConnector;
11
+ readonly contextId: BrowsingContext;
12
+ /** The locator's description for error messages */
13
+ readonly selector: string;
14
+ call<T>(fn: string, args: Arg[], sandbox?: string): Promise<T>;
15
+ waitForActionable(action: string, checks: readonly CheckName[], probe: ProbeOptions, options: ActionOptions | undefined): Promise<unknown>;
16
+ /** The element the locator matches now; throws when none does */
17
+ requireElement(): Promise<ElementHandle>;
18
+ /** The error for an operation that needs an element when none matches */
19
+ noElementError(): Error;
20
+ }
21
+ /**
22
+ * Sets the files of an `<input type=file>`; `[]` clears it. Waits for the input to be attached and enabled;
23
+ * it need not be visible, file inputs are often hidden behind a label.
24
+ */
25
+ export declare function setInputFiles(ctx: ElementContext, files: FileInput | FileInput[], options?: {
26
+ timeout?: number;
27
+ }): Promise<void>;
28
+ /**
29
+ * Drops files on the element, as dragging them from the desktop does: `dragenter`, `dragover` and `drop`
30
+ * with a `DataTransfer` holding the files. The events are synthetic (`isTrusted` is false).
31
+ * Waits for the element to be attached and visible.
32
+ */
33
+ export declare function dropFiles(ctx: ElementContext, files: FileInput | FileInput[], options?: {
34
+ timeout?: number;
35
+ }): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import { BiDiError } from "../transport/bidi-error.js";
2
+ import { isGeneratedFile, resolveFileInputs, } from "../browser/generated-files.js";
3
+ import { DROP_CHECKS, DROP_EVENTS, DROP_INPUT, DROP_SANDBOX, FILE_INPUT_CHECKS, FILE_INPUT_INFO, } from "./page-scripts.js";
4
+ /**
5
+ * Sets the files of an `<input type=file>`; `[]` clears it. Waits for the input to be attached and enabled;
6
+ * it need not be visible, file inputs are often hidden behind a label.
7
+ */
8
+ export async function setInputFiles(ctx, files, options) {
9
+ const paths = await resolveFileInputs(files, "setInputFiles");
10
+ await ctx.waitForActionable("setInputFiles", FILE_INPUT_CHECKS, { scroll: false, hitTest: false }, options);
11
+ const el = await ctx.requireElement();
12
+ let info;
13
+ try {
14
+ info = await ctx.call(FILE_INPUT_INFO, [el]);
15
+ }
16
+ catch (err) {
17
+ if (err instanceof BiDiError && err.code === "no such node")
18
+ throw ctx.noElementError();
19
+ throw err;
20
+ }
21
+ if (info.tag !== "input" || info.type !== "file") {
22
+ const found = info.tag === "input"
23
+ ? `<input type="${info.type || "text"}">`
24
+ : `<${info.tag}>`;
25
+ throw new Error(`setInputFiles(): ${ctx.selector} is not an <input type=file> (found ${found})`);
26
+ }
27
+ if (paths.length > 1 && !info.multiple) {
28
+ throw new Error(`setInputFiles(): ${ctx.selector} does not accept multiple files`);
29
+ }
30
+ await ctx.connector.send("input.setFiles", {
31
+ context: ctx.contextId,
32
+ element: { sharedId: el.sharedId },
33
+ files: paths,
34
+ });
35
+ }
36
+ /**
37
+ * Drops files on the element, as dragging them from the desktop does: `dragenter`, `dragover` and `drop`
38
+ * with a `DataTransfer` holding the files. The events are synthetic (`isTrusted` is false).
39
+ * Waits for the element to be attached and visible.
40
+ */
41
+ export async function dropFiles(ctx, files, options) {
42
+ const list = Array.isArray(files) ? files : [files];
43
+ const paths = await resolveFileInputs(list, "dropFiles");
44
+ const types = list.map((file) => isGeneratedFile(file) ? (file.type ?? "") : "");
45
+ await ctx.waitForActionable("dropFiles", DROP_CHECKS, { scroll: true, hitTest: false }, options);
46
+ const el = await ctx.requireElement();
47
+ // A file input of our own, made in a sandbox and never attached: the page doesn't see it
48
+ const input = await ctx.call(DROP_INPUT, [], DROP_SANDBOX);
49
+ await ctx.connector.send("input.setFiles", {
50
+ context: ctx.contextId,
51
+ element: { sharedId: input.sharedId },
52
+ files: paths,
53
+ });
54
+ await ctx.call(DROP_EVENTS, [el, types], DROP_SANDBOX);
55
+ }
@@ -1,5 +1,5 @@
1
- import { PROBE_ELEMENT } from "../locator/element-state.js";
2
- import { addPreload, runInLoadedContexts } from "./preload.js";
1
+ import { addPreload, runInLoadedContexts, } from "../script/preload.js";
2
+ import { PROBE_ELEMENT } from "./element-state.js";
3
3
  /** Sandbox realm holding the framework's helpers: same DOM as the page, separate JS globals */
4
4
  export const HELPER_SANDBOX = "samurai";
5
5
  /** What `CALL_PROBE` answers with when the helpers are not installed in the document */
@@ -0,0 +1,18 @@
1
+ import type { BiDiConnector } from "../transport/bidi-connection.js";
2
+ /** How long `wait()` pauses when no duration is given (ms) */
3
+ export declare const DEFAULT_PAUSE_MS = 300;
4
+ /** WebDriver code points of the named keys `press()` accepts; any other single character is typed as is */
5
+ export declare const NAMED_KEYS: Readonly<Record<string, string>>;
6
+ /** Clicks the centre of `rect` (viewport pixels) with real pointer input */
7
+ export declare function clickRect(connector: BiDiConnector, context: string, rect: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ }): Promise<void>;
13
+ /** Pauses the input queue for `duration` ms */
14
+ export declare function pause(connector: BiDiConnector, context: string, duration: number): Promise<void>;
15
+ /** Presses and releases one key, given as its WebDriver code point or a character */
16
+ export declare function pressKey(connector: BiDiConnector, context: string, code: string): Promise<void>;
17
+ /** Types `text` one character at a time (each pressed and released) */
18
+ export declare function typeText(connector: BiDiConnector, context: string, text: string): Promise<void>;
@@ -0,0 +1,80 @@
1
+ /** How long `wait()` pauses when no duration is given (ms) */
2
+ export const DEFAULT_PAUSE_MS = 300;
3
+ // Input source ids: BiDi keeps the pointer position and pressed keys per id, so each kind of input reuses one
4
+ const POINTER_ID = "samurai-pointer";
5
+ const KEYBOARD_ID = "samurai-keyboard";
6
+ const PAUSE_ID = "samurai-sleep";
7
+ /** WebDriver code points of the named keys `press()` accepts; any other single character is typed as is */
8
+ export const NAMED_KEYS = {
9
+ Backspace: "\uE003",
10
+ Tab: "\uE004",
11
+ Enter: "\uE007",
12
+ Escape: "\uE00C",
13
+ Space: " ",
14
+ ArrowLeft: "\uE012",
15
+ ArrowUp: "\uE013",
16
+ ArrowRight: "\uE014",
17
+ ArrowDown: "\uE015",
18
+ Delete: "\uE017",
19
+ Home: "\uE011",
20
+ End: "\uE010",
21
+ PageUp: "\uE00E",
22
+ PageDown: "\uE00F",
23
+ };
24
+ /** Clicks the centre of `rect` (viewport pixels) with real pointer input */
25
+ export async function clickRect(connector, context, rect) {
26
+ await connector.send("input.performActions", {
27
+ context,
28
+ actions: [
29
+ {
30
+ type: "pointer",
31
+ id: POINTER_ID,
32
+ actions: [
33
+ {
34
+ type: "pointerMove",
35
+ x: rect.x + rect.width / 2,
36
+ y: rect.y + rect.height / 2,
37
+ },
38
+ { type: "pointerDown", button: 0 },
39
+ { type: "pointerUp", button: 0 },
40
+ ],
41
+ },
42
+ ],
43
+ });
44
+ }
45
+ /** Pauses the input queue for `duration` ms */
46
+ export async function pause(connector, context, duration) {
47
+ await connector.send("input.performActions", {
48
+ context,
49
+ actions: [
50
+ { type: "none", id: PAUSE_ID, actions: [{ type: "pause", duration }] },
51
+ ],
52
+ });
53
+ }
54
+ /** Presses and releases one key, given as its WebDriver code point or a character */
55
+ export async function pressKey(connector, context, code) {
56
+ await connector.send("input.performActions", {
57
+ context,
58
+ actions: [
59
+ {
60
+ type: "key",
61
+ id: KEYBOARD_ID,
62
+ actions: [
63
+ { type: "keyDown", value: code },
64
+ { type: "keyUp", value: code },
65
+ ],
66
+ },
67
+ ],
68
+ });
69
+ }
70
+ /** Types `text` one character at a time (each pressed and released) */
71
+ export async function typeText(connector, context, text) {
72
+ const actions = Array.from(text).flatMap((char) => [
73
+ { type: "keyDown", value: char },
74
+ { type: "keyUp", value: char },
75
+ ]);
76
+ await connector.send("input.performActions", {
77
+ context,
78
+ actions: [{ type: "key", id: KEYBOARD_ID, actions }],
79
+ });
80
+ }
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * A string, so the transpiler cannot inject helpers that do not exist in the page.
12
12
  */
13
- export declare const LABEL_LOCATE = "(starts, value, match, ignoreCase) => {\n const normalise = (text) => {\n const collapsed = String(text).replace(/\\s+/g, \" \").trim();\n return ignoreCase ? collapsed.toLowerCase() : collapsed;\n };\n const wanted = normalise(value);\n const textOf = (node) => node.innerText ?? node.textContent ?? \"\";\n const labelsOf = (el) => {\n const texts = [];\n if (el.labels) for (const label of el.labels) texts.push(textOf(label));\n const by = el.getAttribute(\"aria-labelledby\");\n if (by) {\n for (const id of by.split(/\\s+/)) {\n const named = id && document.getElementById(id);\n if (named) texts.push(textOf(named));\n }\n }\n const own = el.getAttribute(\"aria-label\");\n if (own) texts.push(own);\n return texts;\n };\n const roots = starts.length > 0 ? starts : [document];\n const seen = new Set();\n const matches = [];\n for (const root of roots) {\n for (const el of root.querySelectorAll(\"*\")) {\n if (seen.has(el)) continue;\n seen.add(el);\n const found = labelsOf(el).some((text) => {\n const normal = normalise(text);\n return match === \"full\" ? normal === wanted : normal.includes(wanted);\n });\n if (found) matches.push(el);\n }\n }\n return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));\n}";
13
+ export declare const LABEL_LOCATE = "(starts, value, match, ignoreCase) => {\n \n const normalise = (text) => {\n const collapsed = String(text).replace(/\\s+/g, \" \").trim();\n return ignoreCase ? collapsed.toLowerCase() : collapsed;\n };\n const wanted = normalise(value);\n const textMatches = (text) => {\n const normal = normalise(text);\n return match === \"full\" ? normal === wanted : normal.includes(wanted);\n };\n const textOf = (node) => node.innerText ?? node.textContent ?? \"\";\n const labelsOf = (el) => {\n const texts = [];\n if (el.labels) for (const label of el.labels) texts.push(textOf(label));\n const by = el.getAttribute(\"aria-labelledby\");\n if (by) {\n for (const id of by.split(/\\s+/)) {\n const named = id && document.getElementById(id);\n if (named) texts.push(textOf(named));\n }\n }\n const own = el.getAttribute(\"aria-label\");\n if (own) texts.push(own);\n return texts;\n };\n const roots = starts.length > 0 ? starts : [document];\n const seen = new Set();\n const matches = [];\n for (const root of roots) {\n for (const el of root.querySelectorAll(\"*\")) {\n if (seen.has(el)) continue;\n seen.add(el);\n if (labelsOf(el).some(textMatches)) matches.push(el);\n }\n }\n return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));\n}";
@@ -1,3 +1,4 @@
1
+ import { DOCUMENT_ORDER_JS, MATCHING_JS, ROOTS_JS } from "./page-search.js";
1
2
  /**
2
3
  * Function declaration run in the page as `(starts, value, match, ignoreCase)`; returns the elements
3
4
  * named by a label whose text matches. BiDi has no label locator, so the framework does it.
@@ -11,11 +12,7 @@
11
12
  * A string, so the transpiler cannot inject helpers that do not exist in the page.
12
13
  */
13
14
  export const LABEL_LOCATE = `(starts, value, match, ignoreCase) => {
14
- const normalise = (text) => {
15
- const collapsed = String(text).replace(/\\s+/g, " ").trim();
16
- return ignoreCase ? collapsed.toLowerCase() : collapsed;
17
- };
18
- const wanted = normalise(value);
15
+ ${MATCHING_JS}
19
16
  const textOf = (node) => node.innerText ?? node.textContent ?? "";
20
17
  const labelsOf = (el) => {
21
18
  const texts = [];
@@ -31,19 +28,15 @@ export const LABEL_LOCATE = `(starts, value, match, ignoreCase) => {
31
28
  if (own) texts.push(own);
32
29
  return texts;
33
30
  };
34
- const roots = starts.length > 0 ? starts : [document];
31
+ ${ROOTS_JS}
35
32
  const seen = new Set();
36
33
  const matches = [];
37
34
  for (const root of roots) {
38
35
  for (const el of root.querySelectorAll("*")) {
39
36
  if (seen.has(el)) continue;
40
37
  seen.add(el);
41
- const found = labelsOf(el).some((text) => {
42
- const normal = normalise(text);
43
- return match === "full" ? normal === wanted : normal.includes(wanted);
44
- });
45
- if (found) matches.push(el);
38
+ if (labelsOf(el).some(textMatches)) matches.push(el);
46
39
  }
47
40
  }
48
- return matches.sort((a, b) => (a === b ? 0 : a.compareDocumentPosition(b) & 4 ? -1 : 1));
41
+ return matches.sort(${DOCUMENT_ORDER_JS});
49
42
  }`;
@@ -1,23 +1,13 @@
1
1
  import type { BiDiConnector } from "../transport/bidi-connection.js";
2
2
  import type { BrowsingContext, ElementRectangle } from "../types/bidi-modules/browsing-context.js";
3
- import { type HelperInstaller } from "../script/helpers.js";
4
3
  import { ElementHandle } from "../script/element-handle.js";
5
4
  import { type ElementScreenshotOptions } from "../browser/screenshot.js";
6
- import { type FileInput } from "../browser/generated-files.js";
5
+ import type { FileInput } from "../browser/generated-files.js";
6
+ import { type ActionOptions } from "./actionability.js";
7
7
  import { type WaitForState } from "./element-state.js";
8
+ import type { HelperInstaller } from "./helper-realm.js";
8
9
  import { type Selector, type TextOptions } from "./selector.js";
9
- export type { WaitForState } from "./element-state.js";
10
- export { InvalidSelectorError, UnsupportedOperationError, } from "./selector-errors.js";
11
- export type { TextOptions } from "./selector.js";
12
- export interface ActionOptions {
13
- /**
14
- * Time (ms) to wait for the element to become actionable. Defaults to config `expect.timeout`, then 5000.
15
- * 0 checks once without waiting; the stability check is skipped because it needs two probes
16
- */
17
- timeout?: number;
18
- /** Skip every actionability check except "attached" */
19
- force?: boolean;
20
- }
10
+ export type { ActionOptions } from "./actionability.js";
21
11
  export default class Locator {
22
12
  private biDiConnector;
23
13
  private contextId;
@@ -25,6 +15,11 @@ export default class Locator {
25
15
  private helpers;
26
16
  private alternatives;
27
17
  private matched;
18
+ private resolver;
19
+ /** What the actionability waits use of this locator */
20
+ private actionability;
21
+ /** What the file actions use of this locator */
22
+ private elementContext;
28
23
  /**
29
24
  * `selector` is an xpath, or a chain of selector steps that each search inside the previous step's matches.
30
25
  * `helpers` is the page's helper realm. With it the actionability probe is installed once per
@@ -81,14 +76,7 @@ export default class Locator {
81
76
  /** The primary or the first fallback that currently matches, without its own fallbacks */
82
77
  private firstMatching;
83
78
  private withoutFallbacks;
84
- private resolveChain;
85
79
  private resolveOne;
86
- private locateStep;
87
- private locateNatively;
88
- /** Text search done in the page, for browsers without BiDi's innerText locator */
89
- private locateByText;
90
- /** Runs a page-side search `(starts, value, match, ignoreCase) => elements` */
91
- private locateInPage;
92
80
  /** Calls `fn` in the page; a throwing function becomes a "Failed to locate element" error */
93
81
  private call;
94
82
  /**
@@ -98,24 +86,14 @@ export default class Locator {
98
86
  private callOnElement;
99
87
  /** Like `callOnElement`, for operations that cannot go on without the element */
100
88
  private callOnExistingElement;
101
- /** Reads the element's actionability state: one search, one probe */
89
+ /** The error for an operation that needs an element when none matches */
90
+ private noElementError;
91
+ /** The element the locator matches now; throws when none does */
92
+ private requireElement;
102
93
  private probeState;
103
- private callProbe;
104
- /**
105
- * Probes until every required check passes and returns that state.
106
- * Throws ActionTimeoutError describing the last probe on timeout.
107
- */
108
94
  private waitForActionable;
109
95
  /** Waits until the element is attached, visible, stable, enabled and not covered, then clicks its centre with real pointer input */
110
96
  click(options?: ActionOptions): Promise<void>;
111
- /** Simulate a click event on the center off a page element.
112
- * This is mostly used internally to focus input elements when using the `fill()` method
113
- * @example
114
- * const container = page.locator("div[@id=['clickable-element']");
115
- * const rect = await container.getBoundingClientRect();
116
- * await container.clickRect(rect);
117
- */
118
- clickRect(rect: Pick<ElementRectangle, "x" | "y" | "width" | "height">): Promise<void>;
119
97
  /**
120
98
  * Sets the files of an `<input type=file>`; `[]` clears it. Paths are relative to the project folder (absolute
121
99
  * ones are used as they are); `generatedFile(…)` writes a file of a given size when the step runs.
@@ -150,13 +128,12 @@ export default class Locator {
150
128
  /** Waits until the element is attached, then focuses it */
151
129
  focus(options?: ActionOptions): Promise<void>;
152
130
  /** Waits until the element reaches `state` (default "visible"); does not scroll */
153
- waitFor({ state, timeout, }?: {
131
+ waitFor(options?: {
154
132
  state?: WaitForState;
155
133
  timeout?: number;
156
134
  }): Promise<void>;
157
135
  /** Returns the browser's getBoundingClientRect result for the given element */
158
136
  getBoundingClientRect(): Promise<ElementRectangle>;
159
- private stringOrNull;
160
137
  /** Whether the element exists, has a non-empty box and is not hidden by CSS */
161
138
  isVisible(): Promise<boolean>;
162
139
  /** Trimmed text content, or null when the element is missing */