Ver código fonte

feat: 增加重试页面恢复逻辑,优化点击后等待时间

QLHazyCoder 4 meses atrás
pai
commit
78d6e94213

+ 43 - 2
content/auth-page-recovery.js

@@ -80,6 +80,36 @@
       };
     }
 
+    async function waitForRetryPageRecoveryAfterClick(options = {}) {
+      const {
+        pathPatterns = [],
+        pollIntervalMs = 250,
+        settleAfterClickMs = 3000,
+      } = options;
+      const startedAt = Date.now();
+
+      while (Date.now() - startedAt < settleAfterClickMs) {
+        if (typeof throwIfStopped === 'function') {
+          throwIfStopped();
+        }
+
+        const retryState = getAuthTimeoutErrorPageState({ pathPatterns });
+        if (!retryState) {
+          return {
+            recovered: true,
+            elapsedMs: Date.now() - startedAt,
+          };
+        }
+
+        await sleep(pollIntervalMs);
+      }
+
+      return {
+        recovered: false,
+        elapsedMs: Date.now() - startedAt,
+      };
+    }
+
     async function recoverAuthRetryPage(options = {}) {
       const {
         logLabel = '',
@@ -87,7 +117,7 @@
         pollIntervalMs = 250,
         step = null,
         timeoutMs = 12000,
-        waitAfterClickMs = 1200,
+        waitAfterClickMs = 3000,
       } = options;
       const start = Date.now();
       let clickCount = 0;
@@ -116,7 +146,18 @@
             await humanPause(300, 800);
           }
           simulateClick(retryState.retryButton);
-          await sleep(waitAfterClickMs);
+          const recoveryResult = await waitForRetryPageRecoveryAfterClick({
+            pathPatterns,
+            pollIntervalMs,
+            settleAfterClickMs: waitAfterClickMs,
+          });
+          if (recoveryResult.recovered) {
+            return {
+              recovered: true,
+              clickCount,
+              url: location.href,
+            };
+          }
           continue;
         }
 

+ 13 - 2
content/signup-page.js

@@ -955,7 +955,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
     logLabel = '',
     step = null,
     timeoutMs = 12000,
-    waitAfterClickMs = 1200,
+    waitAfterClickMs = 3000,
   } = payload;
   const pathPatterns = getAuthRetryPathPatternsForFlow(flow);
   if (authPageRecovery?.recoverAuthRetryPage) {
@@ -986,7 +986,18 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
       log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn');
       await humanPause(300, 800);
       simulateClick(retryState.retryButton);
-      await sleep(waitAfterClickMs);
+      const settleStart = Date.now();
+      while (Date.now() - settleStart < waitAfterClickMs) {
+        throwIfStopped();
+        if (!getCurrentAuthRetryPageState(flow)) {
+          return {
+            recovered: true,
+            clickCount,
+            url: location.href,
+          };
+        }
+        await sleep(250);
+      }
       continue;
     }
 

+ 42 - 1
tests/auth-page-recovery.test.js

@@ -44,10 +44,19 @@ function createRecoveryApi(state) {
     log: () => {},
     simulateClick: () => {
       state.clickCount += 1;
+      if (typeof state.onClick === 'function') {
+        state.onClick(state);
+        return;
+      }
       state.retryVisible = false;
       state.pageText = 'Recovered login form';
     },
-    sleep: async () => {},
+    sleep: async (ms = 0) => {
+      await new Promise((resolve) => setTimeout(resolve, Math.max(1, Math.min(5, ms))));
+      if (typeof state.onSleep === 'function') {
+        state.onSleep(state);
+      }
+    },
     throwIfStopped: () => {},
     titlePattern: /something went wrong/i,
   });
@@ -94,3 +103,35 @@ test('auth page recovery clicks retry and waits until page recovers', async () =
   assert.equal(state.clickCount, 1);
   assert.equal(state.retryVisible, false);
 });
