feat: add configurable operation delay
This commit is contained in:
+344
-96
@@ -6,6 +6,14 @@ console.log('[MultiPage:signup-page] Content script loaded on', location.href);
|
||||
|
||||
const SIGNUP_PAGE_LISTENER_SENTINEL = 'data-multipage-signup-page-listener';
|
||||
|
||||
function getOperationDelayRunner() {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function'
|
||||
? gate
|
||||
: async (_metadata, operation) => operation();
|
||||
}
|
||||
|
||||
if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1') {
|
||||
document.documentElement.setAttribute(SIGNUP_PAGE_LISTENER_SENTINEL, '1');
|
||||
|
||||
@@ -251,6 +259,13 @@ function isEmailVerificationPage() {
|
||||
}
|
||||
|
||||
async function resendVerificationCode(step, timeout = 45000) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
if (step === 8) {
|
||||
await waitForLoginVerificationPageReady(10000, step);
|
||||
}
|
||||
@@ -275,7 +290,9 @@ async function resendVerificationCode(step, timeout = 45000) {
|
||||
if (action && isActionEnabled(action)) {
|
||||
log(`步骤 ${step}:重新发送验证码按钮已可用。`);
|
||||
await humanPause(350, 900);
|
||||
simulateClick(action);
|
||||
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'resend-verification-code' }, async () => {
|
||||
simulateClick(action);
|
||||
});
|
||||
await sleep(1200);
|
||||
|
||||
// After clicking resend, check if 405 error appeared
|
||||
@@ -955,6 +972,13 @@ function logSignupPasswordDiagnostics(context, level = 'warn') {
|
||||
}
|
||||
|
||||
async function waitForSignupEntryState(options = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const {
|
||||
timeout = 15000,
|
||||
autoOpenEntry = false,
|
||||
@@ -995,7 +1019,9 @@ async function waitForSignupEntryState(options = {}) {
|
||||
}
|
||||
log('步骤 2:检测到手机号输入模式,正在切换到邮箱输入模式...');
|
||||
await humanPause(350, 900);
|
||||
simulateClick(snapshot.switchToEmailTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'switch-to-signup-email' }, async () => {
|
||||
simulateClick(snapshot.switchToEmailTrigger);
|
||||
});
|
||||
} else if (!snapshot.switchToEmailTrigger && !loggedMissingSwitchToEmail) {
|
||||
loggedMissingSwitchToEmail = true;
|
||||
log('步骤 2:检测到手机号输入模式,但暂未识别到“改用邮箱/继续使用电子邮件地址登录”按钮,继续等待界面稳定...', 'warn');
|
||||
@@ -1023,7 +1049,9 @@ async function waitForSignupEntryState(options = {}) {
|
||||
}
|
||||
log('步骤 2:正在点击官网注册入口...');
|
||||
await humanPause(350, 900);
|
||||
simulateClick(snapshot.signupTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
|
||||
simulateClick(snapshot.signupTrigger);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,6 +1121,13 @@ async function ensureSignupPasswordPageReady(timeout = 20000) {
|
||||
}
|
||||
|
||||
async function fillSignupEmailAndContinue(email, step) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
if (!email) throw new Error(`未提供邮箱地址,步骤 ${step} 无法继续。`);
|
||||
const normalizedEmail = String(email || '').trim().toLowerCase();
|
||||
|
||||
@@ -1123,7 +1158,9 @@ async function fillSignupEmailAndContinue(email, step) {
|
||||
|
||||
log(`步骤 ${step}:正在填写邮箱:${email}`);
|
||||
await humanPause(500, 1400);
|
||||
fillInput(snapshot.emailInput, email);
|
||||
await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'fill', label: 'signup-email' }, async () => {
|
||||
fillInput(snapshot.emailInput, email);
|
||||
});
|
||||
log(`步骤 ${step}:邮箱已填写`);
|
||||
|
||||
const continueButton = snapshot.continueButton || getSignupEmailContinueButton({ allowDisabled: true });
|
||||
@@ -1132,16 +1169,18 @@ async function fillSignupEmailAndContinue(email, step) {
|
||||
}
|
||||
|
||||
log(`步骤 ${step}:邮箱已准备提交,正在前往密码页...`);
|
||||
window.setTimeout(() => {
|
||||
try {
|
||||
throwIfStopped();
|
||||
try {
|
||||
await sleep(120);
|
||||
throwIfStopped();
|
||||
await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'submit', label: 'submit-signup-email' }, async () => {
|
||||
simulateClick(continueButton);
|
||||
} catch (error) {
|
||||
if (!isStopError(error)) {
|
||||
console.error('[MultiPage:signup-page] deferred signup email submit failed:', error?.message || error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (!isStopError(error)) {
|
||||
console.error('[MultiPage:signup-page] signup email submit failed:', error?.message || error);
|
||||
}
|
||||
}, 120);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
submitted: true,
|
||||
@@ -1655,6 +1694,13 @@ function findSignupPhoneCountryOptionByPhoneNumber(phoneInput, phoneNumber) {
|
||||
}
|
||||
|
||||
async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInput = getSignupPhoneInput(), options = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
if (!select || !targetOption) {
|
||||
return false;
|
||||
}
|
||||
@@ -1662,12 +1708,16 @@ async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInpu
|
||||
? (select.options?.[select.selectedIndex] || null)
|
||||
: null;
|
||||
if (selectedOption && isSameSignupCountryOption(selectedOption, targetOption)) {
|
||||
dispatchSignupPhoneFieldEvents(select);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => {
|
||||
dispatchSignupPhoneFieldEvents(select);
|
||||
});
|
||||
await sleep(120);
|
||||
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
|
||||
}
|
||||
select.value = String(targetOption.value || '');
|
||||
dispatchSignupPhoneFieldEvents(select);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => {
|
||||
select.value = String(targetOption.value || '');
|
||||
dispatchSignupPhoneFieldEvents(select);
|
||||
});
|
||||
await sleep(250);
|
||||
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
|
||||
}
|
||||
@@ -1722,6 +1772,13 @@ function findSignupPhoneCountryListboxOption(targetOption, options = {}) {
|
||||
}
|
||||
|
||||
async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const button = getSignupPhoneCountryButton(phoneInput);
|
||||
if (!button) {
|
||||
return false;
|
||||
@@ -1802,7 +1859,9 @@ async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption
|
||||
return scrolled;
|
||||
};
|
||||
|
||||
simulateClick(button);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-phone-country-listbox' }, async () => {
|
||||
simulateClick(button);
|
||||
});
|
||||
await sleep(200);
|
||||
resetListboxScroll();
|
||||
|
||||
@@ -1812,7 +1871,9 @@ async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption
|
||||
throwIfStopped();
|
||||
const option = findSignupPhoneCountryListboxOption(targetOption, options);
|
||||
if (option) {
|
||||
simulateClick(option);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-listbox-option' }, async () => {
|
||||
simulateClick(option);
|
||||
});
|
||||
await sleep(450);
|
||||
if (isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options)) {
|
||||
return true;
|
||||
@@ -2108,6 +2169,13 @@ function getLoginPhoneInputCandidateDiagnostics(limit = 12) {
|
||||
}
|
||||
|
||||
async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const {
|
||||
phoneNumber = '',
|
||||
dialCode = '',
|
||||
@@ -2132,7 +2200,9 @@ async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
|
||||
const fillCandidates = getLoginPhoneFillCandidates(phoneNumber, dialCode, currentInput);
|
||||
for (const attemptedValue of fillCandidates) {
|
||||
currentInput.focus?.();
|
||||
fillInput(currentInput, attemptedValue);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-phone-number' }, async () => {
|
||||
fillInput(currentInput, attemptedValue);
|
||||
});
|
||||
lastVerification = await waitForPhoneInputValue(currentInput, inputValue, {
|
||||
resolvePhoneInput,
|
||||
phoneNumber,
|
||||
@@ -2142,7 +2212,9 @@ async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
|
||||
});
|
||||
if (lastVerification.ok) {
|
||||
const verifiedInput = lastVerification.input || currentInput;
|
||||
const hiddenSync = syncPhoneHiddenFormValue(verifiedInput, { phoneNumber, dialCode, inputValue });
|
||||
const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-hidden-sync' }, async () => (
|
||||
syncPhoneHiddenFormValue(verifiedInput, { phoneNumber, dialCode, inputValue })
|
||||
));
|
||||
const expectedHiddenDigits = normalizePhoneDigits(phoneNumber) || `${normalizePhoneDigits(dialCode)}${normalizePhoneDigits(inputValue)}`;
|
||||
if (hiddenSync && expectedHiddenDigits && normalizePhoneDigits(hiddenSync.value) !== expectedHiddenDigits) {
|
||||
throw new Error(`\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u9690\u85cf\u63d0\u4ea4\u5b57\u6bb5\u540c\u6b65\u5931\u8d25\uff0c\u671f\u671b ${expectedHiddenDigits}\uff0c\u5b9e\u9645 ${normalizePhoneDigits(hiddenSync.value) || '\u7a7a'}\u3002`);
|
||||
@@ -2198,6 +2270,13 @@ function resolveSignupPhoneDialCode(phoneInput, options = {}) {
|
||||
}
|
||||
|
||||
async function waitForSignupPhoneEntryState(options = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const {
|
||||
timeout = 20000,
|
||||
step = 2,
|
||||
@@ -2226,14 +2305,18 @@ async function waitForSignupPhoneEntryState(options = {}) {
|
||||
lastSwitchToPhoneAt = Date.now();
|
||||
log(`步骤 ${step}:检测到邮箱输入模式,正在切换到手机号注册入口...`);
|
||||
await humanPause(350, 900);
|
||||
simulateClick(switchToPhone);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'switch-to-signup-phone' }, async () => {
|
||||
simulateClick(switchToPhone);
|
||||
});
|
||||
} else {
|
||||
const moreOptionsTrigger = findSignupMoreOptionsTrigger();
|
||||
if (moreOptionsTrigger && Date.now() - lastMoreOptionsClickAt >= 1500) {
|
||||
lastMoreOptionsClickAt = Date.now();
|
||||
log(`步骤 ${step}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`);
|
||||
await humanPause(350, 900);
|
||||
simulateClick(moreOptionsTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'signup-phone-more-options' }, async () => {
|
||||
simulateClick(moreOptionsTrigger);
|
||||
});
|
||||
} else if (!switchToPhone && !slowSnapshotLogged && Date.now() - start >= 5000) {
|
||||
slowSnapshotLogged = true;
|
||||
log(`步骤 ${step}:尚未找到手机号入口,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn');
|
||||
@@ -2248,7 +2331,9 @@ async function waitForSignupPhoneEntryState(options = {}) {
|
||||
lastTriggerClickAt = Date.now();
|
||||
log(`步骤 ${step}:正在点击官网注册入口...`);
|
||||
await humanPause(350, 900);
|
||||
simulateClick(snapshot.signupTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
|
||||
simulateClick(snapshot.signupTrigger);
|
||||
});
|
||||
}
|
||||
await sleep(250);
|
||||
continue;
|
||||
@@ -2268,6 +2353,13 @@ async function waitForSignupPhoneEntryState(options = {}) {
|
||||
}
|
||||
|
||||
async function submitSignupPhoneNumberAndContinue(payload = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const phoneNumber = String(payload.phoneNumber || '').trim();
|
||||
const countryLabel = String(payload.countryLabel || '').trim();
|
||||
if (!phoneNumber) {
|
||||
@@ -2312,11 +2404,15 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) {
|
||||
|
||||
log(`步骤 2:正在填写手机号:${phoneNumber}`);
|
||||
await humanPause(500, 1400);
|
||||
fillInput(snapshot.phoneInput, inputValue);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'fill', label: 'signup-phone-number' }, async () => {
|
||||
fillInput(snapshot.phoneInput, inputValue);
|
||||
});
|
||||
const hiddenPhoneNumberInput = getSignupPhoneHiddenNumberInput(snapshot.phoneInput);
|
||||
const e164PhoneNumber = toE164PhoneNumber(phoneNumber, dialCode);
|
||||
if (hiddenPhoneNumberInput && e164PhoneNumber) {
|
||||
fillInput(hiddenPhoneNumberInput, e164PhoneNumber);
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'hidden-sync', label: 'signup-phone-hidden-sync' }, async () => {
|
||||
fillInput(hiddenPhoneNumberInput, e164PhoneNumber);
|
||||
});
|
||||
}
|
||||
log(`步骤 2:手机号已填写:${phoneNumber}${dialCode ? `(区号 +${dialCode},本地号 ${inputValue})` : ''}`);
|
||||
|
||||
@@ -2326,16 +2422,18 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) {
|
||||
}
|
||||
|
||||
log('步骤 2:手机号已准备提交,正在前往下一页...');
|
||||
window.setTimeout(() => {
|
||||
try {
|
||||
throwIfStopped();
|
||||
try {
|
||||
await sleep(120);
|
||||
throwIfStopped();
|
||||
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'submit', label: 'submit-signup-phone' }, async () => {
|
||||
simulateClick(continueButton);
|
||||
} catch (error) {
|
||||
if (!isStopError(error)) {
|
||||
console.error('[MultiPage:signup-page] deferred signup phone submit failed:', error?.message || error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (!isStopError(error)) {
|
||||
console.error('[MultiPage:signup-page] signup phone submit failed:', error?.message || error);
|
||||
}
|
||||
}, 120);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
submitted: true,
|
||||
@@ -2363,6 +2461,13 @@ async function step2_clickRegister(payload = {}) {
|
||||
// ============================================================
|
||||
|
||||
async function step3_fillEmailPassword(payload) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const { email, password } = payload;
|
||||
if (!password) throw new Error('未提供密码,步骤 3 需要可用密码。');
|
||||
const normalizedEmail = String(email || '').trim().toLowerCase();
|
||||
@@ -2426,7 +2531,9 @@ async function step3_fillEmailPassword(payload) {
|
||||
}
|
||||
|
||||
await humanPause(600, 1500);
|
||||
fillInput(snapshot.passwordInput, password);
|
||||
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'signup-password' }, async () => {
|
||||
fillInput(snapshot.passwordInput, password);
|
||||
});
|
||||
log('步骤 3:密码已填写');
|
||||
|
||||
const submitBtn = snapshot.submitButton
|
||||
@@ -2439,8 +2546,6 @@ async function step3_fillEmailPassword(payload) {
|
||||
logSignupPasswordDiagnostics('步骤 3:当前密码页同时存在一次性验证码入口', 'info');
|
||||
}
|
||||
|
||||
// Report complete BEFORE submit, because submit causes page navigation
|
||||
// which kills the content script connection
|
||||
const signupVerificationRequestedAt = submitBtn ? Date.now() : null;
|
||||
const completionPayload = {
|
||||
email,
|
||||
@@ -2450,6 +2555,7 @@ async function step3_fillEmailPassword(payload) {
|
||||
signupVerificationRequestedAt,
|
||||
deferredSubmit: Boolean(submitBtn),
|
||||
};
|
||||
|
||||
reportComplete(3, completionPayload);
|
||||
|
||||
if (submitBtn) {
|
||||
@@ -2458,7 +2564,9 @@ async function step3_fillEmailPassword(payload) {
|
||||
throwIfStopped();
|
||||
await sleep(500);
|
||||
await humanPause(500, 1300);
|
||||
simulateClick(submitBtn);
|
||||
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'submit-signup-password' }, async () => {
|
||||
simulateClick(submitBtn);
|
||||
});
|
||||
log('步骤 3:表单已提交');
|
||||
} catch (error) {
|
||||
if (!isStopError(error)) {
|
||||
@@ -3282,6 +3390,13 @@ function getCurrentAuthRetryPageState(flow = 'auth') {
|
||||
}
|
||||
|
||||
async function recoverCurrentAuthRetryPage(payload = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const {
|
||||
flow = 'auth',
|
||||
logLabel = '',
|
||||
@@ -3300,6 +3415,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
||||
maxClickAttempts,
|
||||
pathPatterns: resolvedPathPatterns,
|
||||
step,
|
||||
stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code',
|
||||
timeoutMs,
|
||||
waitAfterClickMs,
|
||||
});
|
||||
@@ -3332,7 +3448,9 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
||||
clickCount += 1;
|
||||
log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn');
|
||||
await humanPause(300, 800);
|
||||
simulateClick(retryState.retryButton);
|
||||
await performOperationWithDelay({ stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'auth-retry-click' }, async () => {
|
||||
simulateClick(retryState.retryButton);
|
||||
});
|
||||
const settleStart = Date.now();
|
||||
while (Date.now() - settleStart < waitAfterClickMs) {
|
||||
throwIfStopped();
|
||||
@@ -4369,28 +4487,37 @@ function throwForStep6FatalState(snapshot, visibleStep = 7) {
|
||||
|
||||
async function triggerLoginSubmitAction(button, fallbackField) {
|
||||
const form = button?.form || fallbackField?.form || button?.closest?.('form') || fallbackField?.closest?.('form') || null;
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
|
||||
await humanPause(400, 1100);
|
||||
if (button && isActionEnabled(button)) {
|
||||
simulateClick(button);
|
||||
return;
|
||||
}
|
||||
|
||||
if (form && typeof form.requestSubmit === 'function') {
|
||||
if (button && button.form === form) {
|
||||
form.requestSubmit(button);
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'submit', label: 'login-submit' }, async () => {
|
||||
if (button && isActionEnabled(button)) {
|
||||
simulateClick(button);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (button && typeof button.click === 'function') {
|
||||
button.click();
|
||||
return;
|
||||
}
|
||||
if (form && typeof form.requestSubmit === 'function') {
|
||||
if (button && button.form === form) {
|
||||
form.requestSubmit(button);
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('未找到可用的登录提交按钮。URL: ' + location.href);
|
||||
if (button && typeof button.click === 'function') {
|
||||
button.click();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('未找到可用的登录提交按钮。URL: ' + location.href);
|
||||
});
|
||||
}
|
||||
|
||||
function isSignupPasswordErrorPage() {
|
||||
@@ -4480,6 +4607,13 @@ async function waitForSignupVerificationTransition(timeout = 5000) {
|
||||
}
|
||||
|
||||
async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const { password } = payload;
|
||||
const prepareSource = String(payload?.prepareSource || '').trim() || 'step4_execute';
|
||||
const prepareLogLabel = String(payload?.prepareLogLabel || '').trim()
|
||||
@@ -4554,13 +4688,17 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
||||
if ((snapshot.passwordInput.value || '') !== password) {
|
||||
log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn');
|
||||
await humanPause(450, 1100);
|
||||
fillInput(snapshot.passwordInput, password);
|
||||
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'retry-signup-password' }, async () => {
|
||||
fillInput(snapshot.passwordInput, password);
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) {
|
||||
log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn');
|
||||
await humanPause(350, 900);
|
||||
simulateClick(snapshot.submitButton);
|
||||
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'retry-submit-signup-password' }, async () => {
|
||||
simulateClick(snapshot.submitButton);
|
||||
});
|
||||
await sleep(1200);
|
||||
continue;
|
||||
}
|
||||
@@ -4763,6 +4901,13 @@ async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500
|
||||
async function fillVerificationCode(step, payload) {
|
||||
const { code, signupProfile } = payload;
|
||||
if (!code) throw new Error('未提供验证码。');
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
|
||||
if (step === 4) {
|
||||
const postVerificationState = getStep4PostVerificationState();
|
||||
@@ -4854,17 +4999,18 @@ async function fillVerificationCode(step, payload) {
|
||||
|
||||
if (splitInputs?.length >= 6) {
|
||||
log(`步骤 ${step}:发现分开的单字符验证码输入框,正在逐个填写...`);
|
||||
for (let i = 0; i < 6 && i < splitInputs.length; i++) {
|
||||
const targetInput = splitInputs[i];
|
||||
try {
|
||||
targetInput.focus?.();
|
||||
} catch {}
|
||||
fillInput(splitInputs[i], code[i]);
|
||||
try {
|
||||
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true }));
|
||||
} catch {}
|
||||
await sleep(100);
|
||||
}
|
||||
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code', label: 'split-code' }, async () => {
|
||||
for (let i = 0; i < 6 && i < splitInputs.length; i++) {
|
||||
const targetInput = splitInputs[i];
|
||||
try {
|
||||
targetInput.focus?.();
|
||||
} catch {}
|
||||
fillInput(splitInputs[i], code[i]);
|
||||
try {
|
||||
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true }));
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
const filled = await waitForSplitVerificationInputsFilled(splitInputs, code, 2500);
|
||||
if (!filled) {
|
||||
const current = Array.from(splitInputs)
|
||||
@@ -4880,7 +5026,9 @@ async function fillVerificationCode(step, payload) {
|
||||
const splitSubmitBtn = await waitForVerificationSubmitButton(splitInputs[0], 2000).catch(() => null);
|
||||
if (splitSubmitBtn) {
|
||||
await humanPause(450, 1200);
|
||||
simulateClick(splitSubmitBtn);
|
||||
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => {
|
||||
simulateClick(splitSubmitBtn);
|
||||
});
|
||||
log(`步骤 ${step}:分格验证码已提交`);
|
||||
} else {
|
||||
log(`步骤 ${step}:分格验证码页面未找到可点击提交按钮,继续等待页面自动推进。`, 'info');
|
||||
@@ -4906,7 +5054,9 @@ async function fillVerificationCode(step, payload) {
|
||||
throw new Error('未找到验证码输入框。URL: ' + location.href);
|
||||
}
|
||||
|
||||
fillInput(codeInput, code);
|
||||
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'fill', label: 'verification-code' }, async () => {
|
||||
fillInput(codeInput, code);
|
||||
});
|
||||
log(`步骤 ${step}:验证码已填写`);
|
||||
|
||||
// Submit
|
||||
@@ -4915,7 +5065,9 @@ async function fillVerificationCode(step, payload) {
|
||||
|
||||
if (submitBtn) {
|
||||
await humanPause(450, 1200);
|
||||
simulateClick(submitBtn);
|
||||
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => {
|
||||
simulateClick(submitBtn);
|
||||
});
|
||||
log(`步骤 ${step}:验证码已提交`);
|
||||
} else {
|
||||
log(`步骤 ${step}:未找到可提交的验证码按钮,先等待页面自动推进或反馈结果。`, 'warn');
|
||||
@@ -5199,6 +5351,13 @@ async function waitForPhoneLoginEntrySwitchTransition(timeout = 10000) {
|
||||
}
|
||||
|
||||
async function step6OpenLoginEntry(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
const preferPhoneLogin = String(payload?.loginIdentifierType || '').trim() === 'phone' || (!payload?.email && payload?.phoneNumber);
|
||||
@@ -5215,7 +5374,9 @@ async function step6OpenLoginEntry(payload, snapshot) {
|
||||
|
||||
log(`检测到登录入口页,正在点击 "${getActionText(trigger).slice(0, 80)}"...`, 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
await humanPause(350, 900);
|
||||
simulateClick(trigger);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'open-login-entry' }, async () => {
|
||||
simulateClick(trigger);
|
||||
});
|
||||
const nextSnapshot = await waitForLoginEntryOpenTransition();
|
||||
|
||||
if (nextSnapshot.state === 'email_page') {
|
||||
@@ -5267,6 +5428,13 @@ async function step6OpenLoginEntry(payload, snapshot) {
|
||||
}
|
||||
|
||||
async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
const switchTrigger = snapshot?.switchTrigger || findOneTimeCodeLoginTrigger();
|
||||
if (!switchTrigger || !isActionEnabled(switchTrigger)) {
|
||||
@@ -5278,7 +5446,9 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
|
||||
log('已检测到一次性验证码登录入口,准备切换...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
const loginVerificationRequestedAt = Date.now();
|
||||
await humanPause(350, 900);
|
||||
simulateClick(switchTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-one-time-code-login' }, async () => {
|
||||
simulateClick(switchTrigger);
|
||||
});
|
||||
log('已点击一次性验证码登录', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
await sleep(1200);
|
||||
const result = await waitForStep6SwitchTransition(loginVerificationRequestedAt, 10000, { visibleStep });
|
||||
@@ -5305,6 +5475,13 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
|
||||
}
|
||||
|
||||
async function step6LoginFromPhonePage(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
const phoneInput = currentSnapshot.phoneInput || getLoginPhoneInput();
|
||||
@@ -5358,7 +5535,9 @@ async function step6LoginFromPhonePage(payload, snapshot) {
|
||||
|
||||
await sleep(500);
|
||||
const verifiedPhoneInput = fillResult.input || phoneInput;
|
||||
const hiddenSync = syncPhoneHiddenFormValue(verifiedPhoneInput, { phoneNumber, dialCode, inputValue });
|
||||
const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-pre-submit-hidden-sync' }, async () => (
|
||||
syncPhoneHiddenFormValue(verifiedPhoneInput, { phoneNumber, dialCode, inputValue })
|
||||
));
|
||||
const submitButton = getLoginSubmitButton({ allowDisabled: true }) || currentSnapshot.submitButton;
|
||||
const preSubmitRenderedValue = getPhoneInputRenderedValue(verifiedPhoneInput);
|
||||
const preSubmitHiddenInput = hiddenSync?.input || getPhoneHiddenValueInput(verifiedPhoneInput);
|
||||
@@ -5416,6 +5595,13 @@ async function step6LoginFromPhonePage(payload, snapshot) {
|
||||
}
|
||||
|
||||
async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
let currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
let phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger();
|
||||
@@ -5427,7 +5613,9 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
|
||||
stepKey: 'oauth-login',
|
||||
});
|
||||
await humanPause(350, 900);
|
||||
simulateClick(moreOptionsTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'login-more-options' }, async () => {
|
||||
simulateClick(moreOptionsTrigger);
|
||||
});
|
||||
await sleep(800);
|
||||
currentSnapshot = normalizeStep6Snapshot(inspectLoginAuthState());
|
||||
phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger();
|
||||
@@ -5442,7 +5630,9 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
|
||||
|
||||
log(`步骤 ${visibleStep}:当前在邮箱入口,正在切换到手机号登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
await humanPause(350, 900);
|
||||
simulateClick(phoneEntryTrigger);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-phone-login' }, async () => {
|
||||
simulateClick(phoneEntryTrigger);
|
||||
});
|
||||
const nextSnapshot = normalizeStep6Snapshot(await waitForPhoneLoginEntrySwitchTransition(20000));
|
||||
if (nextSnapshot.state === 'phone_entry_page') {
|
||||
return step6LoginFromPhonePage(payload, nextSnapshot);
|
||||
@@ -5491,6 +5681,13 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
|
||||
}
|
||||
|
||||
async function step6LoginFromPasswordPage(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
const hasPassword = Boolean(String(payload?.password || '').trim());
|
||||
@@ -5509,7 +5706,9 @@ async function step6LoginFromPasswordPage(payload, snapshot) {
|
||||
|
||||
log('已进入密码页,准备填写密码...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
await humanPause(550, 1450);
|
||||
fillInput(currentSnapshot.passwordInput, payload.password);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-password' }, async () => {
|
||||
fillInput(currentSnapshot.passwordInput, payload.password);
|
||||
});
|
||||
log('已填写密码', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
|
||||
await sleep(500);
|
||||
@@ -5560,6 +5759,13 @@ async function step6LoginFromPasswordPage(payload, snapshot) {
|
||||
}
|
||||
|
||||
async function step6LoginFromEmailPage(payload, snapshot) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
|
||||
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
|
||||
if (String(payload?.loginIdentifierType || '').trim() === 'phone' && payload?.phoneNumber) {
|
||||
@@ -5572,7 +5778,9 @@ async function step6LoginFromEmailPage(payload, snapshot) {
|
||||
|
||||
if ((emailInput.value || '').trim() !== payload.email) {
|
||||
await humanPause(500, 1400);
|
||||
fillInput(emailInput, payload.email);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-email' }, async () => {
|
||||
fillInput(emailInput, payload.email);
|
||||
});
|
||||
log('已填写邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
} else {
|
||||
log('邮箱已在输入框中,准备提交...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
|
||||
@@ -5783,6 +5991,13 @@ async function waitForAddEmailSubmitOutcome(timeout = 45000) {
|
||||
}
|
||||
|
||||
async function submitAddEmailAndContinue(payload = {}) {
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
const email = String(payload.email || '').trim().toLowerCase();
|
||||
if (!email) {
|
||||
throw new Error('未提供邮箱地址,无法添加邮箱。');
|
||||
@@ -5795,7 +6010,9 @@ async function submitAddEmailAndContinue(payload = {}) {
|
||||
}
|
||||
|
||||
await humanPause(500, 1400);
|
||||
fillInput(emailInput, email);
|
||||
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'add-email' }, async () => {
|
||||
fillInput(emailInput, email);
|
||||
});
|
||||
log(`步骤 8:已填写邮箱:${email}`);
|
||||
|
||||
await sleep(500);
|
||||
@@ -6087,6 +6304,13 @@ async function waitForCombinedSignupVerificationProfilePage(timeout = 2500) {
|
||||
async function step5_fillNameBirthday(payload) {
|
||||
const { firstName, lastName, age, year, month, day, prefillOnly = false } = payload;
|
||||
if (!firstName || !lastName) throw new Error('未提供姓名数据。');
|
||||
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
|
||||
? getOperationDelayRunner()
|
||||
: async (metadata, operation) => {
|
||||
const rootScope = typeof window !== 'undefined' ? window : globalThis;
|
||||
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
|
||||
return typeof gate === 'function' ? gate(metadata, operation) : operation();
|
||||
};
|
||||
|
||||
const resolvedAge = age ?? (year ? new Date().getFullYear() - Number(year) : null);
|
||||
const hasBirthdayData = [year, month, day].every(value => value != null && !Number.isNaN(Number(value)));
|
||||
@@ -6113,7 +6337,9 @@ async function step5_fillNameBirthday(payload) {
|
||||
throw new Error('未找到姓名输入框。URL: ' + location.href);
|
||||
}
|
||||
await humanPause(500, 1300);
|
||||
fillInput(nameInput, fullName);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-name' }, async () => {
|
||||
fillInput(nameInput, fullName);
|
||||
});
|
||||
log(`步骤 5:姓名已填写:${fullName}`);
|
||||
|
||||
let birthdayMode = false;
|
||||
@@ -6183,11 +6409,17 @@ async function step5_fillNameBirthday(payload) {
|
||||
|
||||
log('步骤 5:检测到 React Aria 下拉生日字段,正在填写生日...');
|
||||
await humanPause(450, 1100);
|
||||
await setReactAriaBirthdaySelect(yearReactSelect, year);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-year' }, async () => {
|
||||
await setReactAriaBirthdaySelect(yearReactSelect, year);
|
||||
});
|
||||
await humanPause(250, 650);
|
||||
await setReactAriaBirthdaySelect(monthReactSelect, month);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-month' }, async () => {
|
||||
await setReactAriaBirthdaySelect(monthReactSelect, month);
|
||||
});
|
||||
await humanPause(250, 650);
|
||||
await setReactAriaBirthdaySelect(dayReactSelect, day);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-day' }, async () => {
|
||||
await setReactAriaBirthdaySelect(dayReactSelect, day);
|
||||
});
|
||||
|
||||
if (hiddenBirthday) {
|
||||
const start = Date.now();
|
||||
@@ -6228,20 +6460,28 @@ async function step5_fillNameBirthday(payload) {
|
||||
}
|
||||
|
||||
await humanPause(450, 1100);
|
||||
await setSpinButton(yearSpinner, year);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-year' }, async () => {
|
||||
await setSpinButton(yearSpinner, year);
|
||||
});
|
||||
await humanPause(250, 650);
|
||||
await setSpinButton(monthSpinner, String(month).padStart(2, '0'));
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-month' }, async () => {
|
||||
await setSpinButton(monthSpinner, String(month).padStart(2, '0'));
|
||||
});
|
||||
await humanPause(250, 650);
|
||||
await setSpinButton(daySpinner, String(day).padStart(2, '0'));
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-day' }, async () => {
|
||||
await setSpinButton(daySpinner, String(day).padStart(2, '0'));
|
||||
});
|
||||
log(`步骤 5:生日已填写:${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
|
||||
}
|
||||
|
||||
const hiddenBirthday = document.querySelector('input[name="birthday"]');
|
||||
if (hiddenBirthday) {
|
||||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
hiddenBirthday.value = dateStr;
|
||||
hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'hidden-sync', label: 'profile-dom-sync' }, async () => {
|
||||
hiddenBirthday.value = dateStr;
|
||||
hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
log(`步骤 5:已设置隐藏生日输入框:${dateStr}`);
|
||||
}
|
||||
} else if (ageInput) {
|
||||
@@ -6249,7 +6489,9 @@ async function step5_fillNameBirthday(payload) {
|
||||
throw new Error('检测到年龄字段,但未提供年龄数据。');
|
||||
}
|
||||
await humanPause(500, 1300);
|
||||
fillInput(ageInput, String(resolvedAge));
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday' }, async () => {
|
||||
fillInput(ageInput, String(resolvedAge));
|
||||
});
|
||||
log(`步骤 5:年龄已填写:${resolvedAge}`);
|
||||
} else {
|
||||
throw new Error('未找到生日或年龄输入项。URL: ' + location.href);
|
||||
@@ -6261,15 +6503,19 @@ async function step5_fillNameBirthday(payload) {
|
||||
if (!isStep5CheckboxChecked(allConsentCheckbox)) {
|
||||
const checkboxLabel = allConsentCheckbox.closest('label');
|
||||
await humanPause(500, 1500);
|
||||
if (checkboxLabel && isVisibleElement(checkboxLabel)) {
|
||||
simulateClick(checkboxLabel);
|
||||
} else {
|
||||
simulateClick(allConsentCheckbox);
|
||||
}
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent' }, async () => {
|
||||
if (checkboxLabel && isVisibleElement(checkboxLabel)) {
|
||||
simulateClick(checkboxLabel);
|
||||
} else {
|
||||
simulateClick(allConsentCheckbox);
|
||||
}
|
||||
});
|
||||
await sleep(250);
|
||||
|
||||
if (!isStep5CheckboxChecked(allConsentCheckbox)) {
|
||||
allConsentCheckbox.click();
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent-fallback' }, async () => {
|
||||
allConsentCheckbox.click();
|
||||
});
|
||||
await sleep(250);
|
||||
}
|
||||
|
||||
@@ -6303,7 +6549,9 @@ async function step5_fillNameBirthday(payload) {
|
||||
}
|
||||
|
||||
await humanPause(500, 1300);
|
||||
simulateClick(completeBtn);
|
||||
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'submit', label: 'submit-profile' }, async () => {
|
||||
simulateClick(completeBtn);
|
||||
});
|
||||
|
||||
const completionPayload = getStep5DirectCompletionPayload({ isAgeMode });
|
||||
reportComplete(5, completionPayload);
|
||||
|
||||
Reference in New Issue
Block a user