From 6a0ec37b18201822f43dd9a61e5ac7df77113e19 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Fri, 15 May 2026 00:50:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AF=86=E7=A0=81=E9=A1=B5?= =?UTF-8?q?=E6=94=B6=E5=B0=BE=E9=87=8D=E5=A4=8D=E7=82=B9=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/signup-page.js | 49 +++++++++++- tests/step4-split-code-submit.test.js | 110 ++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/content/signup-page.js b/content/signup-page.js index 6cf4c93..faf7cd5 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -4714,6 +4714,49 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { let recoveryRound = 0; const maxRecoveryRounds = 3; let passwordPageDiagnosticsLogged = false; + const isPasswordSubmitButtonReadyForRetry = (button) => { + if (!button || !isActionEnabled(button)) { + return false; + } + + const ariaBusy = String(button.getAttribute?.('aria-busy') || '').trim().toLowerCase(); + if (ariaBusy === 'true') { + return false; + } + + const pendingAttr = [ + button.getAttribute?.('data-loading'), + button.getAttribute?.('data-pending'), + button.getAttribute?.('data-submitting'), + button.getAttribute?.('data-state'), + ] + .map((value) => String(value || '').trim().toLowerCase()) + .filter(Boolean) + .join(' '); + if (/\b(?:true|loading|pending|submitting|busy)\b/.test(pendingAttr)) { + return false; + } + + let style = null; + try { + style = typeof window !== 'undefined' && window.getComputedStyle + ? window.getComputedStyle(button) + : null; + } catch { + style = null; + } + + if (style?.pointerEvents === 'none') { + return false; + } + + const opacity = Number.parseFloat(style?.opacity || ''); + if (Number.isFinite(opacity) && opacity < 0.8) { + return false; + } + + return true; + }; while (Date.now() - start < timeout && recoveryRound < maxRecoveryRounds) { throwIfStopped(); @@ -4749,12 +4792,11 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { throw new Error('当前邮箱已存在,需要重新开始新一轮。'); } - recoveryRound += 1; - if (snapshot.state === 'error') { if (snapshot.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } + recoveryRound += 1; await recoverCurrentAuthRetryPage({ flow: 'signup', logLabel: `${prepareLogLabel}:检测到注册认证重试页,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`, @@ -4785,7 +4827,8 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { }); } - if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) { + if (snapshot.submitButton && isPasswordSubmitButtonReadyForRetry(snapshot.submitButton)) { + recoveryRound += 1; log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn'); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'retry-submit-signup-password' }, async () => { diff --git a/tests/step4-split-code-submit.test.js b/tests/step4-split-code-submit.test.js index 2d5775d..384f15c 100644 --- a/tests/step4-split-code-submit.test.js +++ b/tests/step4-split-code-submit.test.js @@ -1124,6 +1124,116 @@ return { assert.equal(result.logs.some(({ message }) => /检测到密码页报错/.test(message)), true); }); +test('prepareSignupVerificationFlow waits instead of retrying while matched password submit is pending', async () => { + const api = new Function(` +const logs = []; +const clicks = []; +let now = 0; + +Date.now = () => now; + +const passwordInput = { value: 'Secret123!' }; +const submitButton = { + textContent: 'Continue', + disabled: false, + getAttribute(name) { + if (name === 'aria-disabled') return 'false'; + return ''; + }, +}; + +const window = { + getComputedStyle() { + return { + opacity: '0.5', + pointerEvents: 'auto', + }; + }, +}; + +const location = { + href: 'https://auth.openai.com/create-account/password', + pathname: '/create-account/password', +}; +const document = { + readyState: 'interactive', + title: '', + body: { + textContent: 'Create password Continue', + innerText: 'Create password Continue', + }, + querySelector() { + return null; + }, + querySelectorAll() { + return []; + }, +}; + +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(el) { return Boolean(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== '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 document.body.textContent; } +function getVerificationCodeTarget() { return null; } +function is405MethodNotAllowedPage() { return false; } +async function recoverCurrentAuthRetryPage() {} +function createSignupUserAlreadyExistsError() { return new Error('user already exists'); } +function getSignupPasswordInput() { return passwordInput; } +function getSignupPasswordSubmitButton() { return submitButton; } +function isSignupEmailAlreadyExistsPage() { return false; } +function isSignupPasswordErrorPage() { return false; } +function getSignupPasswordTimeoutErrorPageState() { return null; } +function isStep5Ready() { return false; } +function getSignupPasswordFieldErrorText() { return ''; } +function simulateClick(target) { clicks.push(target?.textContent || 'clicked'); } +async function humanPause() {} +function fillInput(input, value) { input.value = value; } +function logSignupPasswordDiagnostics(message) { logs.push({ message, level: 'warn' }); } +function createSignupPhonePasswordMismatchError(detailText = '') { + return new Error('SIGNUP_PHONE_PASSWORD_MISMATCH::' + detailText); +} + +const VERIFICATION_PAGE_PATTERN = /verification code/i; + +${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: 'step 3 finalize', + }, 11000); + return { threw: false, logs, clicks, now }; + } catch (error) { + return { threw: true, error: error.message, logs, clicks, now }; + } + }, +}; +`)(); + + const result = await api.run(); + + assert.equal(result.threw, true); + assert.equal(result.clicks.length, 0); + assert.equal(result.error.includes('0/3'), true); + assert.equal(result.now >= 11000, true); +}); + test('fillSignupEmailAndContinue reports before deferred submit while submit still waits for operation delay', async () => { const api = new Function(` const events = [];