@jackwener/opencli 1.5.6 → 1.5.8

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 (338) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +4 -2
  3. package/README.zh-CN.md +4 -1
  4. package/SKILL.md +879 -0
  5. package/dist/browser/cdp.d.ts +1 -0
  6. package/dist/browser/cdp.js +30 -27
  7. package/dist/browser/daemon-client.d.ts +7 -1
  8. package/dist/browser/daemon-client.js +3 -0
  9. package/dist/browser/dom-helpers.js +1 -0
  10. package/dist/browser/dom-helpers.test.js +14 -1
  11. package/dist/browser/mcp.js +18 -13
  12. package/dist/browser/page.js +22 -2
  13. package/dist/browser/page.test.d.ts +1 -0
  14. package/dist/browser/page.test.js +44 -0
  15. package/dist/browser/stealth.js +198 -0
  16. package/dist/browser/stealth.test.d.ts +1 -0
  17. package/dist/browser/stealth.test.js +134 -0
  18. package/dist/browser.test.js +1 -1
  19. package/dist/build-manifest.d.ts +1 -0
  20. package/dist/build-manifest.js +5 -1
  21. package/dist/build-manifest.test.js +2 -0
  22. package/dist/cli-manifest.json +544 -137
  23. package/dist/cli.js +20 -3
  24. package/dist/clis/antigravity/serve.d.ts +1 -1
  25. package/dist/clis/antigravity/serve.js +5 -8
  26. package/dist/clis/bilibili/subtitle.js +4 -0
  27. package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
  28. package/dist/clis/bilibili/subtitle.test.js +48 -0
  29. package/dist/clis/chatwise/ask.js +0 -2
  30. package/dist/clis/chatwise/export.js +0 -2
  31. package/dist/clis/chatwise/history.js +0 -2
  32. package/dist/clis/chatwise/model.js +0 -2
  33. package/dist/clis/chatwise/new.js +1 -2
  34. package/dist/clis/chatwise/read.js +0 -2
  35. package/dist/clis/chatwise/screenshot.js +1 -2
  36. package/dist/clis/chatwise/send.js +0 -2
  37. package/dist/clis/chatwise/status.js +1 -2
  38. package/dist/clis/ctrip/search.d.ts +13 -0
  39. package/dist/clis/ctrip/search.js +73 -48
  40. package/dist/clis/ctrip/search.test.d.ts +1 -0
  41. package/dist/clis/ctrip/search.test.js +64 -0
  42. package/dist/clis/douyin/_shared/sts2.js +8 -2
  43. package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
  44. package/dist/clis/douyin/_shared/sts2.test.js +27 -0
  45. package/dist/clis/douyin/activities.js +4 -2
  46. package/dist/clis/douyin/activities.test.js +34 -1
  47. package/dist/clis/douyin/collections.js +1 -1
  48. package/dist/clis/douyin/collections.test.js +24 -2
  49. package/dist/clis/douyin/draft.d.ts +8 -11
  50. package/dist/clis/douyin/draft.js +302 -185
  51. package/dist/clis/douyin/draft.test.d.ts +1 -1
  52. package/dist/clis/douyin/draft.test.js +357 -2
  53. package/dist/clis/douyin/hashtag.js +9 -2
  54. package/dist/clis/douyin/hashtag.test.js +35 -2
  55. package/dist/clis/douyin/profile.js +1 -1
  56. package/dist/clis/douyin/profile.test.js +36 -1
  57. package/dist/clis/douyin/videos.js +22 -5
  58. package/dist/clis/douyin/videos.test.js +45 -2
  59. package/dist/clis/facebook/search.test.d.ts +5 -0
  60. package/dist/clis/facebook/search.test.js +60 -0
  61. package/dist/clis/facebook/search.yaml +4 -3
  62. package/dist/clis/instagram/download.d.ts +16 -0
  63. package/dist/clis/instagram/download.js +225 -0
  64. package/dist/clis/instagram/download.test.d.ts +1 -0
  65. package/dist/clis/instagram/download.test.js +118 -0
  66. package/dist/clis/notebooklm/bind-current.d.ts +1 -0
  67. package/dist/clis/notebooklm/bind-current.js +29 -0
  68. package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
  69. package/dist/clis/notebooklm/bind-current.test.js +35 -0
  70. package/dist/clis/notebooklm/binding.test.d.ts +1 -0
  71. package/dist/clis/notebooklm/binding.test.js +44 -0
  72. package/dist/clis/notebooklm/compat.test.d.ts +3 -0
  73. package/dist/clis/notebooklm/compat.test.js +16 -0
  74. package/dist/clis/notebooklm/current.d.ts +1 -0
  75. package/dist/clis/notebooklm/current.js +28 -0
  76. package/dist/clis/notebooklm/get.d.ts +1 -0
  77. package/dist/clis/notebooklm/get.js +37 -0
  78. package/dist/clis/notebooklm/history.d.ts +1 -0
  79. package/dist/clis/notebooklm/history.js +25 -0
  80. package/dist/clis/notebooklm/history.test.d.ts +1 -0
  81. package/dist/clis/notebooklm/history.test.js +58 -0
  82. package/dist/clis/notebooklm/list.d.ts +1 -0
  83. package/dist/clis/notebooklm/list.js +35 -0
  84. package/dist/clis/notebooklm/note-list.d.ts +1 -0
  85. package/dist/clis/notebooklm/note-list.js +28 -0
  86. package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
  87. package/dist/clis/notebooklm/note-list.test.js +56 -0
  88. package/dist/clis/notebooklm/notes-get.d.ts +1 -0
  89. package/dist/clis/notebooklm/notes-get.js +47 -0
  90. package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
  91. package/dist/clis/notebooklm/notes-get.test.js +72 -0
  92. package/dist/clis/notebooklm/rpc.d.ts +36 -0
  93. package/dist/clis/notebooklm/rpc.js +189 -0
  94. package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
  95. package/dist/clis/notebooklm/rpc.test.js +105 -0
  96. package/dist/clis/notebooklm/shared.d.ts +87 -0
  97. package/dist/clis/notebooklm/shared.js +3 -0
  98. package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
  99. package/dist/clis/notebooklm/source-fulltext.js +44 -0
  100. package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
  101. package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
  102. package/dist/clis/notebooklm/source-get.d.ts +1 -0
  103. package/dist/clis/notebooklm/source-get.js +40 -0
  104. package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
  105. package/dist/clis/notebooklm/source-get.test.js +84 -0
  106. package/dist/clis/notebooklm/source-guide.d.ts +1 -0
  107. package/dist/clis/notebooklm/source-guide.js +44 -0
  108. package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
  109. package/dist/clis/notebooklm/source-guide.test.js +104 -0
  110. package/dist/clis/notebooklm/source-list.d.ts +1 -0
  111. package/dist/clis/notebooklm/source-list.js +30 -0
  112. package/dist/clis/notebooklm/status.d.ts +1 -0
  113. package/dist/clis/notebooklm/status.js +31 -0
  114. package/dist/clis/notebooklm/summary.d.ts +1 -0
  115. package/dist/clis/notebooklm/summary.js +30 -0
  116. package/dist/clis/notebooklm/summary.test.d.ts +1 -0
  117. package/dist/clis/notebooklm/summary.test.js +78 -0
  118. package/dist/clis/notebooklm/utils.d.ts +37 -0
  119. package/dist/clis/notebooklm/utils.js +739 -0
  120. package/dist/clis/notebooklm/utils.test.d.ts +1 -0
  121. package/dist/clis/notebooklm/utils.test.js +390 -0
  122. package/dist/clis/substack/utils.d.ts +4 -0
  123. package/dist/clis/substack/utils.js +8 -2
  124. package/dist/clis/substack/utils.test.d.ts +1 -0
  125. package/dist/clis/substack/utils.test.js +46 -0
  126. package/dist/clis/v2ex/hot.yaml +4 -1
  127. package/dist/clis/v2ex/latest.yaml +4 -1
  128. package/dist/clis/v2ex/topic.yaml +6 -1
  129. package/dist/clis/weixin/download.d.ts +9 -0
  130. package/dist/clis/weixin/download.js +76 -6
  131. package/dist/clis/weread/book.js +108 -2
  132. package/dist/clis/weread/commands.test.js +262 -152
  133. package/dist/clis/weread/utils.d.ts +10 -0
  134. package/dist/clis/weread/utils.js +27 -7
  135. package/dist/clis/xiaohongshu/comments.d.ts +3 -0
  136. package/dist/clis/xiaohongshu/comments.js +76 -17
  137. package/dist/clis/xiaohongshu/comments.test.js +70 -9
  138. package/dist/clis/xiaohongshu/download.d.ts +4 -1
  139. package/dist/clis/xiaohongshu/download.js +83 -22
  140. package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
  141. package/dist/clis/xiaohongshu/download.test.js +75 -0
  142. package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
  143. package/dist/clis/xiaohongshu/note-helpers.js +23 -0
  144. package/dist/clis/xiaohongshu/note.d.ts +7 -0
  145. package/dist/clis/xiaohongshu/note.js +76 -0
  146. package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
  147. package/dist/clis/xiaohongshu/note.test.js +136 -0
  148. package/dist/clis/xiaohongshu/search.js +9 -0
  149. package/dist/clis/xiaohongshu/search.test.js +10 -4
  150. package/dist/clis/youtube/search.js +57 -17
  151. package/dist/clis/zhihu/question.js +19 -17
  152. package/dist/clis/zhihu/question.test.d.ts +1 -0
  153. package/dist/clis/zhihu/question.test.js +54 -0
  154. package/dist/commanderAdapter.js +9 -0
  155. package/dist/commanderAdapter.test.js +25 -0
  156. package/dist/commands/daemon.d.ts +9 -0
  157. package/dist/commands/daemon.js +124 -0
  158. package/dist/commands/daemon.test.d.ts +1 -0
  159. package/dist/commands/daemon.test.js +185 -0
  160. package/dist/completion.js +3 -1
  161. package/dist/constants.d.ts +2 -0
  162. package/dist/constants.js +2 -0
  163. package/dist/daemon.d.ts +1 -1
  164. package/dist/daemon.js +25 -14
  165. package/dist/daemon.test.d.ts +1 -0
  166. package/dist/daemon.test.js +65 -0
  167. package/dist/discovery.d.ts +9 -0
  168. package/dist/discovery.js +47 -2
  169. package/dist/electron-apps.d.ts +29 -0
  170. package/dist/electron-apps.js +65 -0
  171. package/dist/electron-apps.test.d.ts +1 -0
  172. package/dist/electron-apps.test.js +43 -0
  173. package/dist/engine.test.js +41 -9
  174. package/dist/execution.js +20 -16
  175. package/dist/extension-manifest-regression.test.js +1 -0
  176. package/dist/idle-manager.d.ts +19 -0
  177. package/dist/idle-manager.js +54 -0
  178. package/dist/launcher.d.ts +36 -0
  179. package/dist/launcher.js +152 -0
  180. package/dist/launcher.test.d.ts +1 -0
  181. package/dist/launcher.test.js +57 -0
  182. package/dist/main.js +3 -3
  183. package/dist/registry.d.ts +1 -0
  184. package/dist/registry.js +31 -3
  185. package/dist/registry.test.js +13 -0
  186. package/dist/runtime.d.ts +5 -3
  187. package/dist/runtime.js +12 -5
  188. package/dist/serialization.d.ts +1 -0
  189. package/dist/serialization.js +3 -0
  190. package/dist/serialization.test.js +17 -1
  191. package/dist/tui.d.ts +7 -0
  192. package/dist/tui.js +52 -0
  193. package/dist/tui.test.d.ts +1 -0
  194. package/dist/tui.test.js +19 -0
  195. package/dist/weixin-download.test.js +14 -0
  196. package/docs/.vitepress/config.mts +1 -0
  197. package/docs/adapters/browser/notebooklm.md +69 -0
  198. package/docs/adapters/browser/xiaohongshu.md +19 -10
  199. package/docs/adapters/index.md +67 -66
  200. package/docs/guide/browser-bridge.md +12 -0
  201. package/docs/guide/troubleshooting.md +9 -4
  202. package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
  203. package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
  204. package/docs/zh/guide/browser-bridge.md +12 -0
  205. package/extension/dist/background.js +250 -11
  206. package/extension/manifest.json +2 -1
  207. package/extension/src/background.test.ts +202 -2
  208. package/extension/src/background.ts +175 -10
  209. package/extension/src/cdp.test.ts +75 -0
  210. package/extension/src/cdp.ts +89 -3
  211. package/extension/src/protocol.ts +7 -5
  212. package/package.json +1 -1
  213. package/src/browser/cdp.ts +24 -17
  214. package/src/browser/daemon-client.ts +7 -1
  215. package/src/browser/dom-helpers.test.ts +15 -1
  216. package/src/browser/dom-helpers.ts +1 -0
  217. package/src/browser/mcp.ts +18 -13
  218. package/src/browser/page.test.ts +58 -0
  219. package/src/browser/page.ts +18 -2
  220. package/src/browser/stealth.test.ts +153 -0
  221. package/src/browser/stealth.ts +198 -0
  222. package/src/browser.test.ts +1 -1
  223. package/src/build-manifest.test.ts +2 -0
  224. package/src/build-manifest.ts +6 -1
  225. package/src/cli.ts +21 -3
  226. package/src/clis/antigravity/SKILL.md +3 -12
  227. package/src/clis/antigravity/serve.ts +5 -10
  228. package/src/clis/bilibili/subtitle.test.ts +60 -0
  229. package/src/clis/bilibili/subtitle.ts +4 -0
  230. package/src/clis/chatwise/ask.ts +0 -2
  231. package/src/clis/chatwise/export.ts +0 -2
  232. package/src/clis/chatwise/history.ts +0 -2
  233. package/src/clis/chatwise/model.ts +0 -2
  234. package/src/clis/chatwise/new.ts +1 -2
  235. package/src/clis/chatwise/read.ts +0 -2
  236. package/src/clis/chatwise/screenshot.ts +1 -2
  237. package/src/clis/chatwise/send.ts +0 -2
  238. package/src/clis/chatwise/status.ts +1 -2
  239. package/src/clis/ctrip/search.test.ts +73 -0
  240. package/src/clis/ctrip/search.ts +97 -47
  241. package/src/clis/douyin/_shared/sts2.test.ts +31 -0
  242. package/src/clis/douyin/_shared/sts2.ts +11 -3
  243. package/src/clis/douyin/activities.test.ts +41 -1
  244. package/src/clis/douyin/activities.ts +12 -3
  245. package/src/clis/douyin/collections.test.ts +35 -2
  246. package/src/clis/douyin/collections.ts +1 -1
  247. package/src/clis/douyin/draft.test.ts +444 -2
  248. package/src/clis/douyin/draft.ts +382 -218
  249. package/src/clis/douyin/hashtag.test.ts +42 -2
  250. package/src/clis/douyin/hashtag.ts +11 -3
  251. package/src/clis/douyin/profile.test.ts +43 -1
  252. package/src/clis/douyin/profile.ts +9 -2
  253. package/src/clis/douyin/videos.test.ts +52 -2
  254. package/src/clis/douyin/videos.ts +49 -15
  255. package/src/clis/facebook/search.test.ts +70 -0
  256. package/src/clis/facebook/search.yaml +4 -3
  257. package/src/clis/instagram/download.test.ts +159 -0
  258. package/src/clis/instagram/download.ts +286 -0
  259. package/src/clis/notebooklm/bind-current.test.ts +43 -0
  260. package/src/clis/notebooklm/bind-current.ts +36 -0
  261. package/src/clis/notebooklm/binding.test.ts +53 -0
  262. package/src/clis/notebooklm/compat.test.ts +19 -0
  263. package/src/clis/notebooklm/current.ts +38 -0
  264. package/src/clis/notebooklm/get.ts +53 -0
  265. package/src/clis/notebooklm/history.test.ts +70 -0
  266. package/src/clis/notebooklm/history.ts +36 -0
  267. package/src/clis/notebooklm/list.ts +40 -0
  268. package/src/clis/notebooklm/note-list.test.ts +64 -0
  269. package/src/clis/notebooklm/note-list.ts +42 -0
  270. package/src/clis/notebooklm/notes-get.test.ts +88 -0
  271. package/src/clis/notebooklm/notes-get.ts +67 -0
  272. package/src/clis/notebooklm/rpc.test.ts +126 -0
  273. package/src/clis/notebooklm/rpc.ts +286 -0
  274. package/src/clis/notebooklm/shared.ts +98 -0
  275. package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
  276. package/src/clis/notebooklm/source-fulltext.ts +69 -0
  277. package/src/clis/notebooklm/source-get.test.ts +100 -0
  278. package/src/clis/notebooklm/source-get.ts +60 -0
  279. package/src/clis/notebooklm/source-guide.test.ts +121 -0
  280. package/src/clis/notebooklm/source-guide.ts +69 -0
  281. package/src/clis/notebooklm/source-list.ts +45 -0
  282. package/src/clis/notebooklm/status.ts +34 -0
  283. package/src/clis/notebooklm/summary.test.ts +94 -0
  284. package/src/clis/notebooklm/summary.ts +45 -0
  285. package/src/clis/notebooklm/utils.test.ts +446 -0
  286. package/src/clis/notebooklm/utils.ts +893 -0
  287. package/src/clis/substack/utils.test.ts +54 -0
  288. package/src/clis/substack/utils.ts +10 -2
  289. package/src/clis/v2ex/hot.yaml +4 -1
  290. package/src/clis/v2ex/latest.yaml +4 -1
  291. package/src/clis/v2ex/topic.yaml +6 -1
  292. package/src/clis/weixin/download.ts +95 -6
  293. package/src/clis/weread/book.ts +142 -2
  294. package/src/clis/weread/commands.test.ts +314 -154
  295. package/src/clis/weread/utils.ts +33 -4
  296. package/src/clis/xiaohongshu/comments.test.ts +85 -9
  297. package/src/clis/xiaohongshu/comments.ts +76 -17
  298. package/src/clis/xiaohongshu/download.test.ts +96 -0
  299. package/src/clis/xiaohongshu/download.ts +83 -22
  300. package/src/clis/xiaohongshu/note-helpers.ts +25 -0
  301. package/src/clis/xiaohongshu/note.test.ts +164 -0
  302. package/src/clis/xiaohongshu/note.ts +86 -0
  303. package/src/clis/xiaohongshu/search.test.ts +11 -4
  304. package/src/clis/xiaohongshu/search.ts +13 -0
  305. package/src/clis/youtube/search.ts +57 -17
  306. package/src/clis/zhihu/question.test.ts +71 -0
  307. package/src/clis/zhihu/question.ts +27 -15
  308. package/src/commanderAdapter.test.ts +30 -0
  309. package/src/commanderAdapter.ts +7 -0
  310. package/src/commands/daemon.test.ts +238 -0
  311. package/src/commands/daemon.ts +135 -0
  312. package/src/completion.ts +2 -1
  313. package/src/constants.ts +3 -0
  314. package/src/daemon.test.ts +88 -0
  315. package/src/daemon.ts +26 -14
  316. package/src/discovery.ts +52 -2
  317. package/src/electron-apps.test.ts +50 -0
  318. package/src/electron-apps.ts +89 -0
  319. package/src/engine.test.ts +45 -9
  320. package/src/execution.ts +24 -19
  321. package/src/extension-manifest-regression.test.ts +1 -0
  322. package/src/idle-manager.ts +60 -0
  323. package/src/launcher.test.ts +67 -0
  324. package/src/launcher.ts +185 -0
  325. package/src/main.ts +3 -2
  326. package/src/registry.test.ts +15 -0
  327. package/src/registry.ts +32 -3
  328. package/src/runtime.ts +13 -7
  329. package/src/serialization.test.ts +19 -1
  330. package/src/serialization.ts +2 -0
  331. package/src/tui.test.ts +23 -0
  332. package/src/tui.ts +65 -0
  333. package/src/weixin-download.test.ts +27 -0
  334. package/tests/e2e/browser-public-extended.test.ts +6 -2
  335. package/chatwise-opencli.ps1 +0 -82
  336. package/dist/clis/chatwise/shared.d.ts +0 -2
  337. package/dist/clis/chatwise/shared.js +0 -6
  338. package/src/clis/chatwise/shared.ts +0 -8
