step8-stop-cleanup.test.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. const assert = require('assert');
  2. const fs = require('fs');
  3. const source = fs.readFileSync('background.js', 'utf8');
  4. function extractFunction(name) {
  5. const markers = [`async function ${name}(`, `function ${name}(`];
  6. const start = markers
  7. .map(marker => source.indexOf(marker))
  8. .find(index => index >= 0);
  9. if (start < 0) {
  10. throw new Error(`missing function ${name}`);
  11. }
  12. let parenDepth = 0;
  13. let signatureEnded = false;
  14. let braceStart = -1;
  15. for (let i = start; i < source.length; i++) {
  16. const ch = source[i];
  17. if (ch === '(') {
  18. parenDepth += 1;
  19. } else if (ch === ')') {
  20. parenDepth -= 1;
  21. if (parenDepth === 0) {
  22. signatureEnded = true;
  23. }
  24. } else if (ch === '{' && signatureEnded) {
  25. braceStart = i;
  26. break;
  27. }
  28. }
  29. if (braceStart < 0) {
  30. throw new Error(`missing body for function ${name}`);
  31. }
  32. let depth = 0;
  33. let end = braceStart;
  34. for (; end < source.length; end++) {
  35. const ch = source[end];
  36. if (ch === '{') depth += 1;
  37. if (ch === '}') {
  38. depth -= 1;
  39. if (depth === 0) {
  40. end += 1;
  41. break;
  42. }
  43. }
  44. }
  45. return source.slice(start, end);
  46. }
  47. const bundle = [
  48. extractFunction('throwIfStopped'),
  49. extractFunction('cleanupStep8NavigationListeners'),
  50. extractFunction('rejectPendingStep8'),
  51. extractFunction('throwIfStep8SettledOrStopped'),
  52. extractFunction('requestStop'),
  53. extractFunction('executeStep8'),
  54. ].join('\n');
  55. const api = new Function(`
  56. let stopRequested = false;
  57. const STOP_ERROR_MESSAGE = '流程已被用户停止。';
  58. let webNavListener = null;
  59. let webNavCommittedListener = null;
  60. let step8TabUpdatedListener = null;
  61. let step8PendingReject = null;
  62. let autoRunActive = true;
  63. let autoRunCurrentRun = 2;
  64. let autoRunTotalRuns = 3;
  65. let autoRunAttemptRun = 4;
  66. const AUTO_RUN_TIMER_KIND_SCHEDULED_START = 'scheduled_start';
  67. const added = {
  68. beforeNavigate: 0,
  69. committed: 0,
  70. tabUpdated: 0,
  71. };
  72. const removed = {
  73. beforeNavigate: 0,
  74. committed: 0,
  75. tabUpdated: 0,
  76. };
  77. const sentMessages = [];
  78. let clickCount = 0;
  79. let resolveTabId = null;
  80. const chrome = {
  81. webNavigation: {
  82. onBeforeNavigate: {
  83. addListener(listener) {
  84. added.beforeNavigate += 1;
  85. },
  86. removeListener(listener) {
  87. removed.beforeNavigate += 1;
  88. },
  89. },
  90. onCommitted: {
  91. addListener(listener) {
  92. added.committed += 1;
  93. },
  94. removeListener(listener) {
  95. removed.committed += 1;
  96. },
  97. },
  98. },
  99. tabs: {
  100. onUpdated: {
  101. addListener(listener) {
  102. added.tabUpdated += 1;
  103. },
  104. removeListener(listener) {
  105. removed.tabUpdated += 1;
  106. },
  107. },
  108. async update() {},
  109. },
  110. };
  111. const stepWaiters = new Map();
  112. let resumeWaiter = null;
  113. function cancelPendingCommands() {}
  114. async function addLog() {}
  115. async function broadcastStopToContentScripts() {}
  116. async function markRunningStepsStopped() {}
  117. async function broadcastAutoRunStatus() {}
  118. async function getState() {
  119. return { autoRunning: false };
  120. }
  121. function getPendingAutoRunTimerPlan() {
  122. return null;
  123. }
  124. function isAutoRunScheduledState() {
  125. return false;
  126. }
  127. function getStep8CallbackUrlFromNavigation() { return ''; }
  128. function getStep8CallbackUrlFromTabUpdate() { return ''; }
  129. async function completeStepFromBackground() {}
  130. async function getTabId() {
  131. return await new Promise((resolve) => {
  132. resolveTabId = resolve;
  133. });
  134. }
  135. async function reuseOrCreateTab() {
  136. return 999;
  137. }
  138. async function isTabAlive() {
  139. return true;
  140. }
  141. async function sendToContentScript(source, message) {
  142. sentMessages.push({ source, type: message.type });
  143. return { rect: { centerX: 10, centerY: 20 } };
  144. }
  145. async function clickWithDebugger() {
  146. clickCount += 1;
  147. }
  148. ${bundle}
  149. return {
  150. executeStep8,
  151. requestStop,
  152. resolveTabId(tabId) {
  153. if (!resolveTabId) {
  154. throw new Error('resolveTabId is not ready');
  155. }
  156. resolveTabId(tabId);
  157. },
  158. snapshot() {
  159. return {
  160. stopRequested,
  161. webNavListener,
  162. webNavCommittedListener,
  163. step8TabUpdatedListener,
  164. step8PendingReject,
  165. added,
  166. removed,
  167. sentMessages,
  168. clickCount,
  169. autoRunActive,
  170. };
  171. },
  172. };
  173. `)();
  174. (async () => {
  175. const step8Promise = api.executeStep8({ oauthUrl: 'https://example.com/oauth' });
  176. const settledStep8Promise = step8Promise.catch((err) => err);
  177. await new Promise((resolve) => setImmediate(resolve));
  178. await api.requestStop();
  179. await new Promise((resolve) => setImmediate(resolve));
  180. api.resolveTabId(123);
  181. await new Promise((resolve) => setImmediate(resolve));
  182. await new Promise((resolve) => setImmediate(resolve));
  183. const error = await settledStep8Promise;
  184. const state = api.snapshot();
  185. assert.strictEqual(error?.message, '流程已被用户停止。', 'Stop 后 Step 8 promise 应被拒绝为停止错误');
  186. assert.deepStrictEqual(
  187. state.added,
  188. { beforeNavigate: 0, committed: 0, tabUpdated: 0 },
  189. 'Stop 先发生时,不应再注册 Step 8 监听'
  190. );
  191. assert.strictEqual(state.sentMessages.length, 0, 'Stop 后不应再发送 STEP8_FIND_AND_CLICK 命令');
  192. assert.strictEqual(state.clickCount, 0, 'Stop 后不应再触发 debugger 点击');
  193. assert.strictEqual(state.webNavListener, null, 'Stop 后 onBeforeNavigate 引用应为空');
  194. assert.strictEqual(state.webNavCommittedListener, null, 'Stop 后 onCommitted 引用应为空');
  195. assert.strictEqual(state.step8TabUpdatedListener, null, 'Stop 后 tabs.onUpdated 引用应为空');
  196. assert.strictEqual(state.step8PendingReject, null, 'Stop 后不应保留 Step 8 挂起 reject');
  197. console.log('step8 stop cleanup tests passed');
  198. })().catch((error) => {
  199. console.error(error);
  200. process.exit(1);
  201. });