fix: restart signup phone mismatch without duplicate logs
This commit is contained in:
+89
-5
@@ -7299,7 +7299,12 @@ function isRestartCurrentAttemptError(error) {
|
||||
return loggingStatus.isRestartCurrentAttemptError(error);
|
||||
}
|
||||
const message = String(typeof error === 'string' ? error : error?.message || '');
|
||||
return /当前邮箱已存在,需要重新开始新一轮/.test(message);
|
||||
return /当前邮箱已存在,需要重新开始新一轮|SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(message);
|
||||
}
|
||||
|
||||
function isSignupPhonePasswordMismatchFailure(error) {
|
||||
const message = getErrorMessage(error);
|
||||
return /SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(message);
|
||||
}
|
||||
|
||||
function isSignupUserAlreadyExistsFailure(error) {
|
||||
@@ -9957,6 +9962,8 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||
await executeStepAndWait(2, AUTO_STEP_DELAYS[2]);
|
||||
}
|
||||
|
||||
let restartFromStep1WithCurrentEmail = false;
|
||||
|
||||
if (currentStartStep <= 3) {
|
||||
const latestState = await getState();
|
||||
const step3Status = latestState.stepStatuses?.[3] || 'pending';
|
||||
@@ -9969,18 +9976,71 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||
if (isStepDoneStatus(step3Status)) {
|
||||
await addLog(`自动运行:步骤 3 当前状态为 ${step3Status},将直接继续后续流程。`, 'info');
|
||||
} else {
|
||||
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
||||
try {
|
||||
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
||||
} catch (err) {
|
||||
if (isStopError(err)) {
|
||||
throw err;
|
||||
}
|
||||
if (isSignupPhonePasswordMismatchFailure(err)) {
|
||||
step4RestartCount += 1;
|
||||
const preservedState = await getState();
|
||||
const preservedEmail = String(preservedState.email || '').trim();
|
||||
const preservedPassword = String(preservedState.password || '').trim();
|
||||
const activeSignupPhoneNumber = String(
|
||||
preservedState.signupPhoneNumber
|
||||
|| preservedState.signupPhoneActivation?.phoneNumber
|
||||
|| preservedState.signupPhoneCompletedActivation?.phoneNumber
|
||||
|| ''
|
||||
).trim();
|
||||
const emailSuffix = preservedEmail ? `当前邮箱:${preservedEmail};` : '';
|
||||
const phoneSuffix = activeSignupPhoneNumber ? `当前手机号:${activeSignupPhoneNumber};` : '';
|
||||
await addLog(
|
||||
`步骤 3:检测到手机号/密码不匹配,准备丢弃当前注册手机号并回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${phoneSuffix}${emailSuffix}原因:${getErrorMessage(err)}`,
|
||||
'warn'
|
||||
);
|
||||
await invalidateDownstreamAfterStepRestart(1, {
|
||||
logLabel: `步骤 3 检测到手机号/密码不匹配后准备回到步骤 1 重新获取手机号重试(第 ${step4RestartCount} 次重开)`,
|
||||
});
|
||||
const restorePayload = {};
|
||||
if (preservedEmail) restorePayload.email = preservedEmail;
|
||||
if (preservedPassword) restorePayload.password = preservedPassword;
|
||||
if (preservedState.signupPhoneActivation) {
|
||||
restorePayload.signupPhoneNumber = '';
|
||||
restorePayload.signupPhoneActivation = null;
|
||||
restorePayload.signupPhoneCompletedActivation = null;
|
||||
restorePayload.signupPhoneVerificationRequestedAt = null;
|
||||
restorePayload.signupPhoneVerificationPurpose = '';
|
||||
if (String(preservedState.accountIdentifierType || '').trim().toLowerCase() === 'phone') {
|
||||
restorePayload.accountIdentifierType = null;
|
||||
restorePayload.accountIdentifier = '';
|
||||
}
|
||||
await addLog('步骤 3:已清空本轮注册手机号与接码订单,下一次重开将重新获取号码。', 'warn');
|
||||
}
|
||||
if (Object.keys(restorePayload).length) {
|
||||
await setState(restorePayload);
|
||||
}
|
||||
currentStartStep = 1;
|
||||
continueCurrentAttempt = true;
|
||||
restartFromStep1WithCurrentEmail = true;
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续执行剩余流程(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||
}
|
||||
|
||||
if (restartFromStep1WithCurrentEmail) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const signupTabId = await getTabId('signup-page');
|
||||
if (signupTabId) {
|
||||
await chrome.tabs.update(signupTabId, { active: true });
|
||||
}
|
||||
|
||||
let restartFromStep1WithCurrentEmail = false;
|
||||
let step = Math.max(currentStartStep, 4);
|
||||
while (step <= (typeof getLastStepIdForState === 'function'
|
||||
? getLastStepIdForState(await getState())
|
||||
@@ -10033,17 +10093,41 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
||||
const preservedState = await getState();
|
||||
const preservedEmail = String(preservedState.email || '').trim();
|
||||
const preservedPassword = String(preservedState.password || '').trim();
|
||||
const isSignupPhonePasswordMismatch = /SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(getErrorMessage(err));
|
||||
const activeSignupPhoneNumber = String(
|
||||
preservedState.signupPhoneNumber
|
||||
|| preservedState.signupPhoneActivation?.phoneNumber
|
||||
|| preservedState.signupPhoneCompletedActivation?.phoneNumber
|
||||
|| ''
|
||||
).trim();
|
||||
const emailSuffix = preservedEmail ? `当前邮箱:${preservedEmail};` : '';
|
||||
const phoneSuffix = activeSignupPhoneNumber ? `当前手机号:${activeSignupPhoneNumber};` : '';
|
||||
await addLog(
|
||||
`步骤 4:执行失败,准备沿用当前邮箱回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${emailSuffix}原因:${getErrorMessage(err)}`,
|
||||
isSignupPhonePasswordMismatch
|
||||
? `步骤 4:检测到手机号/密码不匹配,准备丢弃当前注册手机号并回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${phoneSuffix}${emailSuffix}原因:${getErrorMessage(err)}`
|
||||
: `步骤 4:执行失败,准备沿用当前邮箱回到步骤 1 重新开始(第 ${step4RestartCount} 次重开)。${emailSuffix}原因:${getErrorMessage(err)}`,
|
||||
'warn'
|
||||
);
|
||||
await invalidateDownstreamAfterStepRestart(1, {
|
||||
logLabel: `步骤 4 报错后准备回到步骤 1 沿用当前邮箱重试(第 ${step4RestartCount} 次重开)`,
|
||||
logLabel: isSignupPhonePasswordMismatch
|
||||
? `步骤 4 检测到手机号/密码不匹配后准备回到步骤 1 重新获取手机号重试(第 ${step4RestartCount} 次重开)`
|
||||
: `步骤 4 报错后准备回到步骤 1 沿用当前邮箱重试(第 ${step4RestartCount} 次重开)`,
|
||||
});
|
||||
const restorePayload = {};
|
||||
if (preservedEmail) restorePayload.email = preservedEmail;
|
||||
if (preservedPassword) restorePayload.password = preservedPassword;
|
||||
if (isSignupPhonePasswordMismatch && preservedState.signupPhoneActivation) {
|
||||
restorePayload.signupPhoneNumber = '';
|
||||
restorePayload.signupPhoneActivation = null;
|
||||
restorePayload.signupPhoneCompletedActivation = null;
|
||||
restorePayload.signupPhoneVerificationRequestedAt = null;
|
||||
restorePayload.signupPhoneVerificationPurpose = '';
|
||||
if (String(preservedState.accountIdentifierType || '').trim().toLowerCase() === 'phone') {
|
||||
restorePayload.accountIdentifierType = null;
|
||||
restorePayload.accountIdentifier = '';
|
||||
}
|
||||
await addLog('步骤 4:已清空本轮注册手机号与接码订单,下一次重开将重新获取号码。', 'warn');
|
||||
}
|
||||
if (Object.keys(restorePayload).length) {
|
||||
await setState(restorePayload);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,9 @@
|
||||
const retryCount = getAutoRunRoundRetryCount(item);
|
||||
const finalReason = item.finalFailureReason || item.failureReasons[item.failureReasons.length - 1] || '未知错误';
|
||||
const reasonSummary = formatAutoRunFailureReasons(item.failureReasons);
|
||||
return `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason};失败记录:${reasonSummary})`;
|
||||
return !reasonSummary || reasonSummary === finalReason
|
||||
? `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason})`
|
||||
: `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason};失败记录:${reasonSummary})`;
|
||||
})
|
||||
.join(';')}`,
|
||||
'error'
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
function isRestartCurrentAttemptError(error) {
|
||||
const message = String(typeof error === 'string' ? error : error?.message || '');
|
||||
return /当前邮箱已存在,需要重新开始新一轮/.test(message);
|
||||
return /当前邮箱已存在,需要重新开始新一轮|SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(message);
|
||||
}
|
||||
|
||||
function isSignupUserAlreadyExistsFailure(error) {
|
||||
|
||||
@@ -587,15 +587,20 @@
|
||||
notifyStepError(message.step, '流程已被用户停止。');
|
||||
return { ok: true, error: userMessage };
|
||||
}
|
||||
const currentState = await getState();
|
||||
const currentStepStatus = currentState?.stepStatuses?.[message.step] || '';
|
||||
const isSignupPhonePasswordMismatch = /SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(String(message.error || ''));
|
||||
if (isStopError(message.error)) {
|
||||
await setStepStatus(message.step, 'stopped');
|
||||
await addLog('已被用户停止', 'warn', { step: message.step });
|
||||
await appendManualAccountRunRecordIfNeeded(`step${message.step}_stopped`, null, message.error);
|
||||
notifyStepError(message.step, message.error);
|
||||
} else {
|
||||
await setStepStatus(message.step, 'failed');
|
||||
await addLog(`失败:${message.error}`, 'error', { step: message.step });
|
||||
await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, message.error);
|
||||
if (!(isSignupPhonePasswordMismatch && currentStepStatus === 'failed')) {
|
||||
await setStepStatus(message.step, 'failed');
|
||||
await addLog(`失败:${message.error}`, 'error', { step: message.step });
|
||||
await appendManualAccountRunRecordIfNeeded(`step${message.step}_failed`, null, message.error);
|
||||
}
|
||||
notifyStepError(message.step, message.error);
|
||||
}
|
||||
return { ok: true };
|
||||
|
||||
@@ -447,6 +447,9 @@ const SIGNUP_PHONE_ACTION_PATTERN = /手机|手机号|电话号码|phone|telepho
|
||||
const SIGNUP_SWITCH_TO_PHONE_PATTERN = new RegExp([
|
||||
String.raw`\u7ee7\u7eed\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?\u767b\u5f55`,
|
||||
String.raw`\u6539\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?\u767b\u5f55`,
|
||||
String.raw`\u7ee7\u7eed\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`,
|
||||
String.raw`\u6539\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`,
|
||||
String.raw`\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`,
|
||||
String.raw`continue\s+(?:with|using)\s+(?:a\s+)?phone(?:\s+number)?`,
|
||||
String.raw`use\s+(?:a\s+)?phone(?:\s+number)?(?:\s+instead)?`,
|
||||
String.raw`sign\s*(?:in|up)\s+with\s+(?:a\s+)?phone`,
|
||||
@@ -612,6 +615,7 @@ function inspectSignupEntryState() {
|
||||
passwordInput,
|
||||
submitButton: getSignupPasswordSubmitButton({ allowDisabled: true }),
|
||||
displayedEmail: getSignupPasswordDisplayedEmail(),
|
||||
passwordErrorText: getSignupPasswordFieldErrorText(),
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
@@ -924,6 +928,7 @@ function getSignupPasswordDiagnostics() {
|
||||
title: document.title || '',
|
||||
readyState: document.readyState || '',
|
||||
displayedEmail: getSignupPasswordDisplayedEmail(),
|
||||
passwordErrorText: getSignupPasswordFieldErrorText(),
|
||||
hasVisiblePasswordInput: Boolean(getSignupPasswordInput()),
|
||||
passwordInputCount: passwordInputs.length,
|
||||
visiblePasswordInputCount: passwordInputs.filter((item) => item.visible).length,
|
||||
@@ -2481,9 +2486,11 @@ const AUTH_ROUTE_ERROR_PATTERN = /405\s+method\s+not\s+allowed|route\s+error.*40
|
||||
const STEP4_405_RECOVERY_ERROR_PREFIX = 'STEP4_405_RECOVERY_LIMIT::';
|
||||
const STEP4_405_RECOVERY_LIMIT = 3;
|
||||
const SIGNUP_USER_ALREADY_EXISTS_ERROR_PREFIX = 'SIGNUP_USER_ALREADY_EXISTS::';
|
||||
const SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX = 'SIGNUP_PHONE_PASSWORD_MISMATCH::';
|
||||
const AUTH_MAX_CHECK_ATTEMPTS_ERROR_PREFIX = 'AUTH_MAX_CHECK_ATTEMPTS::';
|
||||
const STEP8_EMAIL_IN_USE_ERROR_PREFIX = 'STEP8_EMAIL_IN_USE::';
|
||||
const SIGNUP_EMAIL_EXISTS_PATTERN = /与此电子邮件地址相关联的帐户已存在|account\s+associated\s+with\s+this\s+email\s+address\s+already\s+exists|email\s+address.*already\s+exists/i;
|
||||
const SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN = /incorrect\s+phone\s+number\s+or\s+password|phone\s+number\s+or\s+password/i;
|
||||
|
||||
const authPageRecovery = self.MultiPageAuthPageRecovery?.createAuthPageRecovery?.({
|
||||
detailPattern: AUTH_TIMEOUT_ERROR_DETAIL_PATTERN,
|
||||
@@ -2540,6 +2547,14 @@ function createSignupUserAlreadyExistsError() {
|
||||
);
|
||||
}
|
||||
|
||||
function createSignupPhonePasswordMismatchError(detailText = '') {
|
||||
const detail = String(detailText || '').replace(/\s+/g, ' ').trim();
|
||||
const suffix = detail ? `页面提示:${detail}` : '页面提示手机号或密码不正确。';
|
||||
return new Error(
|
||||
`${SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX}步骤 3:检测到注册手机号或密码不正确,需要重新开始当前轮。${suffix}`
|
||||
);
|
||||
}
|
||||
|
||||
function createAuthMaxCheckAttemptsError() {
|
||||
return new Error(`${AUTH_MAX_CHECK_ATTEMPTS_ERROR_PREFIX}max_check_attempts on auth retry page; restart the current auth step without clicking Retry.`);
|
||||
}
|
||||
@@ -2573,6 +2588,24 @@ function getVisibleFieldErrorText() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function getSignupPasswordFieldErrorText() {
|
||||
const text = getVisibleFieldErrorText();
|
||||
if (text && SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN.test(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const passwordInput = getSignupPasswordInput();
|
||||
if (passwordInput) {
|
||||
const wrapper = passwordInput.closest('form, [data-rac], [role="group"], section, div');
|
||||
const wrapperText = (wrapper?.textContent || '').replace(/\s+/g, ' ').trim();
|
||||
if (wrapperText && SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN.test(wrapperText)) {
|
||||
return wrapperText;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isStep5Ready() {
|
||||
return Boolean(
|
||||
document.querySelector('input[name="name"], input[autocomplete="name"], input[name="birthday"], input[name="age"], [role="spinbutton"][data-type="year"]')
|
||||
@@ -4368,6 +4401,7 @@ function inspectSignupVerificationState() {
|
||||
state: 'password',
|
||||
passwordInput,
|
||||
submitButton: getSignupPasswordSubmitButton({ allowDisabled: true }),
|
||||
passwordErrorText: getSignupPasswordFieldErrorText(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4461,6 +4495,10 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
||||
}
|
||||
|
||||
if (snapshot.state === 'password') {
|
||||
if (snapshot.passwordErrorText) {
|
||||
log(`${prepareLogLabel}:检测到密码页报错“${snapshot.passwordErrorText}”,当前轮将回到步骤 1 重新开始。`, 'warn');
|
||||
throw createSignupPhonePasswordMismatchError(snapshot.passwordErrorText);
|
||||
}
|
||||
if (!passwordPageDiagnosticsLogged) {
|
||||
passwordPageDiagnosticsLogged = true;
|
||||
logSignupPasswordDiagnostics(`${prepareLogLabel}:页面仍停留在密码页`);
|
||||
|
||||
@@ -349,7 +349,6 @@ function reportComplete(step, data = {}) {
|
||||
*/
|
||||
function reportError(step, errorMessage) {
|
||||
console.error(LOG_PREFIX, `步骤 ${step} 失败: ${errorMessage}`);
|
||||
log(`失败:${errorMessage}`, 'error', { step });
|
||||
const message = {
|
||||
type: 'STEP_ERROR',
|
||||
source: getRuntimeScriptSource(),
|
||||
|
||||
@@ -56,6 +56,7 @@ const bundle = [
|
||||
extractFunction('isAddPhoneAuthUrl'),
|
||||
extractFunction('isAddPhoneAuthState'),
|
||||
extractFunction('isMail2925ThreadTerminatedError'),
|
||||
extractFunction('isSignupPhonePasswordMismatchFailure'),
|
||||
extractFunction('isSignupUserAlreadyExistsFailure'),
|
||||
extractFunction('getPostStep6AutoRestartDecision'),
|
||||
extractFunction('runAutoSequenceFromStep'),
|
||||
@@ -457,3 +458,328 @@ return {
|
||||
assert.equal(events.logs.some(({ message }) => /步骤 4 当前状态为 skipped/.test(message)), true);
|
||||
assert.equal(events.logs.some(({ message }) => /步骤 5 当前状态为 skipped/.test(message)), true);
|
||||
});
|
||||
|
||||
test('auto-run clears fetched signup phone state before restarting when step 4 detects phone/password mismatch', async () => {
|
||||
const api = new Function(`
|
||||
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||
const LAST_STEP_ID = 10;
|
||||
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const chrome = {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: async () => {},
|
||||
},
|
||||
};
|
||||
|
||||
let remainingFailures = 1;
|
||||
let currentState = {
|
||||
email: '',
|
||||
password: 'Secret123!',
|
||||
signupMethod: 'phone',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: '+56988841722',
|
||||
signupPhoneNumber: '+56988841722',
|
||||
signupPhoneActivation: {
|
||||
activationId: 'act-1',
|
||||
phoneNumber: '+56988841722',
|
||||
},
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'act-1',
|
||||
phoneNumber: '+56988841722',
|
||||
},
|
||||
stepStatuses: {
|
||||
1: 'pending',
|
||||
2: 'pending',
|
||||
3: 'pending',
|
||||
4: 'pending',
|
||||
5: 'pending',
|
||||
6: 'pending',
|
||||
7: 'pending',
|
||||
8: 'pending',
|
||||
9: 'pending',
|
||||
10: 'pending',
|
||||
},
|
||||
};
|
||||
const events = {
|
||||
steps: [],
|
||||
invalidations: [],
|
||||
logs: [],
|
||||
setStateCalls: [],
|
||||
};
|
||||
|
||||
async function addLog(message, level = 'info') {
|
||||
events.logs.push({ message, level });
|
||||
}
|
||||
|
||||
async function ensureAutoEmailReady() {
|
||||
return '';
|
||||
}
|
||||
|
||||
async function broadcastAutoRunStatus() {}
|
||||
async function ensureResolvedSignupMethodForRun() { return 'phone'; }
|
||||
|
||||
async function getState() {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
async function setState(updates) {
|
||||
currentState = {
|
||||
...currentState,
|
||||
...updates,
|
||||
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||
};
|
||||
events.setStateCalls.push(updates);
|
||||
}
|
||||
|
||||
function isStopError(error) {
|
||||
return (error?.message || String(error || '')) === '流程已被用户停止。';
|
||||
}
|
||||
|
||||
function isStepDoneStatus(status) {
|
||||
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
|
||||
}
|
||||
|
||||
async function executeStepAndWait(step) {
|
||||
events.steps.push(step);
|
||||
if (step === 4 && remainingFailures > 0) {
|
||||
remainingFailures -= 1;
|
||||
throw new Error('SIGNUP_PHONE_PASSWORD_MISMATCH::步骤 3:检测到注册手机号或密码不正确,需要重新开始当前轮。页面提示:Incorrect phone number or password');
|
||||
}
|
||||
}
|
||||
|
||||
async function getTabId() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||
events.invalidations.push({ step, options });
|
||||
currentState = {
|
||||
...currentState,
|
||||
password: null,
|
||||
stepStatuses: {
|
||||
1: currentState.stepStatuses[1] || 'completed',
|
||||
2: 'pending',
|
||||
3: 'pending',
|
||||
4: 'pending',
|
||||
5: 'pending',
|
||||
6: 'pending',
|
||||
7: 'pending',
|
||||
8: 'pending',
|
||||
9: 'pending',
|
||||
10: 'pending',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getLoginAuthStateLabel(state) {
|
||||
return state || 'unknown';
|
||||
}
|
||||
|
||||
function getErrorMessage(error) {
|
||||
return error?.message || String(error || '');
|
||||
}
|
||||
|
||||
async function getLoginAuthStateFromContent() {
|
||||
return { state: 'password_page', url: 'https://auth.openai.com/log-in' };
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
async run() {
|
||||
await runAutoSequenceFromStep(1, {
|
||||
targetRun: 1,
|
||||
totalRuns: 1,
|
||||
attemptRuns: 1,
|
||||
continued: false,
|
||||
});
|
||||
return { events, currentState };
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const { events, currentState } = await api.run();
|
||||
|
||||
assert.deepStrictEqual(events.invalidations, [
|
||||
{
|
||||
step: 1,
|
||||
options: {
|
||||
logLabel: '步骤 4 检测到手机号/密码不匹配后准备回到步骤 1 重新获取手机号重试(第 1 次重开)',
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.equal(currentState.signupPhoneNumber, '');
|
||||
assert.equal(currentState.signupPhoneActivation, null);
|
||||
assert.equal(currentState.signupPhoneCompletedActivation, null);
|
||||
assert.equal(currentState.accountIdentifierType, null);
|
||||
assert.equal(currentState.accountIdentifier, '');
|
||||
assert.equal(currentState.password, 'Secret123!');
|
||||
assert.equal(events.logs.some(({ message }) => /丢弃当前注册手机号并回到步骤 1 重新开始/.test(message)), true);
|
||||
assert.equal(events.logs.some(({ message }) => /已清空本轮注册手机号与接码订单/.test(message)), true);
|
||||
});
|
||||
|
||||
test('auto-run restarts from step 1 immediately when step 3 detects phone/password mismatch', async () => {
|
||||
const api = new Function(`
|
||||
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||
const LAST_STEP_ID = 10;
|
||||
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const chrome = {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: async () => {},
|
||||
},
|
||||
};
|
||||
|
||||
let remainingFailures = 1;
|
||||
let currentState = {
|
||||
email: '',
|
||||
password: 'Secret123!',
|
||||
signupMethod: 'phone',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: '+56988841722',
|
||||
signupPhoneNumber: '+56988841722',
|
||||
signupPhoneActivation: {
|
||||
activationId: 'act-1',
|
||||
phoneNumber: '+56988841722',
|
||||
},
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'act-1',
|
||||
phoneNumber: '+56988841722',
|
||||
},
|
||||
stepStatuses: {
|
||||
1: 'pending',
|
||||
2: 'pending',
|
||||
3: 'pending',
|
||||
4: 'pending',
|
||||
5: 'pending',
|
||||
6: 'pending',
|
||||
7: 'pending',
|
||||
8: 'pending',
|
||||
9: 'pending',
|
||||
10: 'pending',
|
||||
},
|
||||
};
|
||||
const events = {
|
||||
steps: [],
|
||||
invalidations: [],
|
||||
logs: [],
|
||||
setStateCalls: [],
|
||||
};
|
||||
|
||||
async function addLog(message, level = 'info') {
|
||||
events.logs.push({ message, level });
|
||||
}
|
||||
|
||||
async function ensureAutoEmailReady() {
|
||||
return '';
|
||||
}
|
||||
|
||||
async function broadcastAutoRunStatus() {}
|
||||
async function ensureResolvedSignupMethodForRun() { return 'phone'; }
|
||||
|
||||
async function getState() {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
async function setState(updates) {
|
||||
currentState = {
|
||||
...currentState,
|
||||
...updates,
|
||||
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||
};
|
||||
events.setStateCalls.push(updates);
|
||||
}
|
||||
|
||||
function isStopError(error) {
|
||||
return (error?.message || String(error || '')) === '流程已被用户停止。';
|
||||
}
|
||||
|
||||
function isStepDoneStatus(status) {
|
||||
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
|
||||
}
|
||||
|
||||
async function executeStepAndWait(step) {
|
||||
events.steps.push(step);
|
||||
if (step === 3 && remainingFailures > 0) {
|
||||
remainingFailures -= 1;
|
||||
throw new Error('SIGNUP_PHONE_PASSWORD_MISMATCH::步骤 3:检测到注册手机号或密码不正确,需要重新开始当前轮。页面提示:Incorrect phone number or password');
|
||||
}
|
||||
}
|
||||
|
||||
async function getTabId() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||
events.invalidations.push({ step, options });
|
||||
currentState = {
|
||||
...currentState,
|
||||
password: null,
|
||||
stepStatuses: {
|
||||
1: currentState.stepStatuses[1] || 'completed',
|
||||
2: 'pending',
|
||||
3: 'pending',
|
||||
4: 'pending',
|
||||
5: 'pending',
|
||||
6: 'pending',
|
||||
7: 'pending',
|
||||
8: 'pending',
|
||||
9: 'pending',
|
||||
10: 'pending',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getLoginAuthStateLabel(state) {
|
||||
return state || 'unknown';
|
||||
}
|
||||
|
||||
function getErrorMessage(error) {
|
||||
return error?.message || String(error || '');
|
||||
}
|
||||
|
||||
async function getLoginAuthStateFromContent() {
|
||||
return { state: 'password_page', url: 'https://auth.openai.com/log-in' };
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
async run() {
|
||||
await runAutoSequenceFromStep(1, {
|
||||
targetRun: 1,
|
||||
totalRuns: 1,
|
||||
attemptRuns: 1,
|
||||
continued: false,
|
||||
});
|
||||
return { events, currentState };
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const { events, currentState } = await api.run();
|
||||
|
||||
assert.deepStrictEqual(events.steps, [1, 2, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
assert.deepStrictEqual(events.invalidations, [
|
||||
{
|
||||
step: 1,
|
||||
options: {
|
||||
logLabel: '步骤 3 检测到手机号/密码不匹配后准备回到步骤 1 重新获取手机号重试(第 1 次重开)',
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.equal(currentState.signupPhoneNumber, '');
|
||||
assert.equal(currentState.signupPhoneActivation, null);
|
||||
assert.equal(currentState.signupPhoneCompletedActivation, null);
|
||||
assert.equal(currentState.accountIdentifierType, null);
|
||||
assert.equal(currentState.accountIdentifier, '');
|
||||
assert.equal(currentState.password, 'Secret123!');
|
||||
assert.equal(events.logs.some(({ message }) => /步骤 3:检测到手机号\/密码不匹配/.test(message)), true);
|
||||
assert.equal(events.logs.some(({ message }) => /步骤 3:已清空本轮注册手机号与接码订单/.test(message)), true);
|
||||
});
|
||||
|
||||
@@ -435,6 +435,36 @@ test('message router marks step 3 failed when post-submit finalize fails', async
|
||||
assert.deepStrictEqual(response, { ok: true, error: '步骤 3 提交后仍停留在密码页。' });
|
||||
});
|
||||
|
||||
test('message router does not duplicate step 3 mismatch failure log after finalize already failed', async () => {
|
||||
const mismatchError = 'SIGNUP_PHONE_PASSWORD_MISMATCH::步骤 3:检测到注册手机号或密码不正确,需要重新开始当前轮。页面提示:Incorrect phone number or password';
|
||||
const state = {
|
||||
stepStatuses: {
|
||||
3: 'failed',
|
||||
},
|
||||
};
|
||||
const { router, events } = createRouter({
|
||||
state,
|
||||
});
|
||||
|
||||
const response = await router.handleMessage({
|
||||
type: 'STEP_ERROR',
|
||||
step: 3,
|
||||
source: 'signup-page',
|
||||
payload: {},
|
||||
error: mismatchError,
|
||||
}, {});
|
||||
|
||||
assert.deepStrictEqual(events.stepStatuses, []);
|
||||
assert.equal(events.logs.some(({ message, step }) => /失败:SIGNUP_PHONE_PASSWORD_MISMATCH::/.test(message) && step === 3), false);
|
||||
assert.deepStrictEqual(events.notifyErrors, [
|
||||
{
|
||||
step: 3,
|
||||
error: mismatchError,
|
||||
},
|
||||
]);
|
||||
assert.deepStrictEqual(response, { ok: true });
|
||||
});
|
||||
|
||||
test('message router stops the flow and surfaces cloudflare security block errors', async () => {
|
||||
const { router, events } = createRouter();
|
||||
|
||||
|
||||
@@ -254,6 +254,64 @@ return {
|
||||
});
|
||||
});
|
||||
|
||||
test('signup verification state exposes password error text on password page', () => {
|
||||
const api = new Function(`
|
||||
const location = {
|
||||
href: 'https://auth.openai.com/log-in/password',
|
||||
};
|
||||
|
||||
function isStep5Ready() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isVerificationPageStillVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSignupPasswordErrorPage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSignupPasswordTimeoutErrorPageState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSignupEmailAlreadyExistsPage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSignupPasswordInput() {
|
||||
return { value: 'Secret123!' };
|
||||
}
|
||||
|
||||
function getSignupPasswordSubmitButton() {
|
||||
return { textContent: 'Continue' };
|
||||
}
|
||||
|
||||
function getSignupPasswordFieldErrorText() {
|
||||
return 'Incorrect phone number or password';
|
||||
}
|
||||
|
||||
${extractFunction('isSignupProfilePageUrl')}
|
||||
${extractFunction('isLikelyLoggedInChatgptHomeUrl')}
|
||||
${extractFunction('getStep4PostVerificationState')}
|
||||
${extractFunction('inspectSignupVerificationState')}
|
||||
|
||||
return {
|
||||
run() {
|
||||
return inspectSignupVerificationState();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
assert.deepStrictEqual(api.run(), {
|
||||
state: 'password',
|
||||
passwordInput: { value: 'Secret123!' },
|
||||
submitButton: { textContent: 'Continue' },
|
||||
passwordErrorText: 'Incorrect phone number or password',
|
||||
});
|
||||
});
|
||||
|
||||
test('signup verification state keeps verification priority when email-verification page also shows profile fields', () => {
|
||||
const api = new Function(`
|
||||
const location = {
|
||||
|
||||
@@ -717,3 +717,92 @@ return {
|
||||
assert.equal(snapshot.sleepCalls >= 3, true);
|
||||
assert.equal(snapshot.targetChecks >= 1, true);
|
||||
});
|
||||
|
||||
test('prepareSignupVerificationFlow stops immediately when password page shows phone/password mismatch', async () => {
|
||||
const api = new Function(`
|
||||
const logs = [];
|
||||
const clicks = [];
|
||||
let now = 0;
|
||||
|
||||
Date.now = () => now;
|
||||
|
||||
function throwIfStopped() {}
|
||||
function log(message, level = 'info') { logs.push({ message, level }); }
|
||||
async function sleep(ms = 0) { now += ms || 200; }
|
||||
function isVisibleElement() { return true; }
|
||||
function isActionEnabled() { return true; }
|
||||
function getActionText(el) { return el?.textContent || ''; }
|
||||
function getCurrentAuthRetryPageState() { return null; }
|
||||
function isPhoneVerificationPageReady() { return false; }
|
||||
function findResendVerificationCodeTrigger() { return null; }
|
||||
function isEmailVerificationPage() { return false; }
|
||||
function getPageTextSnapshot() { return 'Incorrect phone number or password'; }
|
||||
function getVerificationCodeTarget() { return null; }
|
||||
function is405MethodNotAllowedPage() { return false; }
|
||||
async function recoverCurrentAuthRetryPage() {}
|
||||
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
|
||||
function getSignupPasswordInput() { return { value: 'Secret123!' }; }
|
||||
function getSignupPasswordSubmitButton() { return { textContent: 'Continue' }; }
|
||||
function isSignupEmailAlreadyExistsPage() { return false; }
|
||||
function isSignupPasswordErrorPage() { return false; }
|
||||
function getSignupPasswordTimeoutErrorPageState() { return null; }
|
||||
function isStep5Ready() { return false; }
|
||||
function getSignupPasswordFieldErrorText() { return 'Incorrect phone number or password'; }
|
||||
function simulateClick(target) { clicks.push(target?.textContent || 'clicked'); }
|
||||
async function humanPause() {}
|
||||
function fillInput() {}
|
||||
function logSignupPasswordDiagnostics() {}
|
||||
function createSignupPhonePasswordMismatchError(detailText = '') {
|
||||
return new Error('SIGNUP_PHONE_PASSWORD_MISMATCH::' + detailText);
|
||||
}
|
||||
|
||||
const location = {
|
||||
href: 'https://auth.openai.com/log-in/password',
|
||||
pathname: '/log-in/password',
|
||||
};
|
||||
const document = {
|
||||
readyState: 'complete',
|
||||
title: '',
|
||||
body: {
|
||||
textContent: 'Incorrect phone number or password',
|
||||
innerText: 'Incorrect phone number or password',
|
||||
},
|
||||
querySelector() {
|
||||
return null;
|
||||
},
|
||||
querySelectorAll() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
${extractFunction('isSignupVerificationPageInteractiveReady')}
|
||||
${extractFunction('isVerificationPageStillVisible')}
|
||||
${extractFunction('isSignupProfilePageUrl')}
|
||||
${extractFunction('isLikelyLoggedInChatgptHomeUrl')}
|
||||
${extractFunction('getStep4PostVerificationState')}
|
||||
${extractFunction('inspectSignupVerificationState')}
|
||||
${extractFunction('waitForSignupVerificationTransition')}
|
||||
${extractFunction('prepareSignupVerificationFlow')}
|
||||
|
||||
return {
|
||||
async run() {
|
||||
try {
|
||||
await prepareSignupVerificationFlow({
|
||||
password: 'Secret123!',
|
||||
prepareLogLabel: '步骤 3 收尾',
|
||||
}, 10000);
|
||||
return { threw: false, logs, clicks };
|
||||
} catch (error) {
|
||||
return { threw: true, error: error.message, logs, clicks };
|
||||
}
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.equal(result.threw, true);
|
||||
assert.match(result.error, /SIGNUP_PHONE_PASSWORD_MISMATCH::Incorrect phone number or password/);
|
||||
assert.equal(result.clicks.length, 0);
|
||||
assert.equal(result.logs.some(({ message }) => /检测到密码页报错/.test(message)), true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user