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

This commit is contained in:
QLHazyCoder
2026-04-17 16:09:56 +08:00
parent cbe9b1fabf
commit 78d6e94213
4 changed files with 132 additions and 20 deletions
+43 -2
View File
@@ -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 = {}) { async function recoverAuthRetryPage(options = {}) {
const { const {
logLabel = '', logLabel = '',
@@ -87,7 +117,7 @@
pollIntervalMs = 250, pollIntervalMs = 250,
step = null, step = null,
timeoutMs = 12000, timeoutMs = 12000,
waitAfterClickMs = 1200, waitAfterClickMs = 3000,
} = options; } = options;
const start = Date.now(); const start = Date.now();
let clickCount = 0; let clickCount = 0;
@@ -116,7 +146,18 @@
await humanPause(300, 800); await humanPause(300, 800);
} }
simulateClick(retryState.retryButton); 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; continue;
} }
+13 -2
View File
@@ -955,7 +955,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
logLabel = '', logLabel = '',
step = null, step = null,
timeoutMs = 12000, timeoutMs = 12000,
waitAfterClickMs = 1200, waitAfterClickMs = 3000,
} = payload; } = payload;
const pathPatterns = getAuthRetryPathPatternsForFlow(flow); const pathPatterns = getAuthRetryPathPatternsForFlow(flow);
if (authPageRecovery?.recoverAuthRetryPage) { if (authPageRecovery?.recoverAuthRetryPage) {
@@ -986,7 +986,18 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn'); log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn');
await humanPause(300, 800); await humanPause(300, 800);
simulateClick(retryState.retryButton); 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; continue;
} }
+42 -1
View File
@@ -44,10 +44,19 @@ function createRecoveryApi(state) {
log: () => {}, log: () => {},
simulateClick: () => { simulateClick: () => {
state.clickCount += 1; state.clickCount += 1;
if (typeof state.onClick === 'function') {
state.onClick(state);
return;
}
state.retryVisible = false; state.retryVisible = false;
state.pageText = 'Recovered login form'; 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: () => {}, throwIfStopped: () => {},
titlePattern: /something went wrong/i, 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.clickCount, 1);
assert.equal(state.retryVisible, false); 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
View File
@@ -1,4 +1,4 @@
# 项目开发规范(AI协作) # 项目开发规范(AI协作)
本文档是面向 AI 与开发者的项目开发规范。 本文档是面向 AI 与开发者的项目开发规范。
@@ -12,7 +12,8 @@
- 目标是“让项目更清晰、更可维护、更可测试”,不是单纯把代码拆碎。 - 目标是“让项目更清晰、更可维护、更可测试”,不是单纯把代码拆碎。
- 重构优先考虑稳定性、职责边界与可理解性。 - 重构优先考虑稳定性、职责边界与可理解性。
- 任何新增功能都必须沿现有分层接入,禁止重新堆回巨石文件。 - 任何新增功能都必须沿现有分层接入,禁止重新堆回文件。
- 乱码问题视为阻塞问题,不是“后面再顺手修”的小问题。
## 1. 架构原则 ## 1. 架构原则
@@ -31,13 +32,13 @@
- 步骤顺序统一由: - 步骤顺序统一由:
- [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) - [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) - [background/steps/registry.js](c:/Users/projectf/Downloads/codex注册扩展/background/steps/registry.js)
共同管理。 共同管理。
### 1.3 前后端步骤定义共享原则 ### 1.3 前后端步骤定义共享原则
- 任何步骤标题、顺序、key 变更,必须优先改 [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js) - 任何步骤标题、顺序、key 变更,必须优先改 [data/step-definitions.js](c:/Users/projectf/Downloads/codex注册扩展/data/step-definitions.js)
- 不允许只改 sidepanel 文案而不改共享定义 - 不允许只改 sidepanel 文案而不改共享定义
- 不允许只改 registry 而不改共享定义 - 不允许只改 registry 而不改共享定义
## 2. 模块边界规则 ## 2. 模块边界规则
@@ -49,7 +50,7 @@
- 某个单独步骤文件 - 某个单独步骤文件
- 某个单独 manager 文件 - 某个单独 manager 文件
### 2.2 不应继续膨胀的文件 ### 2.2 不应继续膨胀的文件
- [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js) - [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js)
- [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js) - [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js)
@@ -77,7 +78,7 @@
1. 是否有纯工具模块 1. 是否有纯工具模块
2. 是否需要 background provider 调度逻辑 2. 是否需要 background provider 调度逻辑
3. 是否需要 sidepanel 配置项 3. 是否需要 sidepanel 配置项
4. 是否需要 Step 4 / 7 验证码链路接入 4. 是否需要 Step 4 / 8 验证码链路接入
5. 是否需要成功收尾逻辑 5. 是否需要成功收尾逻辑
6. 是否需要 README 与完整链路文档更新 6. 是否需要 README 与完整链路文档更新
@@ -105,7 +106,7 @@
3. 导入导出 3. 导入导出
4. state restore 4. state restore
5. sidepanel UI 5. sidepanel UI
6. 是否挂在正确的职责域 6. 是否挂在正确的职责域
7. 文档 7. 文档
## 4. 测试规范 ## 4. 测试规范
@@ -116,7 +117,7 @@
- 优先测试: - 优先测试:
- 模块是否接入 - 模块是否接入
- 核心纯函数是否仍可验证 - 核心纯函数是否仍可验证
- 回退/停止/异常传播是否仍正确 - 回退停止异常传播是否仍正确
### 4.2 不允许的做法 ### 4.2 不允许的做法
@@ -145,11 +146,11 @@ npm test
### 5.1 必须更新文档的场景 ### 5.1 必须更新文档的场景
- 文件新增/删除/重命名 - 文件新增删除重命名
更新 [项目文件结构说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目文件结构说明.md) 更新 [项目文件结构说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目文件结构说明.md)
- 功能链路变化 - 功能链路变化
更新 [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md) 更新 [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md)
- 开发流程、边界、约束变化 - 开发流程、边界、约束变化
更新当前文件 更新当前文件
### 5.2 文档更新要求 ### 5.2 文档更新要求
@@ -159,6 +160,14 @@ npm test
- 不能让结构文档漏文件 - 不能让结构文档漏文件
- 不能让链路文档落后于真实实现 - 不能让链路文档落后于真实实现
### 5.3 乱码要求
- 所有中文文档、中文注释、中文日志、sidepanel 文案、错误提示文案都必须避免乱码。
- 修改任何包含中文的文件时,必须把“乱码检查”视为与“功能是否正确”同级的必做项。
- 如果某次修改引入了可见乱码,则该次开发视为未完成,不能提交为最终结果。
- 不允许把“当前终端显示有点乱,但文件也许没问题”当作默认成立;必须做显式检查。
- 如果文件历史上曾出现过编码问题,修改后必须再次审查该文件整体,而不是只看改动片段。
## 6. 命名规范 ## 6. 命名规范
### 6.1 文件命名 ### 6.1 文件命名
@@ -177,11 +186,12 @@ npm test
- 优先复用现有模块,不重复发明一套新流程 - 优先复用现有模块,不重复发明一套新流程
- 共享逻辑先提公共层,再让步骤层调用 - 共享逻辑先提公共层,再让步骤层调用
- 代码新增后应尽量减少主文件体积,而不是只做“形式拆分” - 代码新增后应尽量减少主文件体积,而不是只做“形式拆分”
-、留档、日志、导出这类横切能力必须优先挂在独立配置域下,不能借某个 provider 的业务模式开关隐式控制 -、留档、日志、导出这类横切能力必须优先挂在独立配置域下,不能借某个 provider 的业务开关隐式控制
- 保留少量兼容型薄包装是允许的,但必须有明确目的: - 保留少量兼容型薄包装是允许的,但必须有明确目的:
- 运行时装配 - 运行时装配
- 测试迁移过渡 - 测试迁移过渡
- 如果某个薄包装已经没有存在意义,应在后续重构中清掉 - 如果某个薄包装已经没有存在意义,应在后续重构中清掉
- 涉及中文内容的文件必须保持稳定编码,修改后要主动检查是否出现乱码、错码、异常替换字符。
## 8. AI 开发时的自检清单 ## 8. AI 开发时的自检清单
@@ -193,7 +203,8 @@ npm test
4. 我有没有补或迁移测试? 4. 我有没有补或迁移测试?
5. 我有没有更新三份根目录文档? 5. 我有没有更新三份根目录文档?
6. 我新增或修改的文件是否有可见乱码? 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. 完成标准 ## 9. 完成标准
@@ -204,6 +215,7 @@ npm test
- 全量测试通过 - 全量测试通过
- 三份根目录文档已同步 - 三份根目录文档已同步
- 没有可见乱码 - 没有可见乱码
- 已对本次修改涉及的文件做过乱码审查
## 10. 特别要求 ## 10. 特别要求
@@ -214,4 +226,11 @@ npm test
- [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md) - [项目完整链路说明.md](c:/Users/projectf/Downloads/codex注册扩展/项目完整链路说明.md)
- [项目开发规范(AI协作).md](c:/Users/projectf/Downloads/codex注册扩展/项目开发规范(AI协作).md) - [项目开发规范(AI协作).md](c:/Users/projectf/Downloads/codex注册扩展/项目开发规范(AI协作).md)
- 每次开发结束前,必须审查本次修改文件与关键运行文案没有乱码:
- 文档正文
- sidepanel 中文文案
- 日志文案
- 报错文案
- 中文注释
这是硬要求,不是建议。 这是硬要求,不是建议。