qa360 2.3.0 → 2.3.1

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 (507) hide show
  1. package/README.md +155 -262
  2. package/{cli/dist → dist}/commands/ai.js +1 -1
  3. package/{cli/dist → dist}/commands/coverage.js +1 -1
  4. package/{cli/dist → dist}/commands/crawl.js +2 -2
  5. package/{cli/dist → dist}/commands/doctor.js +2 -2
  6. package/{cli/dist → dist}/commands/explain.js +2 -2
  7. package/{cli/dist → dist}/commands/flakiness.js +1 -1
  8. package/{cli/dist → dist}/commands/generate.js +1 -1
  9. package/{cli/dist → dist}/commands/history.js +1 -1
  10. package/{cli/dist → dist}/commands/monitor.js +3 -3
  11. package/{cli/dist → dist}/commands/ollama.js +1 -1
  12. package/{cli/dist → dist}/commands/pack.js +2 -2
  13. package/{cli/dist → dist}/commands/regression.js +1 -1
  14. package/{cli/dist → dist}/commands/repair.js +1 -1
  15. package/{cli/dist → dist}/commands/retry.js +1 -1
  16. package/{cli/dist → dist}/commands/run.d.ts +1 -1
  17. package/{cli/dist → dist}/commands/run.js +1 -1
  18. package/{cli/dist → dist}/commands/secrets.js +1 -1
  19. package/{cli/dist → dist}/commands/serve.js +1 -1
  20. package/{cli/dist → dist}/commands/slo.js +1 -1
  21. package/{cli/dist → dist}/commands/verify.js +1 -1
  22. package/{cli/dist → dist}/core/adapters/playwright-native-api.d.ts +2 -0
  23. package/{cli/dist → dist}/core/adapters/playwright-native-api.js +20 -1
  24. package/{cli/dist → dist}/core/adapters/playwright-ui.d.ts +21 -0
  25. package/dist/core/adapters/playwright-ui.js +2050 -0
  26. package/{cli/dist → dist}/core/ai/ollama-provider.js +15 -3
  27. package/{cli/dist → dist}/core/artifacts/ui-artifacts.js +24 -4
  28. package/dist/core/auth/backup-codes-provider.d.ts +91 -0
  29. package/dist/core/auth/backup-codes-provider.js +215 -0
  30. package/{cli/dist → dist}/core/auth/basic-auth-provider.d.ts +6 -0
  31. package/{cli/dist → dist}/core/auth/basic-auth-provider.js +24 -6
  32. package/dist/core/auth/digest-auth-provider.d.ts +116 -0
  33. package/dist/core/auth/digest-auth-provider.js +244 -0
  34. package/dist/core/auth/hcaptcha-handler.d.ts +103 -0
  35. package/dist/core/auth/hcaptcha-handler.js +288 -0
  36. package/{cli/dist → dist}/core/auth/index.d.ts +81 -4
  37. package/{cli/dist → dist}/core/auth/index.js +15 -1
  38. package/dist/core/auth/oauth-handler.d.ts +408 -0
  39. package/dist/core/auth/oauth-handler.js +636 -0
  40. package/{cli/dist → dist}/core/auth/oauth2-provider.d.ts +9 -0
  41. package/dist/core/auth/oauth2-provider.js +227 -0
  42. package/dist/core/auth/otp-provider.d.ts +93 -0
  43. package/dist/core/auth/otp-provider.js +288 -0
  44. package/dist/core/auth/recaptcha-handler.d.ts +119 -0
  45. package/dist/core/auth/recaptcha-handler.js +301 -0
  46. package/dist/core/auth/remember-me-handler.d.ts +142 -0
  47. package/dist/core/auth/remember-me-handler.js +255 -0
  48. package/dist/core/auth/saml-handler.d.ts +173 -0
  49. package/dist/core/auth/saml-handler.js +364 -0
  50. package/dist/core/auth/webauthn-handler.d.ts +182 -0
  51. package/dist/core/auth/webauthn-handler.js +310 -0
  52. package/dist/core/crawler/advanced-interactions.d.ts +342 -0
  53. package/dist/core/crawler/advanced-interactions.js +1069 -0
  54. package/dist/core/crawler/blob-url-download-handler.d.ts +145 -0
  55. package/dist/core/crawler/blob-url-download-handler.js +392 -0
  56. package/dist/core/crawler/consent-handler.d.ts +49 -0
  57. package/dist/core/crawler/consent-handler.js +258 -0
  58. package/dist/core/crawler/cookie-manager.d.ts +166 -0
  59. package/dist/core/crawler/cookie-manager.js +353 -0
  60. package/dist/core/crawler/coop-coep-handler.d.ts +136 -0
  61. package/dist/core/crawler/coop-coep-handler.js +338 -0
  62. package/dist/core/crawler/csp-handler.d.ts +151 -0
  63. package/dist/core/crawler/csp-handler.js +415 -0
  64. package/dist/core/crawler/download-handler.d.ts +155 -0
  65. package/dist/core/crawler/download-handler.js +370 -0
  66. package/dist/core/crawler/email-testing-handler.d.ts +214 -0
  67. package/dist/core/crawler/email-testing-handler.js +398 -0
  68. package/dist/core/crawler/error-tracking-handler.d.ts +177 -0
  69. package/dist/core/crawler/error-tracking-handler.js +378 -0
  70. package/dist/core/crawler/form-handler.d.ts +100 -0
  71. package/dist/core/crawler/form-handler.js +465 -0
  72. package/dist/core/crawler/framework-wait-handler.d.ts +96 -0
  73. package/dist/core/crawler/framework-wait-handler.js +464 -0
  74. package/dist/core/crawler/geolocation-handler.d.ts +112 -0
  75. package/dist/core/crawler/geolocation-handler.js +276 -0
  76. package/dist/core/crawler/index.d.ts +78 -0
  77. package/{cli/dist → dist}/core/crawler/index.js +74 -1
  78. package/dist/core/crawler/intelligent-selector-generator.d.ts +164 -0
  79. package/dist/core/crawler/intelligent-selector-generator.js +612 -0
  80. package/{cli/dist → dist}/core/crawler/journey-generator.js +44 -1
  81. package/{cli/dist → dist}/core/crawler/page-analyzer.d.ts +16 -1
  82. package/{cli/dist → dist}/core/crawler/page-analyzer.js +469 -17
  83. package/dist/core/crawler/permissions-handler.d.ts +112 -0
  84. package/dist/core/crawler/permissions-handler.js +236 -0
  85. package/dist/core/crawler/permissions-policy-handler.d.ts +113 -0
  86. package/dist/core/crawler/permissions-policy-handler.js +402 -0
  87. package/dist/core/crawler/presets.d.ts +100 -0
  88. package/dist/core/crawler/presets.js +887 -0
  89. package/dist/core/crawler/repl-debug-handler.d.ts +105 -0
  90. package/dist/core/crawler/repl-debug-handler.js +552 -0
  91. package/dist/core/crawler/reporting-api-handler.d.ts +212 -0
  92. package/dist/core/crawler/reporting-api-handler.js +344 -0
  93. package/{cli/dist → dist}/core/crawler/selector-generator.d.ts +9 -0
  94. package/{cli/dist → dist}/core/crawler/selector-generator.js +99 -23
  95. package/dist/core/crawler/site-profiler.d.ts +89 -0
  96. package/dist/core/crawler/site-profiler.js +290 -0
  97. package/dist/core/crawler/sourcemaps-handler.d.ts +144 -0
  98. package/dist/core/crawler/sourcemaps-handler.js +420 -0
  99. package/dist/core/crawler/stacked-modals-handler.d.ts +118 -0
  100. package/dist/core/crawler/stacked-modals-handler.js +429 -0
  101. package/dist/core/crawler/trusted-types-handler.d.ts +149 -0
  102. package/dist/core/crawler/trusted-types-handler.js +413 -0
  103. package/{cli/dist → dist}/core/crawler/types.d.ts +68 -2
  104. package/dist/core/crawler/wait-strategies.d.ts +108 -0
  105. package/dist/core/crawler/wait-strategies.js +399 -0
  106. package/dist/core/fixtures/factories.d.ts +180 -0
  107. package/dist/core/fixtures/factories.js +279 -0
  108. package/dist/core/fixtures/index.d.ts +6 -0
  109. package/dist/core/fixtures/index.js +6 -0
  110. package/{cli/dist → dist}/core/generation/crawler-pack-generator.d.ts +13 -3
  111. package/dist/core/generation/crawler-pack-generator.js +232 -0
  112. package/{cli/dist → dist}/core/generation/index.d.ts +2 -0
  113. package/{cli/dist → dist}/core/generation/index.js +2 -0
  114. package/{cli/dist → dist}/core/index.d.ts +2 -0
  115. package/{cli/dist → dist}/core/index.js +4 -0
  116. package/dist/core/network/index.d.ts +7 -0
  117. package/dist/core/network/index.js +7 -0
  118. package/dist/core/network/network-manager.d.ts +237 -0
  119. package/dist/core/network/network-manager.js +343 -0
  120. package/dist/core/network/network-simulator.d.ts +158 -0
  121. package/dist/core/network/network-simulator.js +261 -0
  122. package/{cli/dist → dist}/core/pack/validator.js +2 -2
  123. package/{cli/dist → dist}/core/pack-v2/migrator.d.ts +5 -0
  124. package/{cli/dist → dist}/core/pack-v2/migrator.js +81 -6
  125. package/{cli/dist → dist}/core/pack-v2/validator.js +4 -3
  126. package/{cli/dist → dist}/core/pom/base-page.js +1 -1
  127. package/{cli/dist → dist}/core/pom/loader.js +1 -1
  128. package/dist/core/reporting/index.d.ts +9 -0
  129. package/dist/core/reporting/index.js +10 -0
  130. package/dist/core/reporting/junit-reporter.d.ts +114 -0
  131. package/dist/core/reporting/junit-reporter.js +306 -0
  132. package/{cli/dist → dist}/core/runner/e2e-helpers.d.ts +1 -1
  133. package/{cli/dist → dist}/core/runner/e2e-helpers.js +2 -2
  134. package/{cli/dist → dist}/core/runner/phase3-runner.d.ts +3 -0
  135. package/{cli/dist → dist}/core/runner/phase3-runner.js +45 -14
  136. package/dist/core/sharding/test-sharding.d.ts +137 -0
  137. package/dist/core/sharding/test-sharding.js +233 -0
  138. package/dist/core/storage/cookie-manager.d.ts +160 -0
  139. package/dist/core/storage/cookie-manager.js +268 -0
  140. package/dist/core/storage/index.d.ts +7 -0
  141. package/dist/core/storage/index.js +7 -0
  142. package/dist/core/storage/storage-helpers.d.ts +138 -0
  143. package/dist/core/storage/storage-helpers.js +315 -0
  144. package/dist/core/test-helpers/index.d.ts +6 -0
  145. package/dist/core/test-helpers/index.js +6 -0
  146. package/dist/core/test-helpers/state-reset.d.ts +119 -0
  147. package/dist/core/test-helpers/state-reset.js +234 -0
  148. package/{cli/dist → dist}/core/types/pack-v1.d.ts +15 -2
  149. package/{cli/dist → dist}/core/types/pack-v2.d.ts +1 -1
  150. package/dist/core/upload/chunked-uploader.d.ts +150 -0
  151. package/dist/core/upload/chunked-uploader.js +289 -0
  152. package/dist/core/upload/index.d.ts +11 -0
  153. package/dist/core/upload/index.js +8 -0
  154. package/dist/core/upload/mime-validator.d.ts +119 -0
  155. package/dist/core/upload/mime-validator.js +373 -0
  156. package/dist/core/upload/presigned-uploader.d.ts +118 -0
  157. package/dist/core/upload/presigned-uploader.js +274 -0
  158. package/dist/core/utils/device-emulation.d.ts +194 -0
  159. package/dist/core/utils/device-emulation.js +380 -0
  160. package/dist/core/utils/index.d.ts +8 -0
  161. package/dist/core/utils/index.js +8 -0
  162. package/dist/core/utils/retry.d.ts +145 -0
  163. package/dist/core/utils/retry.js +242 -0
  164. package/dist/core/utils/smart-wait.d.ts +133 -0
  165. package/dist/core/utils/smart-wait.js +417 -0
  166. package/dist/core/visual/index.d.ts +7 -0
  167. package/dist/core/visual/index.js +7 -0
  168. package/dist/core/visual/pixel-diff.d.ts +87 -0
  169. package/dist/core/visual/pixel-diff.js +213 -0
  170. package/dist/core/visual/screenshot-helper.d.ts +130 -0
  171. package/dist/core/visual/screenshot-helper.js +223 -0
  172. package/{cli/dist → dist}/utils/config.d.ts +1 -1
  173. package/examples/README.md +160 -0
  174. package/examples/accessibility.yml +48 -0
  175. package/examples/api-basic.yml +27 -0
  176. package/examples/complete.yml +146 -0
  177. package/examples/crawler.yml +38 -0
  178. package/examples/fullstack.yml +78 -0
  179. package/examples/security.yml +58 -0
  180. package/examples/ui-advanced.yml +49 -0
  181. package/examples/ui-basic.yml +24 -0
  182. package/package.json +33 -67
  183. package/CHANGELOG.md +0 -330
  184. package/CONTRIBUTING.md +0 -273
  185. package/QUICK_START.md +0 -191
  186. package/cli/CHANGELOG.md +0 -84
  187. package/cli/LICENSE +0 -24
  188. package/cli/README.md +0 -222
  189. package/cli/dist/core/adapters/playwright-ui.js +0 -864
  190. package/cli/dist/core/auth/oauth2-provider.js +0 -114
  191. package/cli/dist/core/coverage/analyzer.d.ts +0 -101
  192. package/cli/dist/core/coverage/analyzer.js +0 -415
  193. package/cli/dist/core/coverage/collector.d.ts +0 -74
  194. package/cli/dist/core/coverage/collector.js +0 -459
  195. package/cli/dist/core/coverage/config.d.ts +0 -37
  196. package/cli/dist/core/coverage/config.js +0 -156
  197. package/cli/dist/core/coverage/index.d.ts +0 -11
  198. package/cli/dist/core/coverage/index.js +0 -15
  199. package/cli/dist/core/coverage/types.d.ts +0 -267
  200. package/cli/dist/core/coverage/types.js +0 -6
  201. package/cli/dist/core/coverage/vault.d.ts +0 -95
  202. package/cli/dist/core/coverage/vault.js +0 -405
  203. package/cli/dist/core/crawler/index.d.ts +0 -57
  204. package/cli/dist/core/fixtures/index.d.ts +0 -8
  205. package/cli/dist/core/fixtures/index.js +0 -8
  206. package/cli/dist/core/generation/crawler-pack-generator.js +0 -231
  207. package/cli/dist/core/reporting/index.d.ts +0 -6
  208. package/cli/dist/core/reporting/index.js +0 -6
  209. package/cli/dist/core/visual/index.d.ts +0 -6
  210. package/cli/dist/core/visual/index.js +0 -6
  211. package/cli/package.json +0 -76
  212. package/core/LICENSE +0 -24
  213. package/core/README.md +0 -105
  214. package/core/package.json +0 -90
  215. package/core/schemas/pack.schema.json +0 -236
  216. /package/{cli/bin → bin}/qa360.js +0 -0
  217. /package/{cli/dist → dist}/cli-minimal.d.ts +0 -0
  218. /package/{cli/dist → dist}/cli-minimal.js +0 -0
  219. /package/{cli/dist → dist}/commands/ai.d.ts +0 -0
  220. /package/{cli/dist → dist}/commands/ask.d.ts +0 -0
  221. /package/{cli/dist → dist}/commands/ask.js +0 -0
  222. /package/{cli/dist → dist}/commands/coverage.d.ts +0 -0
  223. /package/{cli/dist → dist}/commands/crawl.d.ts +0 -0
  224. /package/{cli/dist → dist}/commands/doctor.d.ts +0 -0
  225. /package/{cli/dist → dist}/commands/examples.d.ts +0 -0
  226. /package/{cli/dist → dist}/commands/examples.js +0 -0
  227. /package/{cli/dist → dist}/commands/explain.d.ts +0 -0
  228. /package/{cli/dist → dist}/commands/flakiness.d.ts +0 -0
  229. /package/{cli/dist → dist}/commands/generate.d.ts +0 -0
  230. /package/{cli/dist → dist}/commands/history.d.ts +0 -0
  231. /package/{cli/dist → dist}/commands/init.d.ts +0 -0
  232. /package/{cli/dist → dist}/commands/init.js +0 -0
  233. /package/{cli/dist → dist}/commands/monitor.d.ts +0 -0
  234. /package/{cli/dist → dist}/commands/ollama.d.ts +0 -0
  235. /package/{cli/dist → dist}/commands/pack.d.ts +0 -0
  236. /package/{cli/dist → dist}/commands/regression.d.ts +0 -0
  237. /package/{cli/dist → dist}/commands/repair.d.ts +0 -0
  238. /package/{cli/dist → dist}/commands/report.d.ts +0 -0
  239. /package/{cli/dist → dist}/commands/report.js +0 -0
  240. /package/{cli/dist → dist}/commands/retry.d.ts +0 -0
  241. /package/{cli/dist → dist}/commands/scan.d.ts +0 -0
  242. /package/{cli/dist → dist}/commands/scan.js +0 -0
  243. /package/{cli/dist → dist}/commands/secrets.d.ts +0 -0
  244. /package/{cli/dist → dist}/commands/serve.d.ts +0 -0
  245. /package/{cli/dist → dist}/commands/slo.d.ts +0 -0
  246. /package/{cli/dist → dist}/commands/verify.d.ts +0 -0
  247. /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.d.ts +0 -0
  248. /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.js +0 -0
  249. /package/{cli/dist → dist}/core/adapters/jest-adapter.d.ts +0 -0
  250. /package/{cli/dist → dist}/core/adapters/jest-adapter.js +0 -0
  251. /package/{cli/dist → dist}/core/adapters/k6-perf.d.ts +0 -0
  252. /package/{cli/dist → dist}/core/adapters/k6-perf.js +0 -0
  253. /package/{cli/dist → dist}/core/adapters/osv-deps.d.ts +0 -0
  254. /package/{cli/dist → dist}/core/adapters/osv-deps.js +0 -0
  255. /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.d.ts +0 -0
  256. /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.js +0 -0
  257. /package/{cli/dist → dist}/core/adapters/pytest-adapter.d.ts +0 -0
  258. /package/{cli/dist → dist}/core/adapters/pytest-adapter.js +0 -0
  259. /package/{cli/dist → dist}/core/adapters/semgrep-sast.d.ts +0 -0
  260. /package/{cli/dist → dist}/core/adapters/semgrep-sast.js +0 -0
  261. /package/{cli/dist → dist}/core/adapters/unit-test-types.d.ts +0 -0
  262. /package/{cli/dist → dist}/core/adapters/unit-test-types.js +0 -0
  263. /package/{cli/dist → dist}/core/adapters/vitest-adapter.d.ts +0 -0
  264. /package/{cli/dist → dist}/core/adapters/vitest-adapter.js +0 -0
  265. /package/{cli/dist → dist}/core/adapters/zap-dast.d.ts +0 -0
  266. /package/{cli/dist → dist}/core/adapters/zap-dast.js +0 -0
  267. /package/{cli/dist → dist}/core/ai/anthropic-provider.d.ts +0 -0
  268. /package/{cli/dist → dist}/core/ai/anthropic-provider.js +0 -0
  269. /package/{cli/dist → dist}/core/ai/deepseek-provider.d.ts +0 -0
  270. /package/{cli/dist → dist}/core/ai/deepseek-provider.js +0 -0
  271. /package/{cli/dist → dist}/core/ai/index.d.ts +0 -0
  272. /package/{cli/dist → dist}/core/ai/index.js +0 -0
  273. /package/{cli/dist → dist}/core/ai/llm-client.d.ts +0 -0
  274. /package/{cli/dist → dist}/core/ai/llm-client.js +0 -0
  275. /package/{cli/dist → dist}/core/ai/mock-provider.d.ts +0 -0
  276. /package/{cli/dist → dist}/core/ai/mock-provider.js +0 -0
  277. /package/{cli/dist → dist}/core/ai/ollama-provider.d.ts +0 -0
  278. /package/{cli/dist → dist}/core/ai/openai-provider.d.ts +0 -0
  279. /package/{cli/dist → dist}/core/ai/openai-provider.js +0 -0
  280. /package/{cli/dist → dist}/core/ai/provider-factory.d.ts +0 -0
  281. /package/{cli/dist → dist}/core/ai/provider-factory.js +0 -0
  282. /package/{cli/dist → dist}/core/artifacts/index.d.ts +0 -0
  283. /package/{cli/dist → dist}/core/artifacts/index.js +0 -0
  284. /package/{cli/dist → dist}/core/artifacts/ui-artifacts.d.ts +0 -0
  285. /package/{cli/dist → dist}/core/assertions/engine.d.ts +0 -0
  286. /package/{cli/dist → dist}/core/assertions/engine.js +0 -0
  287. /package/{cli/dist → dist}/core/assertions/index.d.ts +0 -0
  288. /package/{cli/dist → dist}/core/assertions/index.js +0 -0
  289. /package/{cli/dist → dist}/core/assertions/types.d.ts +0 -0
  290. /package/{cli/dist → dist}/core/assertions/types.js +0 -0
  291. /package/{cli/dist → dist}/core/auth/api-key-provider.d.ts +0 -0
  292. /package/{cli/dist → dist}/core/auth/api-key-provider.js +0 -0
  293. /package/{cli/dist → dist}/core/auth/aws-iam-provider.d.ts +0 -0
  294. /package/{cli/dist → dist}/core/auth/aws-iam-provider.js +0 -0
  295. /package/{cli/dist → dist}/core/auth/azure-ad-provider.d.ts +0 -0
  296. /package/{cli/dist → dist}/core/auth/azure-ad-provider.js +0 -0
  297. /package/{cli/dist → dist}/core/auth/gcp-adc-provider.d.ts +0 -0
  298. /package/{cli/dist → dist}/core/auth/gcp-adc-provider.js +0 -0
  299. /package/{cli/dist → dist}/core/auth/jwt-provider.d.ts +0 -0
  300. /package/{cli/dist → dist}/core/auth/jwt-provider.js +0 -0
  301. /package/{cli/dist → dist}/core/auth/manager.d.ts +0 -0
  302. /package/{cli/dist → dist}/core/auth/manager.js +0 -0
  303. /package/{cli/dist → dist}/core/auth/totp-provider.d.ts +0 -0
  304. /package/{cli/dist → dist}/core/auth/totp-provider.js +0 -0
  305. /package/{cli/dist → dist}/core/auth/ui-login-provider.d.ts +0 -0
  306. /package/{cli/dist → dist}/core/auth/ui-login-provider.js +0 -0
  307. /package/{cli/dist → dist}/core/cache/index.d.ts +0 -0
  308. /package/{cli/dist → dist}/core/cache/index.js +0 -0
  309. /package/{cli/dist → dist}/core/cache/lru-cache.d.ts +0 -0
  310. /package/{cli/dist → dist}/core/cache/lru-cache.js +0 -0
  311. /package/{cli/dist/core → dist}/core/coverage/analyzer.d.ts +0 -0
  312. /package/{cli/dist/core → dist}/core/coverage/analyzer.js +0 -0
  313. /package/{cli/dist/core → dist}/core/coverage/collector.d.ts +0 -0
  314. /package/{cli/dist/core → dist}/core/coverage/collector.js +0 -0
  315. /package/{cli/dist/core → dist}/core/coverage/config.d.ts +0 -0
  316. /package/{cli/dist/core → dist}/core/coverage/config.js +0 -0
  317. /package/{cli/dist/core → dist}/core/coverage/index.d.ts +0 -0
  318. /package/{cli/dist/core → dist}/core/coverage/index.js +0 -0
  319. /package/{cli/dist/core → dist}/core/coverage/types.d.ts +0 -0
  320. /package/{cli/dist/core → dist}/core/coverage/types.js +0 -0
  321. /package/{cli/dist/core → dist}/core/coverage/vault.d.ts +0 -0
  322. /package/{cli/dist/core → dist}/core/coverage/vault.js +0 -0
  323. /package/{cli/dist → dist}/core/crawler/journey-generator.d.ts +0 -0
  324. /package/{cli/dist → dist}/core/crawler/types.js +0 -0
  325. /package/{cli/dist → dist}/core/dashboard/assets.d.ts +0 -0
  326. /package/{cli/dist → dist}/core/dashboard/assets.js +0 -0
  327. /package/{cli/dist → dist}/core/dashboard/index.d.ts +0 -0
  328. /package/{cli/dist → dist}/core/dashboard/index.js +0 -0
  329. /package/{cli/dist → dist}/core/dashboard/server.d.ts +0 -0
  330. /package/{cli/dist → dist}/core/dashboard/server.js +0 -0
  331. /package/{cli/dist → dist}/core/dashboard/types.d.ts +0 -0
  332. /package/{cli/dist → dist}/core/dashboard/types.js +0 -0
  333. /package/{cli/dist → dist}/core/discoverer/index.d.ts +0 -0
  334. /package/{cli/dist → dist}/core/discoverer/index.js +0 -0
  335. /package/{cli/dist → dist}/core/fixtures/loader.d.ts +0 -0
  336. /package/{cli/dist → dist}/core/fixtures/loader.js +0 -0
  337. /package/{cli/dist → dist}/core/fixtures/resolver.d.ts +0 -0
  338. /package/{cli/dist → dist}/core/fixtures/resolver.js +0 -0
  339. /package/{cli/dist → dist}/core/fixtures/types.d.ts +0 -0
  340. /package/{cli/dist → dist}/core/fixtures/types.js +0 -0
  341. /package/{cli/dist → dist}/core/flakiness/index.d.ts +0 -0
  342. /package/{cli/dist → dist}/core/flakiness/index.js +0 -0
  343. /package/{cli/dist → dist}/core/generation/code-formatter.d.ts +0 -0
  344. /package/{cli/dist → dist}/core/generation/code-formatter.js +0 -0
  345. /package/{cli/dist → dist}/core/generation/code-generator.d.ts +0 -0
  346. /package/{cli/dist → dist}/core/generation/code-generator.js +0 -0
  347. /package/{cli/dist → dist}/core/generation/generator.d.ts +0 -0
  348. /package/{cli/dist → dist}/core/generation/generator.js +0 -0
  349. /package/{cli/dist → dist}/core/generation/pack-generator.d.ts +0 -0
  350. /package/{cli/dist → dist}/core/generation/pack-generator.js +0 -0
  351. /package/{cli/dist → dist}/core/generation/prompt-builder.d.ts +0 -0
  352. /package/{cli/dist → dist}/core/generation/prompt-builder.js +0 -0
  353. /package/{cli/dist → dist}/core/generation/source-analyzer.d.ts +0 -0
  354. /package/{cli/dist → dist}/core/generation/source-analyzer.js +0 -0
  355. /package/{cli/dist → dist}/core/generation/test-optimizer.d.ts +0 -0
  356. /package/{cli/dist → dist}/core/generation/test-optimizer.js +0 -0
  357. /package/{cli/dist → dist}/core/generation/types.d.ts +0 -0
  358. /package/{cli/dist → dist}/core/generation/types.js +0 -0
  359. /package/{cli/dist → dist}/core/hooks/compose.d.ts +0 -0
  360. /package/{cli/dist → dist}/core/hooks/compose.js +0 -0
  361. /package/{cli/dist → dist}/core/hooks/runner.d.ts +0 -0
  362. /package/{cli/dist → dist}/core/hooks/runner.js +0 -0
  363. /package/{cli/dist → dist}/core/pack/migrator.d.ts +0 -0
  364. /package/{cli/dist → dist}/core/pack/migrator.js +0 -0
  365. /package/{cli/dist → dist}/core/pack/validator.d.ts +0 -0
  366. /package/{cli/dist → dist}/core/pack-v2/index.d.ts +0 -0
  367. /package/{cli/dist → dist}/core/pack-v2/index.js +0 -0
  368. /package/{cli/dist → dist}/core/pack-v2/loader.d.ts +0 -0
  369. /package/{cli/dist → dist}/core/pack-v2/loader.js +0 -0
  370. /package/{cli/dist → dist}/core/pack-v2/validator.d.ts +0 -0
  371. /package/{cli/dist → dist}/core/parallel/index.d.ts +0 -0
  372. /package/{cli/dist → dist}/core/parallel/index.js +0 -0
  373. /package/{cli/dist → dist}/core/parallel/parallel-runner.d.ts +0 -0
  374. /package/{cli/dist → dist}/core/parallel/parallel-runner.js +0 -0
  375. /package/{cli/dist → dist}/core/pom/base-page.d.ts +0 -0
  376. /package/{cli/dist → dist}/core/pom/index.d.ts +0 -0
  377. /package/{cli/dist → dist}/core/pom/index.js +0 -0
  378. /package/{cli/dist → dist}/core/pom/loader.d.ts +0 -0
  379. /package/{cli/dist → dist}/core/pom/types.d.ts +0 -0
  380. /package/{cli/dist → dist}/core/pom/types.js +0 -0
  381. /package/{cli/dist → dist}/core/proof/bundle.d.ts +0 -0
  382. /package/{cli/dist → dist}/core/proof/bundle.js +0 -0
  383. /package/{cli/dist → dist}/core/proof/canonicalize.d.ts +0 -0
  384. /package/{cli/dist → dist}/core/proof/canonicalize.js +0 -0
  385. /package/{cli/dist → dist}/core/proof/index.d.ts +0 -0
  386. /package/{cli/dist → dist}/core/proof/index.js +0 -0
  387. /package/{cli/dist → dist}/core/proof/schema.d.ts +0 -0
  388. /package/{cli/dist → dist}/core/proof/schema.js +0 -0
  389. /package/{cli/dist → dist}/core/proof/signer.d.ts +0 -0
  390. /package/{cli/dist → dist}/core/proof/signer.js +0 -0
  391. /package/{cli/dist → dist}/core/proof/verifier.d.ts +0 -0
  392. /package/{cli/dist → dist}/core/proof/verifier.js +0 -0
  393. /package/{cli/dist → dist}/core/regression/detector.d.ts +0 -0
  394. /package/{cli/dist → dist}/core/regression/detector.js +0 -0
  395. /package/{cli/dist → dist}/core/regression/index.d.ts +0 -0
  396. /package/{cli/dist → dist}/core/regression/index.js +0 -0
  397. /package/{cli/dist → dist}/core/regression/trend-analyzer.d.ts +0 -0
  398. /package/{cli/dist → dist}/core/regression/trend-analyzer.js +0 -0
  399. /package/{cli/dist → dist}/core/regression/types.d.ts +0 -0
  400. /package/{cli/dist → dist}/core/regression/types.js +0 -0
  401. /package/{cli/dist → dist}/core/regression/vault.d.ts +0 -0
  402. /package/{cli/dist → dist}/core/regression/vault.js +0 -0
  403. /package/{cli/dist → dist}/core/repair/engine/fixer.d.ts +0 -0
  404. /package/{cli/dist → dist}/core/repair/engine/fixer.js +0 -0
  405. /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.d.ts +0 -0
  406. /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.js +0 -0
  407. /package/{cli/dist → dist}/core/repair/index.d.ts +0 -0
  408. /package/{cli/dist → dist}/core/repair/index.js +0 -0
  409. /package/{cli/dist → dist}/core/repair/repairer.d.ts +0 -0
  410. /package/{cli/dist → dist}/core/repair/repairer.js +0 -0
  411. /package/{cli/dist → dist}/core/repair/types.d.ts +0 -0
  412. /package/{cli/dist → dist}/core/repair/types.js +0 -0
  413. /package/{cli/dist → dist}/core/repair/utils/error-analyzer.d.ts +0 -0
  414. /package/{cli/dist → dist}/core/repair/utils/error-analyzer.js +0 -0
  415. /package/{cli/dist → dist}/core/reporting/html-reporter.d.ts +0 -0
  416. /package/{cli/dist → dist}/core/reporting/html-reporter.js +0 -0
  417. /package/{cli/dist → dist}/core/retry/flakiness-integration.d.ts +0 -0
  418. /package/{cli/dist → dist}/core/retry/flakiness-integration.js +0 -0
  419. /package/{cli/dist → dist}/core/retry/index.d.ts +0 -0
  420. /package/{cli/dist → dist}/core/retry/index.js +0 -0
  421. /package/{cli/dist → dist}/core/retry/retry-engine.d.ts +0 -0
  422. /package/{cli/dist → dist}/core/retry/retry-engine.js +0 -0
  423. /package/{cli/dist → dist}/core/retry/types.d.ts +0 -0
  424. /package/{cli/dist → dist}/core/retry/types.js +0 -0
  425. /package/{cli/dist → dist}/core/retry/vault.d.ts +0 -0
  426. /package/{cli/dist → dist}/core/retry/vault.js +0 -0
  427. /package/{cli/dist → dist}/core/schemas/pack.schema.json +0 -0
  428. /package/{cli/dist → dist}/core/secrets/crypto.d.ts +0 -0
  429. /package/{cli/dist → dist}/core/secrets/crypto.js +0 -0
  430. /package/{cli/dist → dist}/core/secrets/manager.d.ts +0 -0
  431. /package/{cli/dist → dist}/core/secrets/manager.js +0 -0
  432. /package/{cli/dist → dist}/core/security/redaction-patterns-extended.d.ts +0 -0
  433. /package/{cli/dist → dist}/core/security/redaction-patterns-extended.js +0 -0
  434. /package/{cli/dist → dist}/core/security/redactor.d.ts +0 -0
  435. /package/{cli/dist → dist}/core/security/redactor.js +0 -0
  436. /package/{cli/dist → dist}/core/self-healing/assertion-healer.d.ts +0 -0
  437. /package/{cli/dist → dist}/core/self-healing/assertion-healer.js +0 -0
  438. /package/{cli/dist → dist}/core/self-healing/engine.d.ts +0 -0
  439. /package/{cli/dist → dist}/core/self-healing/engine.js +0 -0
  440. /package/{cli/dist → dist}/core/self-healing/index.d.ts +0 -0
  441. /package/{cli/dist → dist}/core/self-healing/index.js +0 -0
  442. /package/{cli/dist → dist}/core/self-healing/selector-healer.d.ts +0 -0
  443. /package/{cli/dist → dist}/core/self-healing/selector-healer.js +0 -0
  444. /package/{cli/dist → dist}/core/self-healing/types.d.ts +0 -0
  445. /package/{cli/dist → dist}/core/self-healing/types.js +0 -0
  446. /package/{cli/dist → dist}/core/serve/diagnostics-collector.d.ts +0 -0
  447. /package/{cli/dist → dist}/core/serve/diagnostics-collector.js +0 -0
  448. /package/{cli/dist → dist}/core/serve/health-checker.d.ts +0 -0
  449. /package/{cli/dist → dist}/core/serve/health-checker.js +0 -0
  450. /package/{cli/dist → dist}/core/serve/index.d.ts +0 -0
  451. /package/{cli/dist → dist}/core/serve/index.js +0 -0
  452. /package/{cli/dist → dist}/core/serve/metrics-collector.d.ts +0 -0
  453. /package/{cli/dist → dist}/core/serve/metrics-collector.js +0 -0
  454. /package/{cli/dist → dist}/core/serve/process-manager.d.ts +0 -0
  455. /package/{cli/dist → dist}/core/serve/process-manager.js +0 -0
  456. /package/{cli/dist → dist}/core/serve/server.d.ts +0 -0
  457. /package/{cli/dist → dist}/core/serve/server.js +0 -0
  458. /package/{cli/dist → dist}/core/slo/config.d.ts +0 -0
  459. /package/{cli/dist → dist}/core/slo/config.js +0 -0
  460. /package/{cli/dist → dist}/core/slo/index.d.ts +0 -0
  461. /package/{cli/dist → dist}/core/slo/index.js +0 -0
  462. /package/{cli/dist → dist}/core/slo/sli-calculator.d.ts +0 -0
  463. /package/{cli/dist → dist}/core/slo/sli-calculator.js +0 -0
  464. /package/{cli/dist → dist}/core/slo/slo-tracker.d.ts +0 -0
  465. /package/{cli/dist → dist}/core/slo/slo-tracker.js +0 -0
  466. /package/{cli/dist → dist}/core/slo/types.d.ts +0 -0
  467. /package/{cli/dist → dist}/core/slo/types.js +0 -0
  468. /package/{cli/dist → dist}/core/slo/vault.d.ts +0 -0
  469. /package/{cli/dist → dist}/core/slo/vault.js +0 -0
  470. /package/{cli/dist → dist}/core/tui/index.d.ts +0 -0
  471. /package/{cli/dist → dist}/core/tui/index.js +0 -0
  472. /package/{cli/dist → dist}/core/tui/monitor.d.ts +0 -0
  473. /package/{cli/dist → dist}/core/tui/monitor.js +0 -0
  474. /package/{cli/dist → dist}/core/tui/renderer.d.ts +0 -0
  475. /package/{cli/dist → dist}/core/tui/renderer.js +0 -0
  476. /package/{cli/dist → dist}/core/tui/types.d.ts +0 -0
  477. /package/{cli/dist → dist}/core/tui/types.js +0 -0
  478. /package/{cli/dist → dist}/core/types/pack-v1.js +0 -0
  479. /package/{cli/dist → dist}/core/types/pack-v2.js +0 -0
  480. /package/{cli/dist → dist}/core/types/trust-score.d.ts +0 -0
  481. /package/{cli/dist → dist}/core/types/trust-score.js +0 -0
  482. /package/{cli/dist → dist}/core/vault/cas.d.ts +0 -0
  483. /package/{cli/dist → dist}/core/vault/cas.js +0 -0
  484. /package/{cli/dist → dist}/core/vault/index.d.ts +0 -0
  485. /package/{cli/dist → dist}/core/vault/index.js +0 -0
  486. /package/{cli/dist → dist}/core/visual/visual-regression.d.ts +0 -0
  487. /package/{cli/dist → dist}/core/visual/visual-regression.js +0 -0
  488. /package/{cli/dist → dist}/core/watch/index.d.ts +0 -0
  489. /package/{cli/dist → dist}/core/watch/index.js +0 -0
  490. /package/{cli/dist → dist}/core/watch/watch-mode.d.ts +0 -0
  491. /package/{cli/dist → dist}/core/watch/watch-mode.js +0 -0
  492. /package/{cli/dist → dist}/generators/index.d.ts +0 -0
  493. /package/{cli/dist → dist}/generators/index.js +0 -0
  494. /package/{cli/dist → dist}/generators/json-reporter.d.ts +0 -0
  495. /package/{cli/dist → dist}/generators/json-reporter.js +0 -0
  496. /package/{cli/dist → dist}/generators/test-generator.d.ts +0 -0
  497. /package/{cli/dist → dist}/generators/test-generator.js +0 -0
  498. /package/{cli/dist → dist}/index.d.ts +0 -0
  499. /package/{cli/dist → dist}/index.js +0 -0
  500. /package/{cli/dist → dist}/scanners/dom-scanner.d.ts +0 -0
  501. /package/{cli/dist → dist}/scanners/dom-scanner.js +0 -0
  502. /package/{cli/dist → dist}/scanners/index.d.ts +0 -0
  503. /package/{cli/dist → dist}/scanners/index.js +0 -0
  504. /package/{cli/dist → dist}/schemas/pack.schema.json +0 -0
  505. /package/{cli/dist → dist}/types/scan.d.ts +0 -0
  506. /package/{cli/dist → dist}/types/scan.js +0 -0
  507. /package/{cli/dist → dist}/utils/config.js +0 -0
