feat: 添加邮箱验证页面处理逻辑,优化步骤 2 跳过密码步骤的逻辑,更新相关测试用例
This commit is contained in:
+3
-1
@@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
/.github
|
/.github
|
||||||
/_metadata/
|
/_metadata/
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
/data/account-run-history.txt
|
||||||
+34
-2
@@ -3274,6 +3274,16 @@ function isSignupPasswordPageUrl(rawUrl) {
|
|||||||
&& /\/create-account\/password(?:[/?#]|$)/i.test(parsed.pathname || '');
|
&& /\/create-account\/password(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSignupEmailVerificationPageUrl(rawUrl) {
|
||||||
|
if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupEmailVerificationPageUrl) {
|
||||||
|
return navigationUtils.isSignupEmailVerificationPageUrl(rawUrl);
|
||||||
|
}
|
||||||
|
const parsed = parseUrlSafely(rawUrl);
|
||||||
|
if (!parsed) return false;
|
||||||
|
return isSignupPageHost(parsed.hostname)
|
||||||
|
&& /\/email-verification(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||||
|
}
|
||||||
|
|
||||||
function is163MailHost(hostname = '') {
|
function is163MailHost(hostname = '') {
|
||||||
if (typeof navigationUtils !== 'undefined' && navigationUtils?.is163MailHost) {
|
if (typeof navigationUtils !== 'undefined' && navigationUtils?.is163MailHost) {
|
||||||
return navigationUtils.is163MailHost(hostname);
|
return navigationUtils.is163MailHost(hostname);
|
||||||
@@ -4306,6 +4316,17 @@ async function handleStepData(step, payload) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 2:
|
||||||
|
if (payload.email) await setEmailState(payload.email);
|
||||||
|
if (payload.skippedPasswordStep) {
|
||||||
|
const latestState = await getState();
|
||||||
|
const step3Status = latestState.stepStatuses?.[3];
|
||||||
|
if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') {
|
||||||
|
await setStepStatus(3, 'skipped');
|
||||||
|
await addLog('步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。', 'warn');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (payload.email) await setEmailState(payload.email);
|
if (payload.email) await setEmailState(payload.email);
|
||||||
if (payload.signupVerificationRequestedAt) {
|
if (payload.signupVerificationRequestedAt) {
|
||||||
@@ -4994,13 +5015,19 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (startStep <= 3) {
|
if (startStep <= 3) {
|
||||||
|
const latestState = await getState();
|
||||||
|
const step3Status = latestState.stepStatuses?.[3] || 'pending';
|
||||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:阶段 2,填写密码、验证、登录并完成授权(第 ${attemptRuns} 次尝试)===`, 'info');
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:阶段 2,填写密码、验证、登录并完成授权(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||||
await broadcastAutoRunStatus('running', {
|
await broadcastAutoRunStatus('running', {
|
||||||
currentRun: targetRun,
|
currentRun: targetRun,
|
||||||
totalRuns,
|
totalRuns,
|
||||||
attemptRun: attemptRuns,
|
attemptRun: attemptRuns,
|
||||||
});
|
});
|
||||||
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
if (isStepDoneStatus(step3Status)) {
|
||||||
|
await addLog(`自动运行:步骤 3 当前状态为 ${step3Status},将直接继续后续流程。`, 'info');
|
||||||
|
} else {
|
||||||
|
await executeStepAndWait(3, AUTO_STEP_DELAYS[3]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续执行剩余流程(第 ${attemptRuns} 次尝试)===`, 'info');
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:继续执行剩余流程(第 ${attemptRuns} 次尝试)===`, 'info');
|
||||||
}
|
}
|
||||||
@@ -5177,6 +5204,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
getTabId,
|
getTabId,
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
|
isSignupEmailVerificationPageUrl,
|
||||||
isHotmailProvider,
|
isHotmailProvider,
|
||||||
isLuckmailProvider,
|
isLuckmailProvider,
|
||||||
isSignupPasswordPageUrl,
|
isSignupPasswordPageUrl,
|
||||||
@@ -5228,7 +5256,7 @@ const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({
|
|||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
ensureSignupEntryPageReady,
|
ensureSignupEntryPageReady,
|
||||||
ensureSignupPasswordPageReadyInTab,
|
ensureSignupPostEmailPageReadyInTab,
|
||||||
getTabId,
|
getTabId,
|
||||||
isTabAlive,
|
isTabAlive,
|
||||||
resolveSignupEmailForFlow,
|
resolveSignupEmailForFlow,
|
||||||
@@ -5457,6 +5485,10 @@ async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {})
|
|||||||
return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
|
return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
|
||||||
|
return signupFlowHelpers.ensureSignupPostEmailPageReadyInTab(tabId, step, options);
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveSignupEmailForFlow(state) {
|
async function resolveSignupEmailForFlow(state) {
|
||||||
return signupFlowHelpers.resolveSignupEmailForFlow(state);
|
return signupFlowHelpers.resolveSignupEmailForFlow(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,19 @@
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 2:
|
||||||
|
if (payload.email) {
|
||||||
|
await setEmailState(payload.email);
|
||||||
|
}
|
||||||
|
if (payload.skippedPasswordStep) {
|
||||||
|
const latestState = await getState();
|
||||||
|
const step3Status = latestState.stepStatuses?.[3];
|
||||||
|
if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') {
|
||||||
|
await setStepStatus(3, 'skipped');
|
||||||
|
await addLog('步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。', 'warn');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (payload.email) await setEmailState(payload.email);
|
if (payload.email) await setEmailState(payload.email);
|
||||||
if (payload.signupVerificationRequestedAt) {
|
if (payload.signupVerificationRequestedAt) {
|
||||||
|
|||||||
@@ -52,6 +52,13 @@
|
|||||||
&& /\/create-account\/password(?:[/?#]|$)/i.test(parsed.pathname || '');
|
&& /\/create-account\/password(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSignupEmailVerificationPageUrl(rawUrl) {
|
||||||
|
const parsed = parseUrlSafely(rawUrl);
|
||||||
|
if (!parsed) return false;
|
||||||
|
return isSignupPageHost(parsed.hostname)
|
||||||
|
&& /\/email-verification(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||||
|
}
|
||||||
|
|
||||||
function is163MailHost(hostname = '') {
|
function is163MailHost(hostname = '') {
|
||||||
return hostname === 'mail.163.com'
|
return hostname === 'mail.163.com'
|
||||||
|| hostname.endsWith('.mail.163.com')
|
|| hostname.endsWith('.mail.163.com')
|
||||||
@@ -154,6 +161,7 @@
|
|||||||
is163MailHost,
|
is163MailHost,
|
||||||
isLocalCpaUrl,
|
isLocalCpaUrl,
|
||||||
isLocalhostOAuthCallbackUrl,
|
isLocalhostOAuthCallbackUrl,
|
||||||
|
isSignupEmailVerificationPageUrl,
|
||||||
isSignupEntryHost,
|
isSignupEntryHost,
|
||||||
isSignupPageHost,
|
isSignupPageHost,
|
||||||
isSignupPasswordPageUrl,
|
isSignupPasswordPageUrl,
|
||||||
|
|||||||
@@ -3,18 +3,16 @@
|
|||||||
})(typeof self !== 'undefined' ? self : globalThis, function createSignupFlowHelpersModule() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createSignupFlowHelpersModule() {
|
||||||
function createSignupFlowHelpers(deps = {}) {
|
function createSignupFlowHelpers(deps = {}) {
|
||||||
const {
|
const {
|
||||||
addLog,
|
|
||||||
buildGeneratedAliasEmail,
|
buildGeneratedAliasEmail,
|
||||||
chrome,
|
chrome,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
ensureHotmailAccountForFlow,
|
ensureHotmailAccountForFlow,
|
||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
getTabId,
|
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
isHotmailProvider,
|
isHotmailProvider,
|
||||||
isLuckmailProvider,
|
isLuckmailProvider,
|
||||||
|
isSignupEmailVerificationPageUrl,
|
||||||
isSignupPasswordPageUrl,
|
isSignupPasswordPageUrl,
|
||||||
isTabAlive,
|
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
@@ -60,17 +58,46 @@
|
|||||||
return { tabId, result: result || {} };
|
return { tabId, result: result || {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
|
function resolveSignupPostEmailState(rawUrl) {
|
||||||
|
if (isSignupPasswordPageUrl(rawUrl)) {
|
||||||
|
return 'password_page';
|
||||||
|
}
|
||||||
|
if (isSignupEmailVerificationPageUrl(rawUrl)) {
|
||||||
|
return 'verification_page';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
|
||||||
const { skipUrlWait = false } = options;
|
const { skipUrlWait = false } = options;
|
||||||
|
let landingUrl = '';
|
||||||
|
let landingState = '';
|
||||||
|
|
||||||
if (!skipUrlWait) {
|
if (!skipUrlWait) {
|
||||||
const matchedTab = await waitForTabUrlMatch(tabId, (url) => isSignupPasswordPageUrl(url), {
|
const matchedTab = await waitForTabUrlMatch(tabId, (url) => Boolean(resolveSignupPostEmailState(url)), {
|
||||||
timeoutMs: 45000,
|
timeoutMs: 45000,
|
||||||
retryDelayMs: 300,
|
retryDelayMs: 300,
|
||||||
});
|
});
|
||||||
if (!matchedTab) {
|
if (!matchedTab) {
|
||||||
throw new Error('等待进入密码页超时,请检查邮箱提交后页面是否仍停留在官网或邮箱页。');
|
throw new Error('等待邮箱提交后的页面跳转超时,请检查页面是否仍停留在邮箱输入页。');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
landingUrl = matchedTab.url || '';
|
||||||
|
landingState = resolveSignupPostEmailState(landingUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!landingState) {
|
||||||
|
try {
|
||||||
|
const currentTab = await chrome.tabs.get(tabId);
|
||||||
|
landingUrl = landingUrl || currentTab?.url || '';
|
||||||
|
landingState = resolveSignupPostEmailState(landingUrl);
|
||||||
|
} catch {
|
||||||
|
landingUrl = landingUrl || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!landingState) {
|
||||||
|
throw new Error(`邮箱提交后未能识别当前页面,既不是密码页也不是邮箱验证码页。URL: ${landingUrl || 'unknown'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ensureContentScriptReadyOnTab('signup-page', tabId, {
|
await ensureContentScriptReadyOnTab('signup-page', tabId, {
|
||||||
@@ -78,9 +105,19 @@
|
|||||||
injectSource: 'signup-page',
|
injectSource: 'signup-page',
|
||||||
timeoutMs: 45000,
|
timeoutMs: 45000,
|
||||||
retryDelayMs: 900,
|
retryDelayMs: 900,
|
||||||
logMessage: `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
|
logMessage: landingState === 'verification_page'
|
||||||
|
? `步骤 ${step}:邮箱验证码页仍在加载,正在等待页面恢复...`
|
||||||
|
: `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (landingState === 'verification_page') {
|
||||||
|
return {
|
||||||
|
ready: true,
|
||||||
|
state: landingState,
|
||||||
|
url: landingUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const result = await sendToContentScriptResilient('signup-page', {
|
const result = await sendToContentScriptResilient('signup-page', {
|
||||||
type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
|
type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY',
|
||||||
step,
|
step,
|
||||||
@@ -96,7 +133,20 @@
|
|||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result || {};
|
return {
|
||||||
|
...(result || {}),
|
||||||
|
ready: true,
|
||||||
|
state: landingState,
|
||||||
|
url: landingUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
|
||||||
|
const result = await ensureSignupPostEmailPageReadyInTab(tabId, step, options);
|
||||||
|
if (result.state !== 'password_page') {
|
||||||
|
throw new Error(`当前页面不是密码页,实际落地为 ${result.state || 'unknown'}。URL: ${result.url || 'unknown'}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveSignupEmailForFlow(state) {
|
async function resolveSignupEmailForFlow(state) {
|
||||||
@@ -128,6 +178,7 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
ensureSignupEntryPageReady,
|
ensureSignupEntryPageReady,
|
||||||
|
ensureSignupPostEmailPageReadyInTab,
|
||||||
ensureSignupPasswordPageReadyInTab,
|
ensureSignupPasswordPageReadyInTab,
|
||||||
openSignupEntryTab,
|
openSignupEntryTab,
|
||||||
resolveSignupEmailForFlow,
|
resolveSignupEmailForFlow,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
ensureSignupEntryPageReady,
|
ensureSignupEntryPageReady,
|
||||||
ensureSignupPasswordPageReadyInTab,
|
ensureSignupPostEmailPageReadyInTab,
|
||||||
getTabId,
|
getTabId,
|
||||||
isTabAlive,
|
isTabAlive,
|
||||||
resolveSignupEmailForFlow,
|
resolveSignupEmailForFlow,
|
||||||
@@ -50,13 +50,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!step2Result?.alreadyOnPasswordPage) {
|
if (!step2Result?.alreadyOnPasswordPage) {
|
||||||
await addLog(`步骤 2:邮箱 ${resolvedEmail} 已提交,正在等待进入密码页...`);
|
await addLog(`步骤 2:邮箱 ${resolvedEmail} 已提交,正在等待页面加载并确认下一步入口...`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ensureSignupPasswordPageReadyInTab(signupTabId, 2, {
|
const landingResult = await ensureSignupPostEmailPageReadyInTab(signupTabId, 2, {
|
||||||
skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage),
|
skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage),
|
||||||
});
|
});
|
||||||
await completeStepFromBackground(2, {});
|
|
||||||
|
await completeStepFromBackground(2, {
|
||||||
|
email: resolvedEmail,
|
||||||
|
nextSignupState: landingResult?.state || 'password_page',
|
||||||
|
nextSignupUrl: landingResult?.url || step2Result?.url || '',
|
||||||
|
skippedPasswordStep: landingResult?.state === 'verification_page',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { executeStep2 };
|
return { executeStep2 };
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
|
||||||
|
|
||||||
|
function createRouter(overrides = {}) {
|
||||||
|
const events = {
|
||||||
|
logs: [],
|
||||||
|
stepStatuses: [],
|
||||||
|
emailStates: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = api.createMessageRouter({
|
||||||
|
addLog: async (message, level) => {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
},
|
||||||
|
appendAccountRunRecord: async () => null,
|
||||||
|
batchUpdateLuckmailPurchases: async () => {},
|
||||||
|
buildLocalhostCleanupPrefix: () => '',
|
||||||
|
buildLuckmailSessionSettingsPayload: () => ({}),
|
||||||
|
buildPersistentSettingsPayload: () => ({}),
|
||||||
|
broadcastDataUpdate: () => {},
|
||||||
|
cancelScheduledAutoRun: async () => {},
|
||||||
|
checkIcloudSession: async () => {},
|
||||||
|
clearAutoRunTimerAlarm: async () => {},
|
||||||
|
clearLuckmailRuntimeState: async () => {},
|
||||||
|
clearStopRequest: () => {},
|
||||||
|
closeLocalhostCallbackTabs: async () => {},
|
||||||
|
closeTabsByUrlPrefix: async () => {},
|
||||||
|
deleteHotmailAccount: async () => {},
|
||||||
|
deleteHotmailAccounts: async () => {},
|
||||||
|
deleteIcloudAlias: async () => {},
|
||||||
|
deleteUsedIcloudAliases: async () => {},
|
||||||
|
disableUsedLuckmailPurchases: async () => {},
|
||||||
|
doesStepUseCompletionSignal: () => false,
|
||||||
|
ensureManualInteractionAllowed: async () => ({}),
|
||||||
|
executeStep: async () => {},
|
||||||
|
executeStepViaCompletionSignal: async () => {},
|
||||||
|
exportSettingsBundle: async () => ({}),
|
||||||
|
fetchGeneratedEmail: async () => '',
|
||||||
|
finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
|
||||||
|
findHotmailAccount: async () => null,
|
||||||
|
flushCommand: async () => {},
|
||||||
|
getCurrentLuckmailPurchase: () => null,
|
||||||
|
getPendingAutoRunTimerPlan: () => null,
|
||||||
|
getSourceLabel: () => '',
|
||||||
|
getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
|
||||||
|
getStopRequested: () => false,
|
||||||
|
handleAutoRunLoopUnhandledError: async () => {},
|
||||||
|
importSettingsBundle: async () => {},
|
||||||
|
invalidateDownstreamAfterStepRestart: async () => {},
|
||||||
|
isAutoRunLockedState: () => false,
|
||||||
|
isHotmailProvider: () => false,
|
||||||
|
isLocalhostOAuthCallbackUrl: () => true,
|
||||||
|
isLuckmailProvider: () => false,
|
||||||
|
isStopError: () => false,
|
||||||
|
launchAutoRunTimerPlan: async () => {},
|
||||||
|
listIcloudAliases: async () => [],
|
||||||
|
listLuckmailPurchasesForManagement: async () => [],
|
||||||
|
normalizeHotmailAccounts: (items) => items,
|
||||||
|
normalizeRunCount: (value) => value,
|
||||||
|
AUTO_RUN_TIMER_KIND_SCHEDULED_START: 'scheduled',
|
||||||
|
notifyStepComplete: () => {},
|
||||||
|
notifyStepError: () => {},
|
||||||
|
patchHotmailAccount: async () => {},
|
||||||
|
registerTab: async () => {},
|
||||||
|
requestStop: async () => {},
|
||||||
|
resetState: async () => {},
|
||||||
|
resumeAutoRun: async () => {},
|
||||||
|
scheduleAutoRun: async () => {},
|
||||||
|
selectLuckmailPurchase: async () => {},
|
||||||
|
setCurrentHotmailAccount: async () => {},
|
||||||
|
setEmailState: async (email) => {
|
||||||
|
events.emailStates.push(email);
|
||||||
|
},
|
||||||
|
setEmailStateSilently: async () => {},
|
||||||
|
setIcloudAliasPreservedState: async () => {},
|
||||||
|
setIcloudAliasUsedState: async () => {},
|
||||||
|
setLuckmailPurchaseDisabledState: async () => {},
|
||||||
|
setLuckmailPurchasePreservedState: async () => {},
|
||||||
|
setLuckmailPurchaseUsedState: async () => {},
|
||||||
|
setPersistentSettings: async () => {},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async (step, status) => {
|
||||||
|
events.stepStatuses.push({ step, status });
|
||||||
|
},
|
||||||
|
skipAutoRunCountdown: async () => false,
|
||||||
|
skipStep: async () => {},
|
||||||
|
startAutoRunLoop: async () => {},
|
||||||
|
syncHotmailAccounts: async () => {},
|
||||||
|
testHotmailAccountMailAccess: async () => {},
|
||||||
|
upsertHotmailAccount: async () => {},
|
||||||
|
verifyHotmailAccount: async () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { router, events };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('message router skips step 3 when step 2 lands on verification page', async () => {
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state: { stepStatuses: { 3: 'pending' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(2, {
|
||||||
|
email: 'user@example.com',
|
||||||
|
skippedPasswordStep: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.emailStates, ['user@example.com']);
|
||||||
|
assert.deepStrictEqual(events.stepStatuses, [{ step: 3, status: 'skipped' }]);
|
||||||
|
assert.equal(events.logs[0]?.message, '步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('message router does not overwrite a completed step 3 when step 2 is replayed', async () => {
|
||||||
|
const { router, events } = createRouter({
|
||||||
|
state: { stepStatuses: { 3: 'completed' } },
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.handleStepData(2, {
|
||||||
|
skippedPasswordStep: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events.stepStatuses, []);
|
||||||
|
});
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const step2Source = fs.readFileSync('background/steps/submit-signup-email.js', 'utf8');
|
||||||
|
const step2GlobalScope = {};
|
||||||
|
const step2Api = new Function('self', `${step2Source}; return self.MultiPageBackgroundStep2;`)(step2GlobalScope);
|
||||||
|
|
||||||
|
const signupFlowSource = fs.readFileSync('background/signup-flow-helpers.js', 'utf8');
|
||||||
|
const signupFlowGlobalScope = {};
|
||||||
|
const signupFlowApi = new Function('self', `${signupFlowSource}; return self.MultiPageSignupFlowHelpers;`)(signupFlowGlobalScope);
|
||||||
|
|
||||||
|
test('step 2 completes with password step skipped when landing on email verification page', async () => {
|
||||||
|
const completedPayloads = [];
|
||||||
|
|
||||||
|
const executor = step2Api.createStep2Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
completeStepFromBackground: async (step, payload) => {
|
||||||
|
completedPayloads.push({ step, payload });
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureSignupEntryPageReady: async () => ({ tabId: 11 }),
|
||||||
|
ensureSignupPostEmailPageReadyInTab: async () => ({
|
||||||
|
state: 'verification_page',
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
}),
|
||||||
|
getTabId: async () => 11,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||||
|
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep2({ email: 'user@example.com' });
|
||||||
|
|
||||||
|
assert.deepStrictEqual(completedPayloads, [
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
payload: {
|
||||||
|
email: 'user@example.com',
|
||||||
|
nextSignupState: 'verification_page',
|
||||||
|
nextSignupUrl: 'https://auth.openai.com/email-verification',
|
||||||
|
skippedPasswordStep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('step 2 keeps password flow when landing on password page', async () => {
|
||||||
|
const completedPayloads = [];
|
||||||
|
|
||||||
|
const executor = step2Api.createStep2Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: { tabs: { update: async () => {} } },
|
||||||
|
completeStepFromBackground: async (step, payload) => {
|
||||||
|
completedPayloads.push({ step, payload });
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureSignupEntryPageReady: async () => ({ tabId: 12 }),
|
||||||
|
ensureSignupPostEmailPageReadyInTab: async () => ({
|
||||||
|
state: 'password_page',
|
||||||
|
url: 'https://auth.openai.com/create-account/password',
|
||||||
|
}),
|
||||||
|
getTabId: async () => 12,
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
resolveSignupEmailForFlow: async () => 'user@example.com',
|
||||||
|
sendToContentScriptResilient: async () => ({ submitted: true }),
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeStep2({ email: 'user@example.com' });
|
||||||
|
|
||||||
|
assert.deepStrictEqual(completedPayloads, [
|
||||||
|
{
|
||||||
|
step: 2,
|
||||||
|
payload: {
|
||||||
|
email: 'user@example.com',
|
||||||
|
nextSignupState: 'password_page',
|
||||||
|
nextSignupUrl: 'https://auth.openai.com/create-account/password',
|
||||||
|
skippedPasswordStep: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('signup flow helper recognizes email verification page as post-email landing page', async () => {
|
||||||
|
let ensureCalls = 0;
|
||||||
|
let passwordReadyChecks = 0;
|
||||||
|
|
||||||
|
const helpers = signupFlowApi.createSignupFlowHelpers({
|
||||||
|
buildGeneratedAliasEmail: () => '',
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
get: async () => ({
|
||||||
|
id: 21,
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ensureContentScriptReadyOnTab: async () => {
|
||||||
|
ensureCalls += 1;
|
||||||
|
},
|
||||||
|
ensureHotmailAccountForFlow: async () => ({}),
|
||||||
|
ensureLuckmailPurchaseForFlow: async () => ({}),
|
||||||
|
isGeneratedAliasProvider: () => false,
|
||||||
|
isHotmailProvider: () => false,
|
||||||
|
isLuckmailProvider: () => false,
|
||||||
|
isSignupEmailVerificationPageUrl: (url) => /\/email-verification(?:[/?#]|$)/i.test(url || ''),
|
||||||
|
isSignupPasswordPageUrl: (url) => /\/create-account\/password(?:[/?#]|$)/i.test(url || ''),
|
||||||
|
reuseOrCreateTab: async () => 21,
|
||||||
|
sendToContentScriptResilient: async () => {
|
||||||
|
passwordReadyChecks += 1;
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
setEmailState: async () => {},
|
||||||
|
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
waitForTabUrlMatch: async () => ({
|
||||||
|
id: 21,
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await helpers.ensureSignupPostEmailPageReadyInTab(21, 2);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, {
|
||||||
|
ready: true,
|
||||||
|
state: 'verification_page',
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
});
|
||||||
|
assert.equal(ensureCalls, 1);
|
||||||
|
assert.equal(passwordReadyChecks, 0);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user