+
+test('auth page recovery can click retry twice before page recovers', async () => {
+  const state = {
+    clickCount: 0,
+    pageText: 'Something went wrong. Operation timed out.',
+    retryVisible: true,
+    onClick(currentState) {
+      if (currentState.clickCount >= 2) {
+        currentState.retryVisible = false;
+        currentState.pageText = 'Recovered login form';
+      }
+    },
+  };
+  const api = createRecoveryApi(state);
+
+  const result = await api.recoverAuthRetryPage({
+    logLabel: '步骤 8:检测到重试页,正在点击“重试”恢复',
+    pathPatterns: [/\/log-in(?:[/?#]|$)/i],
+    step: 8,
+    timeoutMs: 200,
+    waitAfterClickMs: 10,
+    pollIntervalMs: 1,
+  });
+
+  assert.deepStrictEqual(result, {
+    recovered: true,
+    clickCount: 2,
+    url: 'https://auth.openai.com/log-in',
+  });
+  assert.equal(state.clickCount, 2);
+  assert.equal(state.retryVisible, false);
+});

+ 34 - 15
项目开发规范(AI协作).md

@@ -1,4 +1,4 @@
-# 项目开发规范(AI协作)
+# 项目开发规范(AI协作)
 
 本文档是面向 AI 与开发者的项目开发规范。
 
@@ -12,7 +12,8 @@
 
 - 目标是“让项目更清晰、更可维护、更可测试”,不是单纯把代码拆碎。
 - 重构优先考虑稳定性、职责边界与可理解性。
-- 任何新增功能都必须沿现有分层接入,禁止重新堆回巨石文件。
+- 任何新增功能都必须沿现有分层接入,禁止重新堆回大文件。
+- 乱码问题视为阻塞问题,不是“后面再顺手修”的小问题。
 
 ## 1. 架构原则
 
@@ -31,13 +32,13 @@
 - 步骤顺序统一由:
   - [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
   - [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js)
- 共同管理。
+  共同管理。
 
 ### 1.3 前后端步骤定义共享原则
 
-- 任何步骤标题、顺序、key 变更,必须优先改 [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
-- 不允许只改 sidepanel 文案而不改共享定义
-- 不允许只改 registry 而不改共享定义
+- 任何步骤标题、顺序、key 变更,必须优先改 [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
+- 不允许只改 sidepanel 文案而不改共享定义
+- 不允许只改 registry 而不改共享定义
 
 ## 2. 模块边界规则
 
@@ -49,7 +50,7 @@
 - 某个单独步骤文件
 - 某个单独 manager 文件
 
-### 2.2 不应继续膨胀的文件
+### 2.2 不应继续膨胀的文件
 
 - [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js)
 - [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js)
@@ -77,7 +78,7 @@
 1. 是否有纯工具模块
 2. 是否需要 background provider 调度逻辑
 3. 是否需要 sidepanel 配置项
-4. 是否需要 Step 4 / 7 验证码链路接入
+4. 是否需要 Step 4 / 8 验证码链路接入
 5. 是否需要成功收尾逻辑
 6. 是否需要 README 与完整链路文档更新
 
@@ -105,7 +106,7 @@
 3. 导入导出
 4. state restore
 5. sidepanel UI
-6. 是否挂在正确的职责域
+6. 是否挂在正确的职责域
 7. 文档
 
 ## 4. 测试规范
@@ -116,7 +117,7 @@
 - 优先测试:
   - 模块是否接入
   - 核心纯函数是否仍可验证
-  - 回退/停止/异常传播是否仍正确
+  - 回退、停止、异常传播是否仍正确
 
 ### 4.2 不允许的做法
 
@@ -145,11 +146,11 @@ npm test
 
 ### 5.1 必须更新文档的场景
 
-- 文件新增/删除/重命名  
+- 文件新增、删除、重命名后
   更新 [项目文件结构说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目文件结构说明.md)
-- 功能链路变化  
+- 功能链路变化
   更新 [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md)
-- 开发流程、边界、约束变化  
+- 开发流程、边界、约束变化
   更新当前文件
 
 ### 5.2 文档更新要求
@@ -159,6 +160,14 @@ npm test
 - 不能让结构文档漏文件
 - 不能让链路文档落后于真实实现
 
+### 5.3 乱码要求
+
+- 所有中文文档、中文注释、中文日志、sidepanel 文案、错误提示文案都必须避免乱码。
+- 修改任何包含中文的文件时,必须把“乱码检查”视为与“功能是否正确”同级的必做项。
+- 如果某次修改引入了可见乱码,则该次开发视为未完成,不能提交为最终结果。
+- 不允许把“当前终端显示有点乱,但文件也许没问题”当作默认成立;必须做显式检查。
+- 如果文件历史上曾出现过编码问题,修改后必须再次审查该文件整体,而不是只看改动片段。
+
 ## 6. 命名规范
 
 ### 6.1 文件命名
@@ -177,11 +186,12 @@ npm test
 - 优先复用现有模块,不重复发明一套新流程
 - 共享逻辑先提公共层,再让步骤层调用
 - 代码新增后应尽量减少主文件体积,而不是只做“形式拆分”
-- 观、留档、日志、导出这类横切能力必须优先挂在独立配置域下,不能借某个 provider 的业务模式开关隐式控制
+- 观、留档、日志、导出这类横切能力必须优先挂在独立配置域下,不能借某个 provider 的业务开关隐式控制
 - 保留少量兼容型薄包装是允许的,但必须有明确目的:
   - 运行时装配
   - 测试迁移过渡
 - 如果某个薄包装已经没有存在意义,应在后续重构中清掉
+- 涉及中文内容的文件必须保持稳定编码,修改后要主动检查是否出现乱码、错码、异常替换字符。
 
 ## 8. AI 开发时的自检清单
 
@@ -193,7 +203,8 @@ npm test
 4. 我有没有补或迁移测试?
 5. 我有没有更新三份根目录文档?
 6. 我新增或修改的文件是否有可见乱码?
-7. 如果改动影响 Gmail / 2925 别名邮箱逻辑,我有没有同步检查 `managed-alias-utils.js`、sidepanel 接线、background 调度、auto-run reset 和回归测试?
+7. 我有没有逐个检查本次改动涉及的中文文案、日志、注释、文档没有乱码?
+8. 如果改动影响 Gmail / 2925 别名邮箱逻辑,我有没有同步检查 `managed-alias-utils.js`、sidepanel 接线、background 调度、auto-run reset 和回归测试?
 
 ## 9. 完成标准
 
@@ -204,6 +215,7 @@ npm test
 - 全量测试通过
 - 三份根目录文档已同步
 - 没有可见乱码
+- 已对本次修改涉及的文件做过乱码审查
 
 ## 10. 特别要求
 
@@ -214,4 +226,11 @@ npm test
   - [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md)
   - [项目开发规范(AI协作).md](c:/Users/projectf/Downloads/codex注册扩展/项目开发规范(AI协作).md)
 
+- 每次开发结束前,必须审查本次修改文件与关键运行文案没有乱码:
+  - 文档正文
+  - sidepanel 中文文案
+  - 日志文案
+  - 报错文案
+  - 中文注释
+
 这是硬要求,不是建议。