diff --git a/background.js b/background.js index f7e22a7..6f5cc95 100644 --- a/background.js +++ b/background.js @@ -68,29 +68,56 @@ const NORMAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: false, }) || []; +const NORMAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: false, + signupMethod: 'phone', +}) || NORMAL_STEP_DEFINITIONS; const PLUS_PAYPAL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, plusPaymentMethod: 'paypal', }) || NORMAL_STEP_DEFINITIONS; +const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'paypal', + signupMethod: 'phone', +}) || PLUS_PAYPAL_STEP_DEFINITIONS; const PLUS_GOPAY_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, plusPaymentMethod: 'gopay', }) || PLUS_PAYPAL_STEP_DEFINITIONS; +const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'gopay', + signupMethod: 'phone', +}) || PLUS_GOPAY_STEP_DEFINITIONS; const PLUS_GPC_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', }) || PLUS_GOPAY_STEP_DEFINITIONS; +const PLUS_GPC_PHONE_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getSteps?.({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + plusModeEnabled: true, + plusPaymentMethod: 'gpc-helper', + signupMethod: 'phone', +}) || PLUS_GPC_STEP_DEFINITIONS; const PLUS_STEP_DEFINITIONS = PLUS_PAYPAL_STEP_DEFINITIONS; const ALL_STEP_DEFINITIONS = self.MultiPageStepDefinitions?.getAllSteps?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID, }) || [ ...NORMAL_STEP_DEFINITIONS, + ...NORMAL_PHONE_STEP_DEFINITIONS, ...PLUS_PAYPAL_STEP_DEFINITIONS, + ...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, ...PLUS_GOPAY_STEP_DEFINITIONS, + ...PLUS_GOPAY_PHONE_STEP_DEFINITIONS, ...PLUS_GPC_STEP_DEFINITIONS, + ...PLUS_GPC_PHONE_STEP_DEFINITIONS, ]; const STEP_IDS = Array.from(new Set(ALL_STEP_DEFINITIONS .map((definition) => Number(definition?.id)) @@ -9608,6 +9635,7 @@ const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([ 'platform-verify', ]); const STEP_COMPLETION_SIGNAL_TIMEOUTS_BY_STEP_KEY = new Map([ + ['fill-profile', 150000], ['gopay-subscription-confirm', 1800000], ]); const AUTO_RUN_PRE_EXECUTION_DELAYS_BY_STEP_KEY = new Map([ @@ -10379,8 +10407,9 @@ async function executeNodeAndWait(nodeId, delayAfter = 2000) { const latestState = await getState(); await addLog(`自动运行:节点 ${normalizedNodeId} 已执行返回,当前状态为 ${latestState.nodeStatuses?.[normalizedNodeId] || 'pending'},准备继续后续节点。`, 'info'); } else if (doesNodeUseCompletionSignal(normalizedNodeId, executionState)) { - await addLog(`自动运行:节点 ${normalizedNodeId} 已发起,正在等待完成信号(超时 ${AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS / 1000} 秒)。`, 'info'); - completionPayload = await executeNodeViaCompletionSignal(normalizedNodeId, AUTO_RUN_SIGNAL_COMPLETION_TIMEOUT_MS); + const completionSignalTimeoutMs = getNodeCompletionSignalTimeoutMs(normalizedNodeId, executionState); + await addLog(`自动运行:节点 ${normalizedNodeId} 已发起,正在等待完成信号(超时 ${Math.round(completionSignalTimeoutMs / 1000)} 秒)。`, 'info'); + completionPayload = await executeNodeViaCompletionSignal(normalizedNodeId, completionSignalTimeoutMs); await addLog(`自动运行:节点 ${normalizedNodeId} 已收到完成信号,准备继续后续节点。`, 'info'); } else { await executeNode(normalizedNodeId); @@ -10391,7 +10420,7 @@ async function executeNodeAndWait(nodeId, delayAfter = 2000) { if (signupTabId) { await addLog('自动运行:填写资料节点已收到完成信号,正在等待当前页面完成加载并稳定...', 'info'); await waitForTabStableComplete(signupTabId, { - timeoutMs: 30000, + timeoutMs: 120000, retryDelayMs: 300, stableMs: 1000, initialDelayMs: 800, @@ -12560,23 +12589,31 @@ async function acquireTopLevelAuthChainExecution(step, state = {}) { } const normalStepRegistry = buildStepRegistry(NORMAL_STEP_DEFINITIONS); +const normalPhoneStepRegistry = buildStepRegistry(NORMAL_PHONE_STEP_DEFINITIONS); const plusPayPalStepRegistry = buildStepRegistry(PLUS_PAYPAL_STEP_DEFINITIONS); +const plusPayPalPhoneStepRegistry = buildStepRegistry(PLUS_PAYPAL_PHONE_STEP_DEFINITIONS); const plusGoPayStepRegistry = buildStepRegistry(PLUS_GOPAY_STEP_DEFINITIONS); +const plusGoPayPhoneStepRegistry = buildStepRegistry(PLUS_GOPAY_PHONE_STEP_DEFINITIONS); const plusGpcStepRegistry = buildStepRegistry(PLUS_GPC_STEP_DEFINITIONS); +const plusGpcPhoneStepRegistry = buildStepRegistry(PLUS_GPC_PHONE_STEP_DEFINITIONS); function getStepRegistryForState(state = {}) { const activeFlowId = String(state?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; if (activeFlowId !== DEFAULT_ACTIVE_FLOW_ID) { throw new Error(`当前尚未注册 flow=${activeFlowId} 的步骤执行器。`); } + const signupMethod = getSignupMethodForStepDefinitions(state); if (!isPlusModeState(state)) { - return normalStepRegistry; + return signupMethod === SIGNUP_METHOD_PHONE ? normalPhoneStepRegistry : normalStepRegistry; } const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod); if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { - return plusGpcStepRegistry; + return signupMethod === SIGNUP_METHOD_PHONE ? plusGpcPhoneStepRegistry : plusGpcStepRegistry; } - return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? plusGoPayStepRegistry : plusPayPalStepRegistry; + if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) { + return signupMethod === SIGNUP_METHOD_PHONE ? plusGoPayPhoneStepRegistry : plusGoPayStepRegistry; + } + return signupMethod === SIGNUP_METHOD_PHONE ? plusPayPalPhoneStepRegistry : plusPayPalStepRegistry; } async function requestOAuthUrlFromPanel(state, options = {}) { diff --git a/background/tab-runtime.js b/background/tab-runtime.js index 765f0e7..29e8ced 100644 --- a/background/tab-runtime.js +++ b/background/tab-runtime.js @@ -593,6 +593,7 @@ function getContentScriptResponseTimeoutMs(message) { if (!message || typeof message !== 'object') return 30000; + if (message.type === 'EXECUTE_NODE' && String(message.nodeId || message.payload?.nodeId || '').trim() === 'fill-profile') return 150000; if (message.type === 'EXECUTE_NODE' && String(message.nodeId || message.payload?.nodeId || '').trim() === 'wait-registration-success') return 75000; if (message.type === 'POLL_EMAIL') { const maxAttempts = Math.max(1, Number(message.payload?.maxAttempts) || 1); diff --git a/content/signup-page.js b/content/signup-page.js index 1251af0..b9199ec 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -6753,7 +6753,7 @@ function installStep5NavigationCompletionReporter(completeOnce) { async function waitForStep5SubmitOutcome(options = {}) { const { - timeoutMs = 45000, + timeoutMs = 120000, maxAuthRetryRecoveries = 2, maxSubmitClicks = 3, retryClickIntervalMs = 3500, diff --git a/tests/background-step-registry.test.js b/tests/background-step-registry.test.js index a836d5f..76402bd 100644 --- a/tests/background-step-registry.test.js +++ b/tests/background-step-registry.test.js @@ -12,10 +12,26 @@ test('background imports node registry and shared workflow definitions', () => { assert.match(source, /buildNodeRegistry\(definitions/); assert.match(source, /PLUS_PAYPAL_STEP_DEFINITIONS/); assert.match(source, /PLUS_GOPAY_STEP_DEFINITIONS/); + assert.match(source, /NORMAL_PHONE_STEP_DEFINITIONS/); + assert.match(source, /PLUS_PAYPAL_PHONE_STEP_DEFINITIONS/); + assert.match(source, /PLUS_GOPAY_PHONE_STEP_DEFINITIONS/); + assert.match(source, /PLUS_GPC_PHONE_STEP_DEFINITIONS/); assert.match(source, /plusPayPalStepRegistry/); assert.match(source, /plusGoPayStepRegistry/); - assert.match(source, /normalizePlusPaymentMethod\(state\?\.plusPaymentMethod\) === PLUS_PAYMENT_METHOD_GOPAY/); + assert.match(source, /normalPhoneStepRegistry/); + assert.match(source, /plusPayPalPhoneStepRegistry/); + assert.match(source, /plusGoPayPhoneStepRegistry/); + assert.match(source, /plusGpcPhoneStepRegistry/); + assert.match(source, /const signupMethod = getSignupMethodForStepDefinitions\(state\);/); + assert.match(source, /signupMethod === SIGNUP_METHOD_PHONE \? normalPhoneStepRegistry : normalStepRegistry/); + assert.match(source, /const paymentMethod = normalizePlusPaymentMethod\(state\?\.plusPaymentMethod\);/); + assert.match(source, /paymentMethod === PLUS_PAYMENT_METHOD_GOPAY/); + assert.match(source, /signupMethod === SIGNUP_METHOD_PHONE \? plusGoPayPhoneStepRegistry : plusGoPayStepRegistry/); + assert.match(source, /signupMethod === SIGNUP_METHOD_PHONE \? plusPayPalPhoneStepRegistry : plusPayPalStepRegistry/); + assert.match(source, /signupMethod === SIGNUP_METHOD_PHONE \? plusGpcPhoneStepRegistry : plusGpcStepRegistry/); assert.match(source, /activeStepRegistry\.executeNode\(normalizedNodeId,\s*\{/); + assert.match(source, /'bind-email': \(state\) => step8Executor\.executeBindEmail\(state\)/); + assert.match(source, /'fetch-bind-email-code': \(state\) => step8Executor\.executeFetchBindEmailCode\(state\)/); assert.match(source, /background\/steps\/create-plus-checkout\.js/); assert.match(source, /background\/steps\/fill-plus-checkout\.js/); assert.match(source, /background\/steps\/gopay-manual-confirm\.js/); diff --git a/tests/background-tab-runtime-module.test.js b/tests/background-tab-runtime-module.test.js index 6a31394..6647c69 100644 --- a/tests/background-tab-runtime-module.test.js +++ b/tests/background-tab-runtime-module.test.js @@ -121,6 +121,37 @@ test('tab runtime caps per-attempt response timeout to the remaining resilient t ); }); +test('tab runtime gives step 5 profile submit enough response time for slow page transitions', () => { + const source = fs.readFileSync('background/tab-runtime.js', 'utf8'); + const globalScope = {}; + const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope); + + const runtime = api.createTabRuntime({ + LOG_PREFIX: '[test]', + addLog: async () => {}, + chrome: { + tabs: { + get: async () => ({ id: 1, url: 'https://example.com', status: 'complete' }), + query: async () => [], + }, + }, + getSourceLabel: (sourceName) => sourceName || 'unknown', + getState: async () => ({ tabRegistry: {}, sourceLastUrls: {} }), + matchesSourceUrlFamily: () => false, + setState: async () => {}, + throwIfStopped: () => {}, + }); + + assert.equal( + runtime.getContentScriptResponseTimeoutMs({ type: 'EXECUTE_NODE', nodeId: 'fill-profile' }), + 150000 + ); + assert.equal( + runtime.getContentScriptResponseTimeoutMs({ type: 'EXECUTE_NODE', payload: { nodeId: 'fill-profile' } }), + 150000 + ); +}); + test('tab runtime waitForTabComplete waits until tab status becomes complete', async () => { const source = fs.readFileSync('background/tab-runtime.js', 'utf8'); const globalScope = {};