@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
@@ -1,91 +1,15 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
  import path from "node:path";
3
- import { Browser } from "../browser/browser.js";
4
- import { ensureWarm } from "../browser/warm-up.js";
5
- import { loadConfig, projectDir, readConfig } from "../config/config.js";
3
+ import { loadConfig, projectDir } from "../config/config.js";
6
4
  import { loadSecrets } from "../config/secrets.js";
7
- import { filesFolder, isFolderIgnored, isGitIgnored, } from "../project/index.js";
5
+ import { filesFolder, isFolderIgnored } from "../project/index.js";
8
6
  import { withProject } from "../runner/project.js";
9
- import { parseSpec } from "../steps/parse.js";
10
- import { applyRecorderEvent } from "./apply.js";
11
7
  import { missingFileText, } from "./recorder.js";
12
- const NEW_SPEC_HEADER = `import { expect, test } from "@itmetsam/samurai-framework";\n\n`;
13
- function addTest(source, title) {
14
- const header = source.trim() === ""
15
- ? NEW_SPEC_HEADER
16
- : `${source.replace(/\s*$/, "")}\n\n`;
17
- return `${header}test(${JSON.stringify(title)}, async ({ page }) => {\n});\n`;
18
- }
19
- /** Every `secrets.<NAME>` a parsed value contains, at any depth */
20
- function secretNames(node, found) {
21
- if (Array.isArray(node))
22
- node.forEach((item) => secretNames(item, found));
23
- else if (node !== null && typeof node === "object") {
24
- const record = node;
25
- if (record.kind === "secret" && typeof record.name === "string")
26
- found.add(record.name);
27
- Object.values(record).forEach((item) => secretNames(item, found));
28
- }
29
- }
30
- /** What the toolbar shows of the project: its environment, variables, secrets and the test's screenshots */
31
- export async function toolbarOptions(run, source, dir, config, testIndex) {
32
- const tests = parseSpec(source);
33
- const names = new Set(run.secrets.keys());
34
- secretNames(tests, names);
35
- const secrets = {};
36
- for (const name of names)
37
- secrets[name] = run.secrets.get(name);
38
- const { environment } = run.settings;
39
- return {
40
- environment,
41
- canAddVariables: typeof config.environments === "object",
42
- variables: run.settings.variables,
43
- secrets,
44
- secretsIgnored: await isGitIgnored(dir, `.env.${environment}`),
45
- screenshots: tests[testIndex]?.steps.filter(({ step }) => step.kind === "screenshot")
46
- .length ?? 0,
47
- };
48
- }
49
- /** The secrets a recording needs set: those its steps read, then those added from the panel, once each */
50
- export function resultSecrets(events, added) {
51
- return [
52
- ...new Set([
53
- ...events.flatMap((event) => event.op !== "remove" &&
54
- event.step.kind === "fill" &&
55
- event.step.value.kind === "secret"
56
- ? [event.step.value.name]
57
- : []),
58
- ...added,
59
- ]),
60
- ];
61
- }
62
- /** The index of the test `selector` names (see `RecordSpecOptions.test`) */
63
- export function pickTest(tests, selector) {
64
- if (tests.length === 0)
65
- throw new Error("The file has no tests; give a title with --new to add one");
66
- if (selector === undefined) {
67
- if (tests.length === 1)
68
- return 0;
69
- throw new Error(`The file has ${tests.length} tests; choose one with --test:\n${tests.map((t) => ` ${t.name}`).join("\n")}`);
70
- }
71
- if (typeof selector === "number") {
72
- if (!Number.isInteger(selector) ||
73
- selector < 0 ||
74
- selector >= tests.length) {
75
- throw new Error(`There is no test ${selector}; the file has ${tests.length}`);
76
- }
77
- return selector;
78
- }
79
- const exact = tests.findIndex((t) => t.name === selector);
80
- if (exact !== -1)
81
- return exact;
82
- const partial = tests.flatMap((t, index) => t.name.includes(selector) ? [index] : []);
83
- if (partial.length === 1)
84
- return partial[0];
85
- throw new Error(partial.length === 0
86
- ? `No test matches "${selector}". Tests:\n${tests.map((t) => ` ${t.name}`).join("\n")}`
87
- : `"${selector}" matches ${partial.length} tests; use the full name`);
88
- }
8
+ import { launchRecordingBrowser, prepareRecordingBrowser, } from "./session-browser.js";
9
+ import { resultSecrets, toolbarOptions } from "./session-secrets.js";
10
+ import { prepareSpec, readSpec, SpecWriter } from "./spec-source.js";
11
+ export { pickTest } from "./spec-source.js";
12
+ export { resultSecrets, toolbarOptions } from "./session-secrets.js";
89
13
  /**
90
14
  * Opens a browser window and records what the person does into a spec file, step by step, until `signal`
91
15
  * aborts or the window is closed. Every step is written to the file as it happens, as a minimal edit.
@@ -96,37 +20,20 @@ export async function recordSpec(options) {
96
20
  return withProject(options, async (run) => {
97
21
  const { settings } = run;
98
22
  const file = path.resolve(projectDir(), options.file);
99
- let source = existsSync(file) ? readFileSync(file, "utf8") : undefined;
100
- if (source === undefined && options.create === undefined) {
101
- throw new Error(`${options.file} does not exist; give a title with --new to create it`);
102
- }
23
+ const existing = readSpec(file, options);
103
24
  // Before the spec is touched: a recording without its browser changes nothing
104
- const browserName = (await readConfig("browser")) ?? "firefox";
105
- await ensureWarm(browserName, { port: options.port ?? 9223 }, options.signal);
106
- options.signal?.throwIfAborted();
107
- source ??= "";
108
- if (options.create !== undefined &&
109
- !parseSpec(source).some((t) => t.name === options.create)) {
110
- source = addTest(source, options.create);
111
- mkdirSync(path.dirname(file), { recursive: true });
112
- writeFileSync(file, source);
113
- }
114
- const tests = parseSpec(source);
115
- const testIndex = pickTest(tests, options.create ?? options.test);
116
- const test = tests[testIndex];
117
- const at = options.at ?? test.steps.length;
118
- if (!Number.isInteger(at) || at < 0 || at > test.steps.length) {
119
- throw new RangeError(`--at must be between 0 and ${test.steps.length} (the test has ${test.steps.length} steps)`);
120
- }
121
- const { browser, page } = await Browser.launch(browserName, {
122
- port: options.port ?? 9223,
25
+ const browserName = await prepareRecordingBrowser(options);
26
+ const spec = prepareSpec(file, existing, options);
27
+ const { testIndex, at } = spec;
28
+ const writer = new SpecWriter(spec.source, file, testIndex);
29
+ const { browser, page } = await launchRecordingBrowser(browserName, {
30
+ ...options,
123
31
  headless: options.headless ?? settings.headless,
124
- }, options.signal);
32
+ });
125
33
  const loaded = await loadConfig();
126
34
  const folder = filesFolder(projectDir(), loaded.filesDir);
127
35
  const hadFolder = existsSync(folder.absolute);
128
36
  let recorder;
129
- const events = [];
130
37
  const addedSecrets = new Set();
131
38
  let stopRequested;
132
39
  const stopped = new Promise((resolve) => (stopRequested = resolve));
@@ -141,7 +48,7 @@ export async function recordSpec(options) {
141
48
  initialGoto: at === 0 && start !== undefined,
142
49
  ...(settings.baseURL !== undefined && { baseURL: settings.baseURL }),
143
50
  toolbar: {
144
- ...(await toolbarOptions(run, source, projectDir(), loaded, testIndex)),
51
+ ...(await toolbarOptions(run, spec.source, projectDir(), loaded, testIndex)),
145
52
  projectDir: projectDir(),
146
53
  ...(options.onStatus && { onStatus: options.onStatus }),
147
54
  onSecretAdded: (name) => addedSecrets.add(name),
@@ -153,10 +60,8 @@ export async function recordSpec(options) {
153
60
  onMissing: (file) => options.onMissingFile?.(file, missingFileText(file, folder.relative)),
154
61
  },
155
62
  onEvent: (event) => {
156
- source = applyRecorderEvent(source, testIndex, event);
157
- writeFileSync(file, source);
158
- events.push(event);
159
- options.onEvent?.(event, source);
63
+ const text = writer.write(event);
64
+ options.onEvent?.(event, text);
160
65
  },
161
66
  });
162
67
  await options.onStarted?.({ page, browser });
@@ -178,7 +83,7 @@ export async function recordSpec(options) {
178
83
  finally {
179
84
  await browser.close().catch(() => undefined);
180
85
  }
181
- const secrets = resultSecrets(events, addedSecrets);
86
+ const secrets = resultSecrets(writer.events, addedSecrets);
182
87
  // Read again: the toolbar may have written new ones to the .env file
183
88
  const set = loadSecrets(settings.environment);
184
89
  const unsetSecrets = secrets.filter((name) => !set.has(name));
@@ -186,8 +91,8 @@ export async function recordSpec(options) {
186
91
  const missingFiles = recorder?.missingFiles ?? [];
187
92
  return {
188
93
  file,
189
- test: test.name,
190
- events,
94
+ test: spec.testName,
95
+ events: writer.events,
191
96
  secrets,
192
97
  unsetSecrets,
193
98
  files,
@@ -0,0 +1,39 @@
1
+ import type { RecorderEvent } from "./normalise.js";
2
+ /** `source` with an empty test called `title` added (a new file gets the import line first) */
3
+ export declare function addTest(source: string, title: string): string;
4
+ /** The index of the test `selector` names (see `RecordSpecOptions.test`) */
5
+ export declare function pickTest(tests: readonly {
6
+ name: string;
7
+ }[], selector: string | number | undefined): number;
8
+ /** The spec a recording writes into: its text, the test chosen, and where the first new step goes */
9
+ export interface RecordingSpec {
10
+ source: string;
11
+ testIndex: number;
12
+ /** Full name of the test the steps go into */
13
+ testName: string;
14
+ at: number;
15
+ }
16
+ /** The spec's text, `undefined` when the file is not there yet; throws when it must exist (no `create`) */
17
+ export declare function readSpec(file: string, options: {
18
+ file: string;
19
+ create?: string | undefined;
20
+ }): string | undefined;
21
+ /**
22
+ * Adds the test called `create` to the spec when it is not there yet (writing the file), and picks the test and the
23
+ * step index to record from. Throws before writing anything when the test or `at` is wrong.
24
+ */
25
+ export declare function prepareSpec(file: string, existing: string | undefined, options: {
26
+ create?: string | undefined;
27
+ test?: string | number | undefined;
28
+ at?: number | undefined;
29
+ }): RecordingSpec;
30
+ /** Writes each recorded step into the spec file as a minimal edit and keeps the events */
31
+ export declare class SpecWriter {
32
+ private source;
33
+ private readonly file;
34
+ private readonly testIndex;
35
+ readonly events: RecorderEvent[];
36
+ constructor(source: string, file: string, testIndex: number);
37
+ /** Applies the event, writes the file and returns the file's new content */
38
+ write(event: RecorderEvent): string;
39
+ }
@@ -0,0 +1,84 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { parseSpec } from "../steps/parse.js";
4
+ import { applyRecorderEvent } from "./apply.js";
5
+ const NEW_SPEC_HEADER = `import { expect, test } from "@itmetsam/samurai-framework";\n\n`;
6
+ /** `source` with an empty test called `title` added (a new file gets the import line first) */
7
+ export function addTest(source, title) {
8
+ const header = source.trim() === ""
9
+ ? NEW_SPEC_HEADER
10
+ : `${source.replace(/\s*$/, "")}\n\n`;
11
+ return `${header}test(${JSON.stringify(title)}, async ({ page }) => {\n});\n`;
12
+ }
13
+ /** The index of the test `selector` names (see `RecordSpecOptions.test`) */
14
+ export function pickTest(tests, selector) {
15
+ if (tests.length === 0)
16
+ throw new Error("The file has no tests; give a title with --new to add one");
17
+ if (selector === undefined) {
18
+ if (tests.length === 1)
19
+ return 0;
20
+ throw new Error(`The file has ${tests.length} tests; choose one with --test:\n${tests.map((t) => ` ${t.name}`).join("\n")}`);
21
+ }
22
+ if (typeof selector === "number") {
23
+ if (!Number.isInteger(selector) ||
24
+ selector < 0 ||
25
+ selector >= tests.length) {
26
+ throw new Error(`There is no test ${selector}; the file has ${tests.length}`);
27
+ }
28
+ return selector;
29
+ }
30
+ const exact = tests.findIndex((t) => t.name === selector);
31
+ if (exact !== -1)
32
+ return exact;
33
+ const partial = tests.flatMap((t, index) => t.name.includes(selector) ? [index] : []);
34
+ if (partial.length === 1)
35
+ return partial[0];
36
+ throw new Error(partial.length === 0
37
+ ? `No test matches "${selector}". Tests:\n${tests.map((t) => ` ${t.name}`).join("\n")}`
38
+ : `"${selector}" matches ${partial.length} tests; use the full name`);
39
+ }
40
+ /** The spec's text, `undefined` when the file is not there yet; throws when it must exist (no `create`) */
41
+ export function readSpec(file, options) {
42
+ const source = existsSync(file) ? readFileSync(file, "utf8") : undefined;
43
+ if (source === undefined && options.create === undefined) {
44
+ throw new Error(`${options.file} does not exist; give a title with --new to create it`);
45
+ }
46
+ return source;
47
+ }
48
+ /**
49
+ * Adds the test called `create` to the spec when it is not there yet (writing the file), and picks the test and the
50
+ * step index to record from. Throws before writing anything when the test or `at` is wrong.
51
+ */
52
+ export function prepareSpec(file, existing, options) {
53
+ let source = existing ?? "";
54
+ if (options.create !== undefined &&
55
+ !parseSpec(source).some((t) => t.name === options.create)) {
56
+ source = addTest(source, options.create);
57
+ mkdirSync(path.dirname(file), { recursive: true });
58
+ writeFileSync(file, source);
59
+ }
60
+ const tests = parseSpec(source);
61
+ const testIndex = pickTest(tests, options.create ?? options.test);
62
+ const test = tests[testIndex];
63
+ const at = options.at ?? test.steps.length;
64
+ if (!Number.isInteger(at) || at < 0 || at > test.steps.length) {
65
+ throw new RangeError(`--at must be between 0 and ${test.steps.length} (the test has ${test.steps.length} steps)`);
66
+ }
67
+ return { source, testIndex, testName: test.name, at };
68
+ }
69
+ /** Writes each recorded step into the spec file as a minimal edit and keeps the events */
70
+ export class SpecWriter {
71
+ constructor(source, file, testIndex) {
72
+ this.source = source;
73
+ this.file = file;
74
+ this.testIndex = testIndex;
75
+ this.events = [];
76
+ }
77
+ /** Applies the event, writes the file and returns the file's new content */
78
+ write(event) {
79
+ this.source = applyRecorderEvent(this.source, this.testIndex, event);
80
+ writeFileSync(this.file, this.source);
81
+ this.events.push(event);
82
+ return this.source;
83
+ }
84
+ }
@@ -0,0 +1,21 @@
1
+ import type { ActionReply } from "./protocol.js";
2
+ import type { Message, Of } from "./types.js";
3
+ /** The messages that expect a reply, and that a double click sends twice */
4
+ type Replying = Of<"assert" | "insert" | "add-insertable">;
5
+ /**
6
+ * Drops the repeat of an action that is running or was answered `{ ok: true }` (a double click: its first copy
7
+ * answers), and lets a retry after `{ ok: false }` through.
8
+ */
9
+ export declare class ActionGuard {
10
+ /** Ids of `assert`, `insert` and `add-insertable` being processed */
11
+ private readonly inFlight;
12
+ /** Ids `accept` let through whose `run` hasn't started yet */
13
+ private readonly claimed;
14
+ /** Ids answered `{ ok: true }`: their step exists, a repeat is a double click */
15
+ private readonly done;
16
+ /** Whether to process `message`; call it as the message arrives, before queueing it */
17
+ accept(message: Message): boolean;
18
+ /** Runs an action that expects a reply, unless it is a repeat (see `accept`); `reply` gets the answer */
19
+ run(message: Replying, work: () => Promise<ActionReply>, reply: (id: number, result: ActionReply) => void): Promise<void>;
20
+ }
21
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Drops the repeat of an action that is running or was answered `{ ok: true }` (a double click: its first copy
3
+ * answers), and lets a retry after `{ ok: false }` through.
4
+ */
5
+ export class ActionGuard {
6
+ constructor() {
7
+ /** Ids of `assert`, `insert` and `add-insertable` being processed */
8
+ this.inFlight = new Set();
9
+ /** Ids `accept` let through whose `run` hasn't started yet */
10
+ this.claimed = new Set();
11
+ /** Ids answered `{ ok: true }`: their step exists, a repeat is a double click */
12
+ this.done = new Set();
13
+ }
14
+ /** Whether to process `message`; call it as the message arrives, before queueing it */
15
+ accept(message) {
16
+ if (message.action !== "assert" &&
17
+ message.action !== "insert" &&
18
+ message.action !== "add-insertable")
19
+ return true;
20
+ const { id } = message;
21
+ if (this.inFlight.has(id) || this.done.has(id))
22
+ return false;
23
+ this.inFlight.add(id);
24
+ this.claimed.add(id);
25
+ return true;
26
+ }
27
+ /** Runs an action that expects a reply, unless it is a repeat (see `accept`); `reply` gets the answer */
28
+ async run(message, work, reply) {
29
+ const { id } = message;
30
+ if (!this.claimed.has(id) && !this.accept(message))
31
+ return;
32
+ this.claimed.delete(id);
33
+ try {
34
+ const result = await work();
35
+ if (result.ok)
36
+ this.done.add(id);
37
+ reply(id, result);
38
+ }
39
+ finally {
40
+ this.inFlight.delete(id);
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,4 @@
1
+ import type { ActionReply } from "../protocol.js";
2
+ import type { ActionContext, Of } from "../types.js";
3
+ /** Turns the Assert panel's choice into an `expect` step for the element that was picked */
4
+ export declare function assertAction(ctx: ActionContext, message: Of<"assert">): Promise<ActionReply>;
@@ -0,0 +1,52 @@
1
+ import { fail, PAUSED } from "./reply.js";
2
+ /** Turns the Assert panel's choice into an `expect` step for the element that was picked */
3
+ export async function assertAction(ctx, message) {
4
+ if (ctx.isPaused())
5
+ return fail(PAUSED);
6
+ const pick = ctx.pick();
7
+ if (!pick || pick.id !== message.id)
8
+ return fail("Pick the element again");
9
+ let locator = pick.locator;
10
+ let expectation;
11
+ const expected = String(message.expected ?? "");
12
+ switch (message.matcher) {
13
+ case "toBeVisible":
14
+ expectation = { matcher: "toBeVisible" };
15
+ break;
16
+ case "toHaveText":
17
+ case "toContainText":
18
+ case "toHaveValue":
19
+ expectation = { matcher: message.matcher, expected };
20
+ break;
21
+ case "toHaveAttribute":
22
+ if (!message.attribute)
23
+ return fail("Choose an attribute");
24
+ expectation = {
25
+ matcher: "toHaveAttribute",
26
+ name: message.attribute,
27
+ expected,
28
+ };
29
+ break;
30
+ case "toHaveCount": {
31
+ const call = pick.counts[message.countIndex ?? 0];
32
+ const count = Number(expected);
33
+ if (!call)
34
+ return fail("Choose what to count");
35
+ if (expected.trim() === "" || !Number.isInteger(count) || count < 0)
36
+ return fail("A count is a whole number");
37
+ locator = { chain: [call], fallbacks: [] };
38
+ expectation = { matcher: "toHaveCount", expected: count };
39
+ break;
40
+ }
41
+ default:
42
+ return fail("Choose a matcher");
43
+ }
44
+ ctx.setPick(undefined);
45
+ ctx.deps.observe({
46
+ type: "expect",
47
+ locator,
48
+ not: message.not === true,
49
+ expectation,
50
+ });
51
+ return { ok: true };
52
+ }
@@ -0,0 +1,6 @@
1
+ import type { ActionReply } from "../protocol.js";
2
+ import type { ActionContext, Of } from "../types.js";
3
+ /** Types a variable or secret the tester already has into the field they picked, and records one `insert` step */
4
+ export declare function insertAction(ctx: ActionContext, message: Of<"insert">): Promise<ActionReply>;
5
+ /** Saves a new variable or secret to the project, then types it into the field and records the `insert` step */
6
+ export declare function addInsertableAction(ctx: ActionContext, message: Of<"add-insertable">): Promise<ActionReply>;
@@ -0,0 +1,93 @@
1
+ import { ConfigEditError } from "../../../project/config-edit.js";
2
+ import { SECRET_NAME } from "../../../project/secrets-file.js";
3
+ import { ExistsError, VARIABLE_NAME } from "../../../project/variables.js";
4
+ import { fail, NO_ENVIRONMENT, PAUSED, TYPE_FAILED, UNIDENTIFIABLE, } from "./reply.js";
5
+ /** Types a variable or secret the tester already has into the field they picked, and records one `insert` step */
6
+ export async function insertAction(ctx, message) {
7
+ if (ctx.isPaused())
8
+ return fail(PAUSED);
9
+ const { kind, name } = message;
10
+ const value = kind === "secret" ? ctx.secrets.get(name) : ctx.variables.get(name);
11
+ if (value === undefined)
12
+ return fail(kind === "secret"
13
+ ? `Set a value for ${name} first`
14
+ : `There is no variable "${name}"`);
15
+ const locator = await locate(ctx, message);
16
+ if (!locator)
17
+ return fail(UNIDENTIFIABLE);
18
+ return typeIn(ctx, locator, kind, name, value);
19
+ }
20
+ /** Saves a new variable or secret to the project, then types it into the field and records the `insert` step */
21
+ export async function addInsertableAction(ctx, message) {
22
+ if (ctx.isPaused())
23
+ return fail(PAUSED);
24
+ const { kind, name } = message;
25
+ const value = String(message.value ?? "");
26
+ if (kind === "env" && !ctx.options.canAddVariables)
27
+ return fail(NO_ENVIRONMENT);
28
+ const known = kind === "env"
29
+ ? ctx.variables.has(name)
30
+ : ctx.secrets.get(name) !== undefined;
31
+ if (known)
32
+ return fail(`"${name}" already exists`, "name");
33
+ // Found before anything is written: an element that can't be filled leaves the project as it was
34
+ const locator = await locate(ctx, message);
35
+ if (!locator)
36
+ return fail(UNIDENTIFIABLE);
37
+ const written = kind === "env"
38
+ ? await write(() => ctx.deps.addVariable(name, value), (err) => err instanceof ConfigEditError
39
+ ? fail(`samurai.config.ts can't be changed automatically. Add ${name} to environments.${ctx.options.environment}.variables yourself.`)
40
+ : err instanceof ExistsError || !VARIABLE_NAME.test(name)
41
+ ? fail(errorText(err), "name")
42
+ : fail(errorText(err)))
43
+ : await write(() => ctx.deps.setSecret(name, value), (err) =>
44
+ // An error never echoes the value
45
+ SECRET_NAME.test(name)
46
+ ? fail(scrub(errorText(err), value))
47
+ : fail(scrub(errorText(err), value), "name"));
48
+ if (written)
49
+ return written;
50
+ if (kind === "env")
51
+ ctx.variables.set(name, value);
52
+ else {
53
+ ctx.secrets.set(name, value);
54
+ ctx.deps.secretAdded(name);
55
+ }
56
+ ctx.push();
57
+ return typeIn(ctx, locator, kind, name, value, true);
58
+ }
59
+ /** Undefined when `work` succeeded, else the reply for its error */
60
+ async function write(work, replyFor) {
61
+ try {
62
+ await work();
63
+ return undefined;
64
+ }
65
+ catch (err) {
66
+ return replyFor(err);
67
+ }
68
+ }
69
+ async function locate(ctx, message) {
70
+ if (!message.target)
71
+ return undefined;
72
+ try {
73
+ return await ctx.deps.rank(message.target, message.candidates);
74
+ }
75
+ catch {
76
+ return undefined; // the element went away
77
+ }
78
+ }
79
+ async function typeIn(ctx, locator, kind, name, value, saved = false) {
80
+ try {
81
+ await ctx.deps.type(locator, value);
82
+ }
83
+ catch {
84
+ // Typing clicks the field, which closes the panel the reply is for: the toast is what the tester sees
85
+ ctx.showToast(saved ? `Saved ${name}, but couldn't type into that field` : TYPE_FAILED);
86
+ ctx.push();
87
+ return fail(TYPE_FAILED);
88
+ }
89
+ ctx.deps.observe({ type: "insert", locator, value: { kind, name } });
90
+ return { ok: true };
91
+ }
92
+ const errorText = (err) => err instanceof Error ? err.message : String(err);
93
+ const scrub = (text, secret) => secret === "" ? text : text.split(secret).join("••••••");
@@ -0,0 +1,8 @@
1
+ import type { PickInfo } from "../protocol.js";
2
+ /**
3
+ * A field may hold an inserted secret: its value (and any text or attribute equal to a secret) is left out,
4
+ * and the first matcher follows the Alt+click rule without a value, so neither the page nor the spec gets it.
5
+ */
6
+ export declare function withoutSecrets(info: PickInfo & {
7
+ ok: true;
8
+ }, secrets: Map<string, string | undefined>): PickInfo;
@@ -0,0 +1,26 @@
1
+ import { expectationFor } from "../../expectation.js";
2
+ /**
3
+ * A field may hold an inserted secret: its value (and any text or attribute equal to a secret) is left out,
4
+ * and the first matcher follows the Alt+click rule without a value, so neither the page nor the spec gets it.
5
+ */
6
+ export function withoutSecrets(info, secrets) {
7
+ const values = [...secrets.values()].filter((value) => value !== undefined && value !== "");
8
+ const secret = (text) => text !== undefined && values.some((value) => text.includes(value));
9
+ if (!secret(info.value) &&
10
+ !secret(info.text) &&
11
+ !secret(info.first.expected) &&
12
+ !info.attributes.some((a) => secret(a.value)))
13
+ return info;
14
+ const text = secret(info.text) ? "" : info.text;
15
+ const first = expectationFor(text, undefined);
16
+ const { value: _value, ...rest } = info;
17
+ return {
18
+ ...rest,
19
+ text,
20
+ attributes: info.attributes.filter((a) => !secret(a.value)),
21
+ first: {
22
+ matcher: first.matcher,
23
+ ...("expected" in first && { expected: String(first.expected) }),
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,7 @@
1
+ import type { ActionReply } from "../protocol.js";
2
+ export declare const UNIDENTIFIABLE = "This element can't be identified uniquely";
3
+ export declare const TYPE_FAILED = "Couldn't type into that field";
4
+ export declare const NO_ENVIRONMENT = "This recording has no environment";
5
+ export declare const PAUSED = "Nothing you do now is recorded.";
6
+ /** A refusal the toolbar shows; `field` names the form field to mark */
7
+ export declare const fail: (error: string, field?: "name") => ActionReply;
@@ -0,0 +1,10 @@
1
+ export const UNIDENTIFIABLE = "This element can't be identified uniquely";
2
+ export const TYPE_FAILED = "Couldn't type into that field";
3
+ export const NO_ENVIRONMENT = "This recording has no environment";
4
+ export const PAUSED = "Nothing you do now is recorded.";
5
+ /** A refusal the toolbar shows; `field` names the form field to mark */
6
+ export const fail = (error, field) => ({
7
+ ok: false,
8
+ error,
9
+ ...(field && { field }),
10
+ });
@@ -0,0 +1,47 @@
1
+ import { ElementHandle } from "../../script/element-handle.js";
2
+ import type { LocatorCall, LocatorSpec } from "../../steps/model.js";
3
+ import type { Observation } from "../normalise.js";
4
+ import { ToolbarController } from "./controller.js";
5
+ import type { ActionReply, PickInfo, ToolbarMessage, ToolbarState } from "./protocol.js";
6
+ import type { ControllerDeps, ControllerOptions } from "./types.js";
7
+ export declare const SECRET = "hunter2-very-secret";
8
+ export declare const NEW_SECRET = "n3w-s3cr3t-value";
9
+ export declare const TARGET: ElementHandle;
10
+ export declare const CANDIDATES: LocatorCall[];
11
+ export declare const LOCATOR: LocatorSpec;
12
+ export declare const COUNT_CALL: LocatorCall;
13
+ export declare const INFO: PickInfo & {
14
+ ok: true;
15
+ };
16
+ export declare const lastOf: <T>(items: T[]) => T | undefined;
17
+ export type Message = ToolbarMessage & {
18
+ target?: ElementHandle;
19
+ };
20
+ export declare const msg: (message: Record<string, unknown>) => Message;
21
+ export declare const insert: (id: number, kind: "env" | "secret", name: string) => Message;
22
+ export declare const add: (id: number, kind: "env" | "secret", name: string, value: string) => Message;
23
+ export declare const pick: (id: number) => Message;
24
+ /** A controller over fakes that record everything */
25
+ export declare function setup(options?: Partial<ControllerOptions>, overrides?: Partial<ControllerDeps>): {
26
+ controller: ToolbarController;
27
+ pushes: ToolbarState[];
28
+ replies: {
29
+ id: number;
30
+ result: PickInfo | ActionReply;
31
+ }[];
32
+ observed: Observation[];
33
+ statuses: {
34
+ state: string;
35
+ toolbar: boolean;
36
+ }[];
37
+ calls: string[];
38
+ page: {
39
+ url: string;
40
+ };
41
+ last: () => ToolbarState;
42
+ noSecretLeaks: () => void;
43
+ };
44
+ /** Deps whose `type` fails, as a field that can't be typed into does */
45
+ export declare const failingType: {
46
+ type: () => Promise<never>;
47
+ };