@@ -0,0 +1,1069 @@
1
+ /**
2
+ * QA360 Advanced Interactions Handler
3
+ *
4
+ * P0 Features for Enterprise-Grade Testing:
5
+ * - Shadow DOM support (auto-piercing)
6
+ * - Iframe handling (same-origin + cross-origin)
7
+ * - File upload/download
8
+ *
9
+ * @since 2.3.0
10
+ */
11
+ /**
12
+ * Advanced Interactions Handler
13
+ *
14
+ * Handles complex DOM interactions that go beyond simple element selection.
15
+ */
16
+ export class AdvancedInteractionsHandler {
17
+ page;
18
+ options;
19
+ // Track discovered shadow roots for reuse
20
+ shadowRootCache = new Map();
21
+ // Track discovered frames
22
+ frameCache = new Map();
23
+ constructor(page, options = {}) {
24
+ this.page = page;
25
+ this.options = {
26
+ maxShadowDepth: options.maxShadowDepth ?? 5,
27
+ autoShadowPiercing: options.autoShadowPiercing ?? true,
28
+ handleIframes: options.handleIframes ?? true,
29
+ frameTimeout: options.frameTimeout ?? 5000,
30
+ };
31
+ }
32
+ /**
33
+ * ═══════════════════════════════════════════════════════════════════════════════
34
+ * SHADOW DOM SUPPORT
35
+ * ═══════════════════════════════════════════════════════════════════════════════
36
+ */
37
+ /**
38
+ * Find an element with automatic shadow DOM piercing
39
+ *
40
+ * Attempts to find an element in the main DOM first,
41
+ * then recursively searches through shadow DOM trees.
42
+ *
43
+ * @param selector - CSS selector to search for
44
+ * @param options - Search options
45
+ * @returns Element location with context
46
+ */
47
+ async findWithShadowPiercing(selector, options = {}) {
48
+ const { timeout = 5000, maxDepth = this.options.maxShadowDepth } = options;
49
+ // First, try normal locator (non-shadow DOM)
50
+ // Use JavaScript to check if element exists in regular DOM only
51
+ const inRegularDOM = await this.page.evaluate((sel) => {
52
+ const element = document.querySelector(sel);
53
+ if (!element)
54
+ return false;
55
+ // Check if element is in a shadow root using getRootNode()
56
+ // getRootNode() returns the ShadowRoot if element is within one,
57
+ // otherwise returns the Document
58
+ const rootNode = element.getRootNode();
59
+ return rootNode === document; // true if in regular DOM, false if in shadow DOM
60
+ }, selector).catch(() => false);
61
+ if (inRegularDOM) {
62
+ // Element exists in regular DOM, not shadow DOM
63
+ const normalLocator = this.page.locator(selector).first();
64
+ return {
65
+ locator: normalLocator,
66
+ isInShadowDom: false,
67
+ shadowDepth: 0,
68
+ isInIframe: false,
69
+ path: [selector],
70
+ };
71
+ }
72
+ // Shadow DOM piercing not enabled
73
+ if (!this.options.autoShadowPiercing) {
74
+ return null;
75
+ }
76
+ // Search through shadow DOM
77
+ const result = await this.searchInShadowDom(selector, maxDepth);
78
+ return result;
79
+ }
80
+ /**
81
+ * Recursively search for element in shadow DOM
82
+ * Supports nested shadow DOM (shadow roots within shadow roots)
83
+ */
84
+ async searchInShadowDom(selector, maxDepth, currentDepth = 0) {
85
+ if (currentDepth >= maxDepth) {
86
+ return null;
87
+ }
88
+ // JavaScript-based shadow DOM piercing with nested shadow root support
89
+ const result = await this.page.evaluate(({ sel, maxD }) => {
90
+ // Helper to get shadow root
91
+ function getShadowRoot(element) {
92
+ if ('shadowRoot' in element) {
93
+ return element.shadowRoot;
94
+ }
95
+ return null;
96
+ }
97
+ // BFS through shadow DOM with nested shadow root tracking
98
+ // Start with document.body, not in shadow yet
99
+ const queue = [
100
+ { element: document.body, shadowDepth: 0, path: [], parentShadowRoot: null },
101
+ ];
102
+ const visited = new Set();
103
+ let deepestShadowDepth = 0;
104
+ while (queue.length > 0) {
105
+ const { element, shadowDepth, path, parentShadowRoot } = queue.shift();
106
+ if (visited.has(element))
107
+ continue;
108
+ visited.add(element);
109
+ // Check if element has shadow root
110
+ const shadowRoot = getShadowRoot(element);
111
+ if (shadowRoot) {
112
+ // Track deepest shadow depth encountered
113
+ const newShadowDepth = shadowDepth + 1;
114
+ if (newShadowDepth > deepestShadowDepth) {
115
+ deepestShadowDepth = newShadowDepth;
116
+ }
117
+ // Check max depth
118
+ if (newShadowDepth > maxD) {
119
+ continue;
120
+ }
121
+ // Search in this shadow root
122
+ const found = shadowRoot.querySelector(sel);
123
+ if (found) {
124
+ return {
125
+ found: true,
126
+ path: path,
127
+ depth: newShadowDepth,
128
+ shadowDepth: newShadowDepth,
129
+ tagName: found.tagName.toLowerCase(),
130
+ id: found.id,
131
+ className: found.className,
132
+ };
133
+ }
134
+ // Add all children in this shadow root to queue
135
+ // They are at the new shadow depth
136
+ const shadowChildren = Array.from(shadowRoot.children);
137
+ for (let i = 0; i < shadowChildren.length; i++) {
138
+ const child = shadowChildren[i];
139
+ // Check if this child has its own shadow root (nested shadow)
140
+ const childShadowRoot = getShadowRoot(child);
141
+ if (childShadowRoot) {
142
+ // Nested shadow root - add at increased depth
143
+ queue.push({
144
+ element: child,
145
+ shadowDepth: newShadowDepth,
146
+ path: [...path, i],
147
+ parentShadowRoot: shadowRoot,
148
+ });
149
+ }
150
+ else {
151
+ // Regular element within shadow root
152
+ queue.push({
153
+ element: child,
154
+ shadowDepth: newShadowDepth,
155
+ path: [...path, i],
156
+ parentShadowRoot: shadowRoot,
157
+ });
158
+ }
159
+ }
160
+ }
161
+ else if (parentShadowRoot) {
162
+ // Element is within a shadow root but doesn't have its own shadow
163
+ // Add its children at the same shadow depth
164
+ const children = Array.from(element.children);
165
+ for (let i = 0; i < children.length; i++) {
166
+ const child = children[i];
167
+ const childShadowRoot = getShadowRoot(child);
168
+ queue.push({
169
+ element: child,
170
+ shadowDepth: childShadowRoot ? shadowDepth + 1 : shadowDepth,
171
+ path: [...path, i],
172
+ parentShadowRoot: parentShadowRoot,
173
+ });
174
+ }
175
+ }
176
+ else {
177
+ // Element is in regular DOM (before first shadow root)
178
+ const children = Array.from(element.children);
179
+ for (let i = 0; i < children.length; i++) {
180
+ queue.push({
181
+ element: children[i],
182
+ shadowDepth: 0,
183
+ path: [...path, i],
184
+ parentShadowRoot: null,
185
+ });
186
+ }
187
+ }
188
+ }
189
+ return { found: false };
190
+ }, { sel: selector, maxD: maxDepth });
191
+ if (!result || !result.found) {
192
+ return null;
193
+ }
194
+ const shadowResult = result;
195
+ // Create locator for the found element
196
+ const shadowSelector = this.buildShadowDomSelector(selector, shadowResult.shadowDepth ?? 0, shadowResult.path);
197
+ return {
198
+ locator: this.page.locator(shadowSelector).first(),
199
+ isInShadowDom: true,
200
+ shadowDepth: shadowResult.shadowDepth ?? 0,
201
+ nestedShadowDepth: shadowResult.shadowDepth ?? 0,
202
+ isInIframe: false,
203
+ path: [shadowSelector],
204
+ };
205
+ }
206
+ /**
207
+ * Build a Playwright-compatible selector for shadow DOM elements
208
+ *
209
+ * Playwright supports shadow DOM piercing with >> >>
210
+ */
211
+ buildShadowDomSelector(baseSelector, depth, path) {
212
+ if (depth === 0) {
213
+ return baseSelector;
214
+ }
215
+ // For shadow DOM, we need to pierce through shadow roots
216
+ // Playwright supports: selector >> >>> inner-selector
217
+ // But we don't know the exact path, so we use a generic approach
218
+ // For now, use the base selector - the actual shadow piercing
219
+ // will be done via JavaScript in the action methods
220
+ return baseSelector;
221
+ }
222
+ /**
223
+ * Click an element with shadow DOM support
224
+ */
225
+ async clickWithShadowPiercing(selector) {
226
+ const location = await this.findWithShadowPiercing(selector);
227
+ if (!location) {
228
+ return false;
229
+ }
230
+ try {
231
+ if (location.isInShadowDom) {
232
+ // For shadow DOM, use JavaScript click with proper shadow piercing
233
+ const result = await this.page.evaluate((sel) => {
234
+ // Find element in shadow DOM
235
+ const findInShadow = (root) => {
236
+ // Direct match
237
+ const direct = root.querySelector(sel);
238
+ if (direct)
239
+ return direct;
240
+ // Search in shadow roots
241
+ const elements = root.querySelectorAll('*');
242
+ for (const el of elements) {
243
+ if ('shadowRoot' in el) {
244
+ const shadow = el.shadowRoot;
245
+ if (shadow) {
246
+ const found = findInShadow(shadow);
247
+ if (found)
248
+ return found;
249
+ }
250
+ }
251
+ }
252
+ return null;
253
+ };
254
+ const element = findInShadow(document);
255
+ if (element) {
256
+ element.click();
257
+ return { found: true };
258
+ }
259
+ return { found: false };
260
+ }, selector);
261
+ return result?.found ?? false;
262
+ }
263
+ else {
264
+ // Normal DOM - use Playwright locator
265
+ await location.locator.click();
266
+ return true;
267
+ }
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ }
273
+ /**
274
+ * Fill an input with shadow DOM support
275
+ */
276
+ async fillWithShadowPiercing(selector, value) {
277
+ const location = await this.findWithShadowPiercing(selector);
278
+ if (!location) {
279
+ return false;
280
+ }
281
+ try {
282
+ if (location.isInShadowDom) {
283
+ const result = await this.page.evaluate(({ sel, val }) => {
284
+ const findInShadow = (root) => {
285
+ const direct = root.querySelector(sel);
286
+ if (direct)
287
+ return direct;
288
+ const elements = root.querySelectorAll('*');
289
+ for (const el of elements) {
290
+ if ('shadowRoot' in el) {
291
+ const shadow = el.shadowRoot;
292
+ if (shadow) {
293
+ const found = findInShadow(shadow);
294
+ if (found)
295
+ return found;
296
+ }
297
+ }
298
+ }
299
+ return null;
300
+ };
301
+ const element = findInShadow(document);
302
+ if (element) {
303
+ element.value = val;
304
+ element.dispatchEvent(new Event('input', { bubbles: true }));
305
+ element.dispatchEvent(new Event('change', { bubbles: true }));
306
+ return true;
307
+ }
308
+ return false;
309
+ }, { sel: selector, val: value });
310
+ return result ?? false;
311
+ }
312
+ else {
313
+ await location.locator.fill(value);
314
+ return true;
315
+ }
316
+ }
317
+ catch {
318
+ return false;
319
+ }
320
+ }
321
+ /**
322
+ * ═══════════════════════════════════════════════════════════════════════════════
323
+ * IFRAME HANDLING
324
+ * ═══════════════════════════════════════════════════════════════════════════════
325
+ */
326
+ /**
327
+ * Find an element within iframes
328
+ *
329
+ * Searches through same-origin iframes recursively.
330
+ *
331
+ * @param selector - CSS selector to search for
332
+ * @param options - Search options
333
+ * @returns Element location with iframe context
334
+ */
335
+ async findInIframes(selector, options = {}) {
336
+ const { timeout = 5000, maxDepth = 3, allowCrossOrigin = true } = options;
337
+ if (!this.options.handleIframes) {
338
+ return null;
339
+ }
340
+ const baseUrl = this.page.url();
341
+ // First, try same-origin iframes (faster, can use frame API directly)
342
+ const sameOriginResult = await this.findInSameOriginIframes(selector, { timeout, maxDepth, baseUrl });
343
+ if (sameOriginResult) {
344
+ return sameOriginResult;
345
+ }
346
+ // Then try cross-origin iframes if allowed
347
+ if (allowCrossOrigin) {
348
+ return await this.findInCrossOriginIframes(selector, { timeout, maxDepth });
349
+ }
350
+ return null;
351
+ }
352
+ /**
353
+ * Find element in same-origin iframes (faster, direct frame access)
354
+ */
355
+ async findInSameOriginIframes(selector, options) {
356
+ const { timeout, maxDepth, baseUrl } = options;
357
+ const frames = this.page.frames();
358
+ for (const frame of frames) {
359
+ if (frame === this.page.mainFrame()) {
360
+ continue;
361
+ }
362
+ try {
363
+ const frameUrl = frame.url();
364
+ // Skip cross-origin or empty frames
365
+ if (!frameUrl || !this.isSameOrigin(baseUrl, frameUrl)) {
366
+ continue;
367
+ }
368
+ // Use frame's waitForSelector (works for same-origin)
369
+ const element = await frame.waitForSelector(selector, { timeout: timeout / frames.length }).catch(() => null);
370
+ if (element) {
371
+ const frameSelector = await this.getFrameSelector(frame);
372
+ const locator = this.page.frameLocator(frameSelector || 'iframe').locator(selector);
373
+ return {
374
+ locator,
375
+ isInShadowDom: false,
376
+ shadowDepth: 0,
377
+ isInIframe: true,
378
+ isCrossOrigin: false,
379
+ frameSelector,
380
+ path: [`${frameSelector || 'iframe'} >> ${selector}`],
381
+ };
382
+ }
383
+ // Recursively search nested iframes
384
+ const nestedResult = await this.findInNestedFrames(frame, selector, maxDepth - 1);
385
+ if (nestedResult) {
386
+ return nestedResult;
387
+ }
388
+ }
389
+ catch {
390
+ // Continue to next frame
391
+ }
392
+ }
393
+ return null;
394
+ }
395
+ /**
396
+ * Find element in cross-origin iframes using frameLocator
397
+ *
398
+ * For cross-origin iframes, we cannot use frame.waitForSelector() due to SOP.
399
+ * Instead, we use Playwright's frameLocator which handles cross-origin transparently.
400
+ */
401
+ async findInCrossOriginIframes(selector, options) {
402
+ const { timeout, maxDepth } = options;
403
+ // Get all iframe elements in the page
404
+ const iframeElements = await this.page.$$('iframe');
405
+ for (const iframe of iframeElements) {
406
+ try {
407
+ // Get iframe selector for this iframe
408
+ const frameSelector = await this.getFrameSelectorForElement(iframe);
409
+ // Use frameLocator to check if element exists (works for cross-origin)
410
+ const frameLocator = this.page.frameLocator(frameSelector);
411
+ const elementLocator = frameLocator.locator(selector);
412
+ // Try to wait for element with short timeout
413
+ try {
414
+ await elementLocator.waitFor({ state: 'attached', timeout: timeout / iframeElements.length });
415
+ // Element found!
416
+ return {
417
+ locator: elementLocator,
418
+ isInShadowDom: false,
419
+ shadowDepth: 0,
420
+ isInIframe: true,
421
+ isCrossOrigin: true,
422
+ frameSelector,
423
+ path: [`${frameSelector} >> ${selector}`],
424
+ };
425
+ }
426
+ catch {
427
+ // Element not found in this iframe, continue
428
+ }
429
+ }
430
+ catch {
431
+ // Error accessing this iframe, continue
432
+ }
433
+ }
434
+ return null;
435
+ }
436
+ /**
437
+ * Recursively search nested iframes
438
+ */
439
+ async findInNestedFrames(parentFrame, selector, maxDepth) {
440
+ if (maxDepth <= 0) {
441
+ return null;
442
+ }
443
+ const childFrames = parentFrame.childFrames();
444
+ for (const frame of childFrames) {
445
+ try {
446
+ // Use frame's waitForSelector instead of locator
447
+ const element = await frame.waitForSelector(selector, { timeout: 1000 }).catch(() => null);
448
+ if (element) {
449
+ const frameSelector = await this.getFrameSelector(frame);
450
+ const page = frame.page();
451
+ // Create a locator using page.frameLocator
452
+ const locator = (page || this.page).frameLocator(frameSelector || 'iframe').locator(selector);
453
+ return {
454
+ locator,
455
+ isInShadowDom: false,
456
+ shadowDepth: 0,
457
+ isInIframe: true,
458
+ frameSelector,
459
+ path: [`${frameSelector || 'iframe'} >> ${selector}`],
460
+ };
461
+ }
462
+ // Recurse deeper
463
+ const nestedResult = await this.findInNestedFrames(frame, selector, maxDepth - 1);
464
+ if (nestedResult) {
465
+ return nestedResult;
466
+ }
467
+ }
468
+ catch {
469
+ // Continue
470
+ }
471
+ }
472
+ return null;
473
+ }
474
+ /**
475
+ * Get the selector for a frame
476
+ */
477
+ async getFrameSelector(frame) {
478
+ // Try to find the iframe element in the parent
479
+ const page = frame.page();
480
+ const frameUrl = frame.url();
481
+ // Search for iframe with matching src
482
+ const iframes = await page.$$('iframe');
483
+ for (const iframe of iframes) {
484
+ const src = await iframe.getAttribute('src');
485
+ const name = await iframe.getAttribute('name');
486
+ if (src && frameUrl.includes(src)) {
487
+ // Found by src - generate selector
488
+ const id = await iframe.getAttribute('id');
489
+ if (id) {
490
+ return `#${id}`;
491
+ }
492
+ if (name) {
493
+ return `iframe[name="${name}"]`;
494
+ }
495
+ // Use nth
496
+ const index = await page.$$eval('iframe', (frames, target) => {
497
+ return frames.findIndex((f) => f === target);
498
+ }, iframe);
499
+ return `iframe >> nth=${index}`;
500
+ }
501
+ if (name) {
502
+ return `iframe[name="${name}"]`;
503
+ }
504
+ }
505
+ // Fallback: use frame's name
506
+ return `iframe`;
507
+ }
508
+ /**
509
+ * Get the selector for an iframe element
510
+ * Used for cross-origin iframe handling where we have an ElementHandle
511
+ */
512
+ async getFrameSelectorForElement(iframe) {
513
+ // Try to get a unique identifier for the iframe
514
+ const id = await iframe.getAttribute('id');
515
+ if (id) {
516
+ return `#${id}`;
517
+ }
518
+ const name = await iframe.getAttribute('name');
519
+ if (name) {
520
+ return `iframe[name="${name}"]`;
521
+ }
522
+ // Try to get src for identification
523
+ const src = await iframe.getAttribute('src');
524
+ if (src) {
525
+ // Use src as part of selector (with wildcard for partial matches)
526
+ // Note: For Playwright frameLocator, we need a reliable selector
527
+ // Try to find index by comparing with all iframes
528
+ const iframes = await this.page.$$('iframe');
529
+ for (let i = 0; i < iframes.length; i++) {
530
+ if (await iframes[i].evaluate((el, target) => el === target, iframe)) {
531
+ return `iframe >> nth=${i}`;
532
+ }
533
+ }
534
+ }
535
+ // Fallback: use iframe with nth
536
+ const iframes = await this.page.$$('iframe');
537
+ for (let i = 0; i < iframes.length; i++) {
538
+ if (await iframes[i].evaluate((el, target) => el === target, iframe)) {
539
+ return `iframe >> nth=${i}`;
540
+ }
541
+ }
542
+ return 'iframe';
543
+ }
544
+ /**
545
+ * Check if two URLs are same-origin
546
+ */
547
+ isSameOrigin(url1, url2) {
548
+ try {
549
+ const u1 = new URL(url1);
550
+ const u2 = new URL(url2);
551
+ return u1.origin === u2.origin;
552
+ }
553
+ catch {
554
+ return false;
555
+ }
556
+ }
557
+ /**
558
+ * Interact with element in iframe
559
+ *
560
+ * @param iframeSelector - Selector for the iframe
561
+ * @param elementSelector - Selector for element within iframe
562
+ * @param action - Action to perform
563
+ * @returns Action result
564
+ */
565
+ async interactInIframe(iframeSelector, elementSelector, action) {
566
+ const frameLocator = this.page.frameLocator(iframeSelector);
567
+ const frame = this.page.frame(iframeSelector);
568
+ if (!frame) {
569
+ throw new Error(`Frame not found: ${iframeSelector}`);
570
+ }
571
+ return await action(frame);
572
+ }
573
+ /**
574
+ * ═══════════════════════════════════════════════════════════════════════════════
575
+ * FILE OPERATIONS
576
+ * ═══════════════════════════════════════════════════════════════════════════════
577
+ */
578
+ /**
579
+ * Upload files to an input element
580
+ *
581
+ * @param selector - File input selector
582
+ * @param filePaths - Paths to files to upload
583
+ * @returns Upload result
584
+ */
585
+ async uploadFiles(selector, filePaths) {
586
+ const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
587
+ try {
588
+ // First try normal upload
589
+ const locator = this.page.locator(selector);
590
+ const count = await locator.count();
591
+ if (count === 0) {
592
+ // Try in shadow DOM with a shorter timeout for quick existence check
593
+ const shadowLocation = await this.findWithShadowPiercing(selector, { timeout: 1000 });
594
+ if (!shadowLocation) {
595
+ return {
596
+ success: false,
597
+ count: 0,
598
+ fileNames: [],
599
+ error: `Element not found: ${selector}`,
600
+ };
601
+ }
602
+ // For shadow DOM, we need to use JS to set files
603
+ const result = await this.uploadFilesInShadowDom(selector, paths);
604
+ return result;
605
+ }
606
+ // Use Playwright's setInputFiles
607
+ await locator.setInputFiles(paths);
608
+ return {
609
+ success: true,
610
+ count: paths.length,
611
+ fileNames: paths.map(p => p.split('/').pop() || p.split('\\').pop() || p),
612
+ };
613
+ }
614
+ catch (error) {
615
+ return {
616
+ success: false,
617
+ count: 0,
618
+ fileNames: [],
619
+ error: error instanceof Error ? error.message : String(error),
620
+ };
621
+ }
622
+ }
623
+ /**
624
+ * Upload files to an input in shadow DOM
625
+ */
626
+ async uploadFilesInShadowDom(selector, filePaths) {
627
+ // For shadow DOM, we need to:
628
+ // 1. Find the element
629
+ // 2. Create a FileList object
630
+ // 3. Set it on the input
631
+ // This is complex - for now, try using the page's evaluate with the file paths
632
+ // In a real scenario, you'd need to read the files as buffers/DataURLs
633
+ try {
634
+ const result = await this.page.evaluate(async ({ sel, count }) => {
635
+ const findInShadow = (root) => {
636
+ const direct = root.querySelector(sel);
637
+ if (direct)
638
+ return direct;
639
+ const elements = root.querySelectorAll('*');
640
+ for (const el of elements) {
641
+ if ('shadowRoot' in el) {
642
+ const shadow = el.shadowRoot;
643
+ if (shadow) {
644
+ const found = findInShadow(shadow);
645
+ if (found)
646
+ return found;
647
+ }
648
+ }
649
+ }
650
+ return null;
651
+ };
652
+ const input = findInShadow(document);
653
+ if (input && input.type === 'file') {
654
+ // Create a DataTransfer to simulate file selection
655
+ const dt = new DataTransfer();
656
+ // Note: We can't actually create files from paths in the browser
657
+ // This would need to be done differently - via the Playwright API
658
+ return { found: true, needsApi: true };
659
+ }
660
+ return { found: false };
661
+ }, { sel: selector, count: filePaths.length });
662
+ if (result.found && result.needsApi) {
663
+ // Use Playwright's API by getting the actual element handle
664
+ const element = await this.page.waitForSelector(selector, { timeout: 1000 });
665
+ if (element) {
666
+ await element.setInputFiles(filePaths);
667
+ return {
668
+ success: true,
669
+ count: filePaths.length,
670
+ fileNames: filePaths.map(p => p.split('/').pop() || p),
671
+ };
672
+ }
673
+ }
674
+ return {
675
+ success: false,
676
+ count: 0,
677
+ fileNames: [],
678
+ error: 'Could not find file input in shadow DOM',
679
+ };
680
+ }
681
+ catch (error) {
682
+ return {
683
+ success: false,
684
+ count: 0,
685
+ fileNames: [],
686
+ error: error instanceof Error ? error.message : String(error),
687
+ };
688
+ }
689
+ }
690
+ /**
691
+ * Handle file download with interception
692
+ *
693
+ * @param triggerSelector - Selector for element that triggers download
694
+ * @param options - Download options
695
+ * @returns Download result
696
+ */
697
+ async handleDownload(triggerSelector, options = {}) {
698
+ const { action = 'click', timeout = 30000, savePath } = options;
699
+ try {
700
+ // Check if trigger element exists first to avoid download timeout
701
+ const triggerElement = await this.page.$(triggerSelector);
702
+ if (!triggerElement) {
703
+ return {
704
+ success: false,
705
+ error: `Trigger element not found: ${triggerSelector}`,
706
+ };
707
+ }
708
+ // Set up download handler
709
+ const downloadPromise = this.page.waitForEvent('download', { timeout });
710
+ // Trigger download
711
+ if (action === 'click') {
712
+ await this.page.click(triggerSelector);
713
+ }
714
+ else {
715
+ // Find the form and submit it
716
+ const form = await this.page.$(`${triggerSelector} form`);
717
+ if (form) {
718
+ await form.evaluate((f) => f.submit());
719
+ }
720
+ else {
721
+ await this.page.click(triggerSelector);
722
+ }
723
+ }
724
+ // Wait for download to start
725
+ const download = await downloadPromise;
726
+ // Get file info
727
+ const fileName = download.suggestedFilename();
728
+ const failure = download.failure();
729
+ if (failure) {
730
+ return {
731
+ success: false,
732
+ error: `Download failed: ${failure}`,
733
+ };
734
+ }
735
+ // Save to path if specified
736
+ let finalPath;
737
+ let size;
738
+ if (savePath) {
739
+ // saveAs returns void, so we use savePath as the final path
740
+ await download.saveAs(savePath);
741
+ finalPath = savePath;
742
+ }
743
+ else {
744
+ // Save to temp dir and get the path
745
+ finalPath = await download.path();
746
+ }
747
+ // Get file size if we have a path
748
+ if (finalPath) {
749
+ try {
750
+ const fs = await import('fs/promises');
751
+ const stats = await fs.stat(finalPath);
752
+ size = stats.size;
753
+ }
754
+ catch {
755
+ // Size not available
756
+ }
757
+ }
758
+ return {
759
+ success: true,
760
+ path: finalPath,
761
+ fileName,
762
+ size,
763
+ };
764
+ }
765
+ catch (error) {
766
+ return {
767
+ success: false,
768
+ error: error instanceof Error ? error.message : String(error),
769
+ };
770
+ }
771
+ }
772
+ /**
773
+ * ═══════════════════════════════════════════════════════════════════════════════
774
+ * COMBINED SEARCH (Shadow DOM + Iframes)
775
+ * ═══════════════════════════════════════════════════════════════════════════════
776
+ */
777
+ /**
778
+ * Find element anywhere (normal DOM, shadow DOM, or iframes)
779
+ *
780
+ * This is the main entry point for finding elements that may be
781
+ * hidden in shadow DOM or inside iframes.
782
+ *
783
+ * @param selector - CSS selector
784
+ * @param options - Search options
785
+ * @returns Element location or null
786
+ */
787
+ async findElementAnywhere(selector, options = {}) {
788
+ const { timeout = 5000 } = options;
789
+ // 1. First, check if element exists in regular DOM ONLY (without shadow piercing)
790
+ const inRegularDOM = await this.page.evaluate((sel) => {
791
+ const element = document.querySelector(sel);
792
+ return element !== null;
793
+ }, selector).catch(() => false);
794
+ if (inRegularDOM) {
795
+ // Element exists in regular DOM, not shadow DOM
796
+ const normalLocator = this.page.locator(selector).first();
797
+ return {
798
+ locator: normalLocator,
799
+ isInShadowDom: false,
800
+ shadowDepth: 0,
801
+ nestedShadowDepth: 0,
802
+ isInIframe: false,
803
+ path: [selector],
804
+ };
805
+ }
806
+ // 2. Try shadow DOM (with piercing)
807
+ const shadowResult = await this.findWithShadowPiercing(selector, { timeout });
808
+ if (shadowResult) {
809
+ return shadowResult;
810
+ }
811
+ // 3. Try iframes
812
+ const iframeResult = await this.findInIframes(selector, { timeout });
813
+ if (iframeResult) {
814
+ return iframeResult;
815
+ }
816
+ // Not found
817
+ return null;
818
+ }
819
+ /**
820
+ * P0: Handle print dialog (window.print())
821
+ *
822
+ * Uses CDP to automatically dismiss or capture print dialogs.
823
+ *
824
+ * @param options - Print handling options
825
+ * @returns Promise that resolves when print dialog is handled
826
+ */
827
+ async handlePrintDialog(options = {}) {
828
+ const { action = 'dismiss', path } = options;
829
+ try {
830
+ // For Chromium, use CDP to handle print
831
+ const client = await this.page.context().newCDPSession(this.page);
832
+ if (action === 'capture' && path) {
833
+ // Enable page printing
834
+ await client.send('Page.enable');
835
+ // Generate PDF on print
836
+ // Note: This is a simplified approach - actual print-to-PDF would need more setup
837
+ const pdf = await this.page.pdf({
838
+ path,
839
+ printBackground: true,
840
+ });
841
+ return { handled: true, pdfPath: path };
842
+ }
843
+ else {
844
+ // Just dismiss the print dialog
845
+ // In headless mode, print dialogs don't show, but we track that print was called
846
+ return { handled: true };
847
+ }
848
+ }
849
+ catch (error) {
850
+ // Fallback: print dialog might not have shown (headless mode)
851
+ return { handled: false };
852
+ }
853
+ }
854
+ /**
855
+ * P0: Hover over an element
856
+ *
857
+ * Triggers hover state and waits for any visual changes.
858
+ * Supports elements in shadow DOM and iframes.
859
+ *
860
+ * @param selector - CSS selector for the element
861
+ * @param options - Hover options
862
+ * @returns Hover result
863
+ */
864
+ async hover(options) {
865
+ const { selector, waitForAnimation = true, animationWait = 100, force = false, } = options;
866
+ try {
867
+ // First do a quick check if element exists to avoid long timeout when not found
868
+ const quickCheck = await this.page.$(selector);
869
+ if (!quickCheck) {
870
+ // Try shadow DOM quick check
871
+ const shadowCheck = await this.findWithShadowPiercing(selector, { timeout: 1000 });
872
+ if (!shadowCheck) {
873
+ return {
874
+ success: false,
875
+ error: `Element not found: ${selector}`,
876
+ };
877
+ }
878
+ }
879
+ // Try to find element anywhere (including shadow DOM)
880
+ const location = await this.findElementAnywhere(selector, { timeout: 5000 });
881
+ if (!location) {
882
+ // Try standard locator
883
+ const locator = this.page.locator(selector);
884
+ await locator.hover({ force });
885
+ }
886
+ else {
887
+ await location.locator.hover({ force });
888
+ }
889
+ // Wait for animations/tooltip/dropdown to appear
890
+ if (waitForAnimation) {
891
+ await this.sleep(animationWait);
892
+ }
893
+ return { success: true };
894
+ }
895
+ catch (error) {
896
+ return {
897
+ success: false,
898
+ error: error instanceof Error ? error.message : String(error),
899
+ };
900
+ }
901
+ }
902
+ /**
903
+ * P0: Drag and drop operation
904
+ *
905
+ * Drags an element and drops it onto a target element.
906
+ * Supports elements in shadow DOM and iframes.
907
+ *
908
+ * @param options - Drag and drop options
909
+ * @returns Drag result
910
+ */
911
+ async dragAndDrop(options) {
912
+ const { from, to, position = 'center', verifyDrop = true } = options;
913
+ try {
914
+ // Find source element
915
+ const fromLocator = this.page.locator(from).first();
916
+ const fromCount = await fromLocator.count();
917
+ if (fromCount === 0) {
918
+ return {
919
+ success: false,
920
+ error: `Source element not found: ${from}`,
921
+ };
922
+ }
923
+ // Find target element
924
+ const toLocator = this.page.locator(to).first();
925
+ const toCount = await toLocator.count();
926
+ if (toCount === 0) {
927
+ return {
928
+ success: false,
929
+ error: `Target element not found: ${to}`,
930
+ };
931
+ }
932
+ // Perform drag and drop
933
+ await fromLocator.dragTo(toLocator, {
934
+ targetPosition: position === 'center' ? undefined : this.getPositionForDrop(position),
935
+ });
936
+ // Wait for animations
937
+ await this.sleep(100);
938
+ // Verify drop if requested
939
+ if (verifyDrop) {
940
+ // Check if source is no longer at original position (moved)
941
+ // or if target now contains source
942
+ await this.sleep(100);
943
+ }
944
+ return { success: true };
945
+ }
946
+ catch (error) {
947
+ return {
948
+ success: false,
949
+ error: error instanceof Error ? error.message : String(error),
950
+ };
951
+ }
952
+ }
953
+ /**
954
+ * P0: Drag element to specific coordinates
955
+ *
956
+ * Drags an element from its current position to specific x, y coordinates.
957
+ *
958
+ * @param options - Drag to coordinates options
959
+ * @returns Drag result
960
+ */
961
+ async dragToCoordinates(options) {
962
+ const { selector, x, y, fromX, fromY, steps = 10 } = options;
963
+ try {
964
+ const locator = this.page.locator(selector).first();
965
+ const count = await locator.count();
966
+ if (count === 0) {
967
+ return {
968
+ success: false,
969
+ error: `Element not found: ${selector}`,
970
+ };
971
+ }
972
+ const box = await locator.boundingBox();
973
+ if (!box) {
974
+ return {
975
+ success: false,
976
+ error: `Could not get bounding box for: ${selector}`,
977
+ };
978
+ }
979
+ const startX = fromX ?? box.x + box.width / 2;
980
+ const startY = fromY ?? box.y + box.height / 2;
981
+ // Perform the drag using mouse API
982
+ await this.page.mouse.move(startX, startY);
983
+ await this.page.mouse.down();
984
+ await this.page.mouse.move(x, y, { steps });
985
+ await this.page.mouse.up();
986
+ // Wait for animations
987
+ await this.sleep(100);
988
+ return { success: true };
989
+ }
990
+ catch (error) {
991
+ return {
992
+ success: false,
993
+ error: error instanceof Error ? error.message : String(error),
994
+ };
995
+ }
996
+ }
997
+ /**
998
+ * P0: Multi-select drag (select multiple elements)
999
+ *
1000
+ * Performs Ctrl+Click on multiple elements to select them.
1001
+ *
1002
+ * @param selectors - Array of CSS selectors to select
1003
+ * @returns Selection result
1004
+ */
1005
+ async multiSelect(selectors) {
1006
+ const errors = [];
1007
+ let selected = 0;
1008
+ for (const selector of selectors) {
1009
+ try {
1010
+ const locator = this.page.locator(selector).first();
1011
+ const count = await locator.count();
1012
+ if (count > 0) {
1013
+ // Ctrl+Click to add to selection
1014
+ await locator.click({
1015
+ modifiers: ['Control'],
1016
+ });
1017
+ selected++;
1018
+ }
1019
+ else {
1020
+ errors.push(`Element not found: ${selector}`);
1021
+ }
1022
+ }
1023
+ catch (error) {
1024
+ errors.push(`Error selecting ${selector}: ${error instanceof Error ? error.message : String(error)}`);
1025
+ }
1026
+ }
1027
+ return {
1028
+ success: errors.length === 0,
1029
+ selected,
1030
+ errors,
1031
+ };
1032
+ }
1033
+ /**
1034
+ * Get position offset for drop position
1035
+ */
1036
+ getPositionForDrop(position) {
1037
+ switch (position) {
1038
+ case 'top':
1039
+ return { x: 0.5, y: 0.25 }; // Top center
1040
+ case 'right':
1041
+ return { x: 0.75, y: 0.5 }; // Right center
1042
+ case 'bottom':
1043
+ return { x: 0.5, y: 0.75 }; // Bottom center
1044
+ case 'left':
1045
+ return { x: 0.25, y: 0.5 }; // Left center
1046
+ default:
1047
+ return { x: 0.5, y: 0.5 }; // Center
1048
+ }
1049
+ }
1050
+ /**
1051
+ * Sleep utility for waiting
1052
+ */
1053
+ sleep(ms) {
1054
+ return new Promise((resolve) => setTimeout(resolve, ms));
1055
+ }
1056
+ /**
1057
+ * Clear caches
1058
+ */
1059
+ clearCache() {
1060
+ this.shadowRootCache.clear();
1061
+ this.frameCache.clear();
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Factory function to create handler
1066
+ */
1067
+ export function createAdvancedInteractionsHandler(page, options) {
1068
+ return new AdvancedInteractionsHandler(page, options);
1069
+ }