feat: 增加重试页面恢复逻辑,优化点击后等待时间
This commit is contained in:
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 中文文案
|
||||||
|
- 日志文案
|
||||||
|
- 报错文案
|
||||||
|
- 中文注释
|
||||||
|
|
||||||
这是硬要求,不是建议。
|
这是硬要求,不是建议。
|
||||||
|
|||||||
Reference in New Issue
Block a user