feat: Enhance Step 9 diagnostics and error handling

- Refactor getStatusBadgeEntries to use createStep9Entry for better encapsulation.
- Introduce error visual signals detection in createStep9Entry.
- Add functions to identify OAuth callback timeout failures and step 9 failure texts.
- Update buildStep9StatusDiagnostics to include error visual summaries and handle conflicts between success and failure states.
- Implement new tests for step 9 diagnostics, ensuring correct behavior with success badges and error banners.
- Remove outdated tests related to step 5 onboarding and redirect race conditions.
- Add new tests for auto-run behavior in step 6 restart scenarios.
This commit is contained in:
QLHazyCoder
2026-04-17 04:33:14 +08:00
parent 1a5f569d16
commit c745405f90
17 changed files with 772 additions and 691 deletions
+100 -107
View File
@@ -3403,6 +3403,10 @@ async function waitForTabUrlMatch(tabId, matcher, options = {}) {
return tabRuntime.waitForTabUrlMatch(tabId, matcher, options);
}
async function waitForTabComplete(tabId, options = {}) {
return tabRuntime.waitForTabComplete(tabId, options);
}
async function ensureContentScriptReadyOnTab(source, tabId, options = {}) {
return tabRuntime.ensureContentScriptReadyOnTab(source, tabId, options);
}
@@ -3631,7 +3635,7 @@ function isStep9RecoverableAuthError(error) {
function isLegacyStep9RecoverableAuthError(error) {
const message = String(typeof error === 'string' ? error : error?.message || '');
return /STEP9_OAUTH_TIMEOUT::|认证失败:\s*Timeout waiting for OAuth callback/i.test(message);
return /STEP9_OAUTH_TIMEOUT::|认证失败:\s*(?:Timeout waiting for OAuth callback|timeout of \d+ms exceeded)/i.test(message);
}
function isStepDoneStatus(status) {
@@ -4708,6 +4712,17 @@ async function executeStepAndWait(step, delayAfter = 2000) {
await executeStep(step);
}
if (step === 5) {
const signupTabId = await getTabId('signup-page');
if (signupTabId) {
await addLog('自动运行:步骤 5 已收到完成信号,正在等待当前页面完成加载...', 'info');
await waitForTabComplete(signupTabId, {
timeoutMs: 15000,
retryDelayMs: 300,
});
}
}
// Extra delay for page transitions / DOM updates
if (delayAfter > 0) {
await sleepWithStop(delayAfter + Math.floor(Math.random() * 1200));
@@ -4791,7 +4806,7 @@ const AUTO_STEP_DELAYS = {
2: 2000,
3: 3000,
4: 2000,
5: 3000,
5: 0,
6: 3000,
7: 2000,
8: 2000,
@@ -4996,8 +5011,7 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
async function runAutoSequenceFromStep(startStep, context = {}) {
const { targetRun, totalRuns, attemptRuns, continued = false } = context;
const maxStep9RestartAttempts = 5;
let step9RestartAttempts = 0;
let postStep6RestartCount = 0;
if (continued) {
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续当前进度,从步骤 ${startStep} 开始(第 ${attemptRuns} 次尝试)===`, 'info');
@@ -5048,27 +5062,31 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
}
step += 1;
} catch (err) {
const latestState = await getState();
const currentMail = getMailConfig(latestState);
const shouldRetryStep9 = step === 9
&& (
isLegacyStep9RecoverableAuthError(err)
|| (currentMail.provider === HOTMAIL_PROVIDER && isStep9RecoverableAuthError(err))
)
&& step9RestartAttempts < maxStep9RestartAttempts;
if (shouldRetryStep9) {
step9RestartAttempts += 1;
const restartDecision = await getPostStep6AutoRestartDecision(step, err);
if (restartDecision.shouldRestart) {
postStep6RestartCount += 1;
const authState = restartDecision.authState;
const authStateLabel = authState?.state ? getLoginAuthStateLabel(authState.state) : '未知页面';
const authStateSuffix = authState?.url
? `当前认证页:${authStateLabel}${authState.url}`
: authState?.state
? `当前认证页:${authStateLabel}`
: '未获取到认证页状态';
await addLog(
`步骤 9:检测到 CPA 认证失败,正在回到步骤 6 重新开始授权流程(${step9RestartAttempts}/${maxStep9RestartAttempts}...`,
`步骤 ${step}:检测到报错且当前未进入 add-phone,正在回到步骤 6 重新开始授权流程(${postStep6RestartCount} 次重开)。${authStateSuffix};原因:${restartDecision.errorMessage || '未知错误'}`,
'warn'
);
await invalidateDownstreamAfterStepRestart(6, {
logLabel: `步骤 9 认证失败后准备回到步骤 6 重试(${step9RestartAttempts}/${maxStep9RestartAttempts}`,
await invalidateDownstreamAfterStepRestart(5, {
logLabel: `步骤 ${step} 报错后准备回到步骤 6 重试(${postStep6RestartCount} 次重开`,
});
step = 6;
continue;
}
if (restartDecision.blockedByAddPhone) {
const addPhoneUrl = restartDecision.authState?.url || 'https://auth.openai.com/add-phone';
await addLog(`步骤 ${step}:检测到认证流程进入 add-phone(${addPhoneUrl}),停止自动回到步骤 6 重开。`, 'warn');
}
throw err;
}
}
@@ -5297,13 +5315,7 @@ const step5Executor = self.MultiPageBackgroundStep5?.createStep5Executor({
addLog,
generateRandomBirthday,
generateRandomName,
getState,
getTabId,
handleChatgptOnboardingSkip,
isRetryableContentScriptTransportError,
LOG_PREFIX,
sendToContentScript,
waitForStep5ChatgptRedirect,
});
const step6Executor = self.MultiPageBackgroundStep6?.createStep6Executor({
addLog,
@@ -5651,91 +5663,10 @@ async function executeStep4(state) {
// Step 5: Fill Name & Birthday (via signup-page.js)
// ============================================================
async function waitForStep5ChatgptRedirect(tabId, timeoutMs = 15000) {
if (!Number.isInteger(tabId)) {
return null;
}
const matchedTab = await waitForTabUrlMatch(tabId, (url) => /chatgpt\.com/i.test(url || ''), {
timeoutMs,
retryDelayMs: 300,
});
if (matchedTab) {
return matchedTab;
}
const currentTab = await chrome.tabs.get(tabId).catch(() => null);
if (currentTab && /chatgpt\.com/i.test(currentTab.url || '')) {
return currentTab;
}
return null;
}
async function executeStep5(state) {
return step5Executor.executeStep5(state);
}
async function handleChatgptOnboardingSkip(knownTabId) {
const signupTabId = knownTabId || await getTabId('signup-page');
if (!signupTabId) {
throw new Error('步骤 5:无法找到注册页标签页,无法处理 ChatGPT 引导页。');
}
// Wait for the tab to navigate to chatgpt.com (may already be there)
const start = Date.now();
const timeout = 15000;
let tabUrl = '';
while (Date.now() - start < timeout) {
throwIfStopped();
const tab = await chrome.tabs.get(signupTabId).catch(() => null);
if (tab && /chatgpt\.com/i.test(tab.url || '')) {
tabUrl = tab.url;
break;
}
await sleepWithStop(500);
}
if (!tabUrl) {
throw new Error('步骤 5:等待页面跳转到 ChatGPT 引导页超时。');
}
// Wait for page to finish loading
await sleepWithStop(2000);
// Inject content script on chatgpt.com
await ensureContentScriptReadyOnTab('signup-page', signupTabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
timeoutMs: 15000,
logMessage: '步骤 5:正在等待 ChatGPT 引导页内容脚本就绪...',
});
// Send CHATGPT_SKIP_ONBOARDING message
const result = await sendToContentScriptResilient('signup-page', {
type: 'CHATGPT_SKIP_ONBOARDING',
source: 'background',
payload: {},
}, {
timeoutMs: 60000,
retryDelayMs: 1000,
logMessage: '步骤 5:ChatGPT 引导页正在处理,等待跳过完成...',
});
if (result?.error) {
throw new Error(result.error);
}
if (result?.alreadyCompleted) {
await addLog('步骤 5:已进入 ChatGPT 页面,无需继续跳过引导,注册成功。', 'ok');
await completeStepFromBackground(5, { chatgptHome: true });
return;
}
await addLog('步骤 5:ChatGPT 引导页跳过完成,注册成功。', 'ok');
await completeStepFromBackground(5, { chatgptOnboarding: true });
}
// ============================================================
// Step 6 Cookie Cleanup
// ============================================================
@@ -5880,7 +5811,69 @@ function isStep6RecoverableResult(result) {
return result?.step6Outcome === 'recoverable';
}
async function getLoginAuthStateFromContent() {
function isAddPhoneAuthUrl(url) {
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)/i.test(String(url || '').trim());
}
function isAddPhoneAuthState(authState = {}) {
return authState?.state === 'add_phone_page'
|| Boolean(authState?.addPhonePage)
|| isAddPhoneAuthUrl(authState?.url);
}
async function getPostStep6AutoRestartDecision(step, error) {
const normalizedStep = Number(step);
const errorMessage = getErrorMessage(error);
if (!Number.isFinite(normalizedStep) || normalizedStep < 6 || normalizedStep > 9) {
return {
shouldRestart: false,
blockedByAddPhone: false,
errorMessage,
authState: null,
};
}
if (isAddPhoneAuthUrl(errorMessage)) {
return {
shouldRestart: false,
blockedByAddPhone: true,
errorMessage,
authState: null,
};
}
let authState = null;
try {
authState = await getLoginAuthStateFromContent({
logMessage: `步骤 ${normalizedStep}:正在确认当前认证页状态,以决定是否回到步骤 6 重开...`,
});
} catch (inspectError) {
console.warn(LOG_PREFIX, '[AutoRun] failed to inspect login auth state after post-step6 error', {
step: normalizedStep,
sourceError: errorMessage,
inspectError: inspectError?.message || inspectError,
});
}
if (isAddPhoneAuthState(authState)) {
return {
shouldRestart: false,
blockedByAddPhone: true,
errorMessage,
authState,
};
}
return {
shouldRestart: true,
blockedByAddPhone: false,
errorMessage,
authState,
};
}
async function getLoginAuthStateFromContent(options = {}) {
const { logMessage = '步骤 7:认证页正在切换,等待页面重新就绪后继续确认验证码页状态...' } = options;
const result = await sendToContentScriptResilient(
'signup-page',
{
@@ -5891,7 +5884,7 @@ async function getLoginAuthStateFromContent() {
{
timeoutMs: 15000,
retryDelayMs: 600,
logMessage: '步骤 7:认证页正在切换,等待页面重新就绪后继续确认验证码页状态...',
logMessage,
}
);