@@ -1,6 +1,106 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+
5
+ import { afterAll, describe, expect, it, vi } from 'vitest';
6
+
7
+ import { wrapForEval } from '../../browser/utils.js';
2
8
  import { getRegistry } from '../../registry.js';
3
- import './draft.js';
9
+ import type { IPage } from '../../types.js';
10
+ import { buildCoverCheckPanelTextJs } from './draft.js';
11
+
12
+ // ─── Shared test helpers ────────────────────────────────────────────
13
+
14
+ const tempDirs: string[] = [];
15
+
16
+ function createTempVideo(name = 'demo.mp4'): string {
17
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-douyin-draft-'));
18
+ tempDirs.push(dir);
19
+ const filePath = path.join(dir, name);
20
+ fs.writeFileSync(filePath, Buffer.from([0, 0, 0, 20, 102, 116, 121, 112]));
21
+ return filePath;
22
+ }
23
+
24
+ function createTempCover(videoPath: string, name = 'cover.jpg'): string {
25
+ const filePath = path.join(path.dirname(videoPath), name);
26
+ fs.writeFileSync(filePath, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
27
+ return filePath;
28
+ }
29
+
30
+ function getDraftCommand() {
31
+ const registry = getRegistry();
32
+ const cmd = [...registry.values()].find(c => c.site === 'douyin' && c.name === 'draft');
33
+ if (!cmd?.func) throw new Error('douyin draft command not registered');
34
+ return cmd as typeof cmd & { func: NonNullable<typeof cmd.func> };
35
+ }
36
+
37
+ afterAll(() => {
38
+ for (const dir of tempDirs) {
39
+ fs.rmSync(dir, { recursive: true, force: true });
40
+ }
41
+ });
42
+
43
+ type FakeNode = {
44
+ textContent: string;
45
+ parentElement: FakeNode | null;
46
+ querySelectorAll: (_selector: string) => FakeNode[];
47
+ };
48
+
49
+ function createFakeTree(text: string, children: FakeNode[] = []): FakeNode {
50
+ const node: FakeNode = {
51
+ textContent: text,
52
+ parentElement: null,
53
+ querySelectorAll: () => [],
54
+ };
55
+ node.querySelectorAll = () => {
56
+ const descendants: FakeNode[] = [];
57
+ for (const child of children) {
58
+ descendants.push(child, ...child.querySelectorAll('*'));
59
+ }
60
+ return descendants;
61
+ };
62
+ for (const child of children) {
63
+ child.parentElement = node;
64
+ }
65
+ return node;
66
+ }
67
+
68
+ function createPageMock(
69
+ evaluateResults: unknown[],
70
+ overrides: Partial<IPage> = {},
71
+ ): IPage {
72
+ const evaluate = vi.fn();
73
+ for (const result of evaluateResults) {
74
+ evaluate.mockResolvedValueOnce(result);
75
+ }
76
+
77
+ return {
78
+ goto: vi.fn().mockResolvedValue(undefined),
79
+ evaluate,
80
+ getCookies: vi.fn().mockResolvedValue([]),
81
+ snapshot: vi.fn().mockResolvedValue(undefined),
82
+ click: vi.fn().mockResolvedValue(undefined),
83
+ typeText: vi.fn().mockResolvedValue(undefined),
84
+ pressKey: vi.fn().mockResolvedValue(undefined),
85
+ scrollTo: vi.fn().mockResolvedValue(undefined),
86
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
87
+ wait: vi.fn().mockResolvedValue(undefined),
88
+ tabs: vi.fn().mockResolvedValue([]),
89
+ closeTab: vi.fn().mockResolvedValue(undefined),
90
+ newTab: vi.fn().mockResolvedValue(undefined),
91
+ selectTab: vi.fn().mockResolvedValue(undefined),
92
+ networkRequests: vi.fn().mockResolvedValue([]),
93
+ consoleMessages: vi.fn().mockResolvedValue([]),
94
+ scroll: vi.fn().mockResolvedValue(undefined),
95
+ autoScroll: vi.fn().mockResolvedValue(undefined),
96
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
97
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
98
+ waitForCapture: vi.fn().mockResolvedValue(undefined),
99
+ screenshot: vi.fn().mockResolvedValue(''),
100
+ setFileInput: vi.fn().mockResolvedValue(undefined),
101
+ ...overrides,
102
+ };
103
+ }
4
104
 
5
105
  describe('douyin draft registration', () => {
6
106
  it('registers the draft command', () => {
@@ -9,4 +109,346 @@ describe('douyin draft registration', () => {
9
109
  const cmd = values.find(c => c.site === 'douyin' && c.name === 'draft');
10
110
  expect(cmd).toBeDefined();
11
111
  });
112
+
113
+ it('extracts the higher quick-check panel instead of stopping at a header-only ancestor', () => {
114
+ const marker = createFakeTree('快速检测');
115
+ const state = createFakeTree('重新检测');
116
+ const header = createFakeTree('快速检测', [marker]);
117
+ const status = createFakeTree('重新检测', [state]);
118
+ const panel = createFakeTree('快速检测重新检测', [header, status]);
119
+ const body = createFakeTree('body', [panel]);
120
+
121
+ const g = globalThis as unknown as {
122
+ document?: { body: FakeNode; querySelectorAll: (_selector: string) => FakeNode[] };
123
+ };
124
+ const originalDocument = g.document;
125
+ g.document = {
126
+ body,
127
+ querySelectorAll: () => [marker, state],
128
+ };
129
+
130
+ try {
131
+ expect((eval(buildCoverCheckPanelTextJs()) as () => string)()).toBe('快速检测重新检测');
132
+ } finally {
133
+ g.document = originalDocument;
134
+ }
135
+ });
136
+
137
+ it('returns empty when only header text exists and no exact quick-check state node is present', () => {
138
+ const marker = createFakeTree('快速检测');
139
+ const note = createFakeTree('检测说明');
140
+ const header = createFakeTree('快速检测检测说明', [marker, note]);
141
+ const body = createFakeTree('body', [header]);
142
+
143
+ const g = globalThis as unknown as {
144
+ document?: { body: FakeNode; querySelectorAll: (_selector: string) => FakeNode[] };
145
+ };
146
+ const originalDocument = g.document;
147
+ g.document = {
148
+ body,
149
+ querySelectorAll: () => [marker, note],
150
+ };
151
+
152
+ try {
153
+ expect((eval(buildCoverCheckPanelTextJs()) as () => string)()).toBe('');
154
+ } finally {
155
+ g.document = originalDocument;
156
+ }
157
+ });
158
+
159
+ it('extracts the quick-check panel when busy state is rendered as a single 封面检测中 node', () => {
160
+ const marker = createFakeTree('快速检测');
161
+ const busy = createFakeTree('封面检测中');
162
+ const header = createFakeTree('快速检测', [marker]);
163
+ const status = createFakeTree('封面检测中', [busy]);
164
+ const panel = createFakeTree('快速检测封面检测中', [header, status]);
165
+ const body = createFakeTree('body', [panel]);
166
+
167
+ const g = globalThis as unknown as {
168
+ document?: { body: FakeNode; querySelectorAll: (_selector: string) => FakeNode[] };
169
+ };
170
+ const originalDocument = g.document;
171
+ g.document = {
172
+ body,
173
+ querySelectorAll: () => [marker, busy],
174
+ };
175
+
176
+ try {
177
+ expect((eval(buildCoverCheckPanelTextJs()) as () => string)()).toBe('快速检测封面检测中');
178
+ } finally {
179
+ g.document = originalDocument;
180
+ }
181
+ });
182
+
183
+ it('uploads through the official creator draft page and saves the draft session', async () => {
184
+ const cmd = getDraftCommand();
185
+ const videoPath = createTempVideo('demo.mp4');
186
+
187
+ const page = createPageMock(
188
+ [
189
+ undefined,
190
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
191
+ undefined,
192
+ true,
193
+ true,
194
+ true,
195
+ { ok: true, text: '暂存离开', creationId: 'creation-001' },
196
+ {
197
+ href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
198
+ bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
199
+ },
200
+ ],
201
+ );
202
+
203
+ const rows = await cmd.func(page, {
204
+ video: videoPath,
205
+ title: '最小修复验证',
206
+ caption: 'opencli draft e2e',
207
+ cover: '',
208
+ visibility: 'friends',
209
+ });
210
+
211
+ expect(page.goto).toHaveBeenCalledWith(
212
+ 'https://creator.douyin.com/creator-micro/content/upload',
213
+ );
214
+ expect(page.wait).toHaveBeenCalledWith({
215
+ selector: 'input[type="file"]',
216
+ timeout: 20,
217
+ });
218
+ expect(page.setFileInput).toHaveBeenCalledWith([videoPath], 'input[type="file"]');
219
+
220
+ const evaluateCalls = (page.evaluate as ReturnType<typeof vi.fn>).mock.calls.map(
221
+ (args: unknown[]) => String(args[0]),
222
+ );
223
+ expect(evaluateCalls.some((code: string) => code.includes('填写作品标题'))).toBe(true);
224
+ expect(evaluateCalls.some((code: string) => code.includes('好友可见'))).toBe(true);
225
+ expect(evaluateCalls.some((code: string) => code.includes('暂存离开'))).toBe(true);
226
+
227
+ expect(rows).toEqual([
228
+ {
229
+ status: '✅ 草稿已保存,可在创作中心继续编辑',
230
+ draft_id: 'creation-001',
231
+ },
232
+ ]);
233
+ });
234
+
235
+ it('waits for the composer when upload processing is slower than the first few polls', async () => {
236
+ const cmd = getDraftCommand();
237
+ const videoPath = createTempVideo('slow.mp4');
238
+
239
+ const page = createPageMock(
240
+ [
241
+ undefined,
242
+ { href: 'https://creator.douyin.com/creator-micro/content/upload', ready: false, bodyText: '上传中 42%' },
243
+ { href: 'https://creator.douyin.com/creator-micro/content/upload', ready: false, bodyText: '转码中' },
244
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
245
+ undefined,
246
+ true,
247
+ true,
248
+ { ok: true, text: '暂存离开', creationId: 'creation-slow' },
249
+ {
250
+ href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
251
+ bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
252
+ },
253
+ ],
254
+ );
255
+
256
+ const rows = await cmd.func(page, {
257
+ video: videoPath,
258
+ title: '慢上传验证',
259
+ caption: '',
260
+ cover: '',
261
+ visibility: 'public',
262
+ });
263
+
264
+ expect(rows).toEqual([
265
+ {
266
+ status: '✅ 草稿已保存,可在创作中心继续编辑',
267
+ draft_id: 'creation-slow',
268
+ },
269
+ ]);
270
+ expect(page.wait).toHaveBeenCalledWith({ time: 0.5 });
271
+ const shortWaitCalls = (page.wait as ReturnType<typeof vi.fn>).mock.calls.filter(
272
+ ([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }),
273
+ );
274
+ expect(shortWaitCalls).toHaveLength(2);
275
+ });
276
+
277
+ it('fails fast when the save action does not expose a draft creation id', async () => {
278
+ const cmd = getDraftCommand();
279
+ const videoPath = createTempVideo('missing-id.mp4');
280
+
281
+ const page = createPageMock(
282
+ [
283
+ undefined,
284
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
285
+ undefined,
286
+ true,
287
+ true,
288
+ { ok: true, text: '暂存离开', creationId: '' },
289
+ ],
290
+ );
291
+
292
+ await expect(
293
+ cmd.func(page, {
294
+ video: videoPath,
295
+ title: '缺失 creation id',
296
+ caption: '',
297
+ cover: '',
298
+ visibility: 'public',
299
+ }),
300
+ ).rejects.toThrow('点击草稿按钮失败: creation-id-missing');
301
+ });
302
+
303
+ it('uses the dedicated cover upload input when a custom cover is provided', async () => {
304
+ const cmd = getDraftCommand();
305
+ const videoPath = createTempVideo('demo.mp4');
306
+ const coverPath = createTempCover(videoPath);
307
+
308
+ const page = createPageMock(
309
+ [
310
+ undefined,
311
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
312
+ undefined,
313
+ 1,
314
+ { ok: false, reason: 'cover-input-pending' },
315
+ { ok: true, selector: '[data-opencli-cover-input="1"]' },
316
+ '快速检测检测中',
317
+ '快速检测重新检测',
318
+ true,
319
+ true,
320
+ { ok: true, text: '暂存离开', creationId: 'creation-002' },
321
+ {
322
+ href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
323
+ bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
324
+ },
325
+ ],
326
+ );
327
+
328
+ const rows = await cmd.func(page, {
329
+ video: videoPath,
330
+ title: '封面上传验证',
331
+ caption: '',
332
+ cover: coverPath,
333
+ visibility: 'public',
334
+ });
335
+
336
+ expect(page.setFileInput).toHaveBeenNthCalledWith(1, [videoPath], 'input[type="file"]');
337
+ expect(page.setFileInput).toHaveBeenNthCalledWith(2, [coverPath], '[data-opencli-cover-input="1"]');
338
+ const shortWaitCalls = (page.wait as ReturnType<typeof vi.fn>).mock.calls.filter(
339
+ ([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }),
340
+ );
341
+ expect(shortWaitCalls).toHaveLength(2);
342
+
343
+ const evaluateCalls = (page.evaluate as ReturnType<typeof vi.fn>).mock.calls.map(
344
+ (args: unknown[]) => String(args[0]),
345
+ );
346
+ expect(evaluateCalls.some((code: string) => code.includes('上传新封面'))).toBe(true);
347
+ expect(evaluateCalls.some((code: string) => code.includes("text.includes('快速检测检测')"))).toBe(false);
348
+ expect(() => {
349
+ for (const code of evaluateCalls) {
350
+ new Function(wrapForEval(code));
351
+ }
352
+ }).not.toThrow();
353
+
354
+ expect(rows).toEqual([
355
+ {
356
+ status: '✅ 草稿已保存,可在创作中心继续编辑',
357
+ draft_id: 'creation-002',
358
+ },
359
+ ]);
360
+ });
361
+
362
+ it('waits for a late cover-section update before treating the custom cover as ready', async () => {
363
+ const cmd = getDraftCommand();
364
+ const videoPath = createTempVideo('cover-race.mp4');
365
+ const coverPath = createTempCover(videoPath, 'cover-race.jpg');
366
+
367
+ const page = createPageMock(
368
+ [
369
+ undefined,
370
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
371
+ undefined,
372
+ 1,
373
+ { ok: true, selector: '[data-opencli-cover-input="1"]' },
374
+ '快速检测重新检测',
375
+ '快速检测重新检测',
376
+ '快速检测重新检测',
377
+ '快速检测检测中',
378
+ '快速检测横/竖双封面缺失',
379
+ true,
380
+ true,
381
+ { ok: true, text: '暂存离开', creationId: 'creation-cover-race' },
382
+ {
383
+ href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
384
+ bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
385
+ },
386
+ ],
387
+ );
388
+
389
+ const rows = await cmd.func(page, {
390
+ video: videoPath,
391
+ title: '封面竞态验证',
392
+ caption: '',
393
+ cover: coverPath,
394
+ visibility: 'public',
395
+ });
396
+
397
+ expect(rows).toEqual([
398
+ {
399
+ status: '✅ 草稿已保存,可在创作中心继续编辑',
400
+ draft_id: 'creation-cover-race',
401
+ },
402
+ ]);
403
+ const shortWaitCalls = (page.wait as ReturnType<typeof vi.fn>).mock.calls.filter(
404
+ ([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }),
405
+ );
406
+ expect(shortWaitCalls).toHaveLength(4);
407
+ });
408
+
409
+ it('accepts the same ready label after cover busy state when the quick-check panel actually transitioned', async () => {
410
+ const cmd = getDraftCommand();
411
+ const videoPath = createTempVideo('cover-same-ready.mp4');
412
+ const coverPath = createTempCover(videoPath, 'cover-same-ready.jpg');
413
+
414
+ const page = createPageMock(
415
+ [
416
+ undefined,
417
+ { href: 'https://creator.douyin.com/creator-micro/content/post/video?enter_from=publish_page', ready: true, bodyText: '' },
418
+ undefined,
419
+ 1,
420
+ { ok: true, selector: '[data-opencli-cover-input="1"]' },
421
+ '快速检测重新检测',
422
+ '快速检测重新检测',
423
+ '快速检测检测中',
424
+ '快速检测重新检测',
425
+ true,
426
+ true,
427
+ { ok: true, text: '暂存离开', creationId: 'creation-cover-same-ready' },
428
+ {
429
+ href: 'https://creator.douyin.com/creator-micro/content/upload?enter_from=publish',
430
+ bodyText: '你还有上次未发布的视频,是否继续编辑?继续编辑放弃',
431
+ },
432
+ ],
433
+ );
434
+
435
+ const rows = await cmd.func(page, {
436
+ video: videoPath,
437
+ title: '封面同文案验证',
438
+ caption: '',
439
+ cover: coverPath,
440
+ visibility: 'public',
441
+ });
442
+
443
+ expect(rows).toEqual([
444
+ {
445
+ status: '✅ 草稿已保存,可在创作中心继续编辑',
446
+ draft_id: 'creation-cover-same-ready',
447
+ },
448
+ ]);
449
+ const shortWaitCalls = (page.wait as ReturnType<typeof vi.fn>).mock.calls.filter(
450
+ ([arg]) => JSON.stringify(arg) === JSON.stringify({ time: 0.5 }),
451
+ );
452
+ expect(shortWaitCalls).toHaveLength(3);
453
+ });
12
454
  });