feat: Enhance login flow with add-email and phone verification support
- Added support for the add-email page in the logging status module. - Updated step 8 to handle phone-registered accounts during email verification. - Implemented logic to submit add-email before polling for email verification codes. - Enhanced tests for step 7 to detect username input as phone number and ignore hidden inputs. - Improved step 8 to allow add-email handoff only when requested. - Updated documentation to reflect changes in the login verification flow.
This commit is contained in:
@@ -44,5 +44,6 @@ test('logging/status add-phone detection ignores step 2 phone-entry switch failu
|
||||
true
|
||||
);
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('phone_verification_page'), '手机验证码页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('add_email_page'), '添加邮箱页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('oauth_consent_page'), 'OAuth 授权页');
|
||||
});
|
||||
|
||||
@@ -85,16 +85,23 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
{ step8VerificationTargetEmail: 'display.user@example.com' },
|
||||
]);
|
||||
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
||||
{ visibleStep: 8, authLoginStep: 7, timeoutMs: 5000 },
|
||||
{
|
||||
visibleStep: 8,
|
||||
authLoginStep: 7,
|
||||
allowPhoneVerificationPage: true,
|
||||
allowAddEmailPage: true,
|
||||
timeoutMs: 5000,
|
||||
},
|
||||
]);
|
||||
assert.equal(calls.resolveOptions.completionStep, 8);
|
||||
});
|
||||
|
||||
test('step 8 routes phone login verification through sms helper and skips mail provider setup', async () => {
|
||||
test('step 8 keeps phone-registered accounts on email-code flow when page is email verification', async () => {
|
||||
const calls = {
|
||||
getMailConfigCalls: 0,
|
||||
helperCalls: [],
|
||||
completions: [],
|
||||
resolveCalls: 0,
|
||||
};
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
@@ -109,9 +116,7 @@ test('step 8 routes phone login verification through sms helper and skips mail p
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => {
|
||||
throw new Error('phone login branch should not probe email verification page readiness');
|
||||
},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: 'phone-user@example.com' }),
|
||||
getOAuthFlowRemainingMs: async () => 5000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getMailConfig: () => {
|
||||
@@ -141,7 +146,7 @@ test('step 8 routes phone login verification through sms helper and skips mail p
|
||||
},
|
||||
resolveSignupMethod: () => 'phone',
|
||||
resolveVerificationStep: async () => {
|
||||
throw new Error('phone login branch should not call email verification flow');
|
||||
calls.resolveCalls += 1;
|
||||
},
|
||||
rerunStep7ForStep8Recovery: async () => {
|
||||
throw new Error('phone login branch should not rerun step 7 in this test');
|
||||
@@ -164,6 +169,73 @@ test('step 8 routes phone login verification through sms helper and skips mail p
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(calls.getMailConfigCalls, 1);
|
||||
assert.equal(calls.resolveCalls, 1);
|
||||
assert.deepStrictEqual(calls.helperCalls, []);
|
||||
assert.deepStrictEqual(calls.completions, []);
|
||||
});
|
||||
|
||||
test('step 8 routes only a real phone verification page through sms helper', async () => {
|
||||
const calls = {
|
||||
getMailConfigCalls: 0,
|
||||
helperCalls: [],
|
||||
completions: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
calls.completions.push({ step, payload });
|
||||
},
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'phone_verification_page' }),
|
||||
getOAuthFlowRemainingMs: async () => 5000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getMailConfig: () => {
|
||||
calls.getMailConfigCalls += 1;
|
||||
return {
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
};
|
||||
},
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
phoneVerificationHelpers: {
|
||||
completeLoginPhoneVerificationFlow: async (tabId, options) => {
|
||||
calls.helperCalls.push({ tabId, visibleStep: options.visibleStep, state: options.state });
|
||||
return { code: '654321' };
|
||||
},
|
||||
},
|
||||
resolveVerificationStep: async () => {
|
||||
throw new Error('real phone verification branch should not call email verification flow');
|
||||
},
|
||||
rerunStep7ForStep8Recovery: async () => {
|
||||
throw new Error('real phone verification branch should not rerun step 7 in this test');
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
visibleStep: 8,
|
||||
accountIdentifierType: 'phone',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(calls.getMailConfigCalls, 0);
|
||||
assert.deepStrictEqual(calls.helperCalls, [
|
||||
{
|
||||
@@ -172,10 +244,6 @@ test('step 8 routes phone login verification through sms helper and skips mail p
|
||||
state: {
|
||||
visibleStep: 8,
|
||||
accountIdentifierType: 'phone',
|
||||
signupPhoneCompletedActivation: {
|
||||
activationId: 'signup-done',
|
||||
phoneNumber: '66959916439',
|
||||
},
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
},
|
||||
},
|
||||
@@ -192,6 +260,96 @@ test('step 8 routes phone login verification through sms helper and skips mail p
|
||||
]);
|
||||
});
|
||||
|
||||
test('step 8 submits add-email before polling the email verification code', async () => {
|
||||
const calls = {
|
||||
contentMessages: [],
|
||||
resolvedStates: [],
|
||||
setStates: [],
|
||||
mailStates: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'add_email_page', url: 'https://auth.openai.com/add-email' }),
|
||||
getOAuthFlowRemainingMs: async () => 5000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getMailConfig: (state) => {
|
||||
calls.mailStates.push(state);
|
||||
return {
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
};
|
||||
},
|
||||
getState: async () => ({
|
||||
email: '',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
}),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveSignupEmailForFlow: async (state) => {
|
||||
calls.resolvedStates.push(state);
|
||||
return 'new.user@example.com';
|
||||
},
|
||||
resolveVerificationStep: async (_step, state, _mail, options) => {
|
||||
calls.resolvedVerification = { state, options };
|
||||
},
|
||||
rerunStep7ForStep8Recovery: async () => {},
|
||||
reuseOrCreateTab: async () => {},
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
calls.contentMessages.push(message);
|
||||
assert.equal(message.type, 'SUBMIT_ADD_EMAIL');
|
||||
assert.equal(message.payload.email, 'new.user@example.com');
|
||||
return {
|
||||
submitted: true,
|
||||
displayedEmail: 'new.user@example.com',
|
||||
url: 'https://auth.openai.com/email-verification',
|
||||
};
|
||||
},
|
||||
setState: async (payload) => {
|
||||
calls.setStates.push(payload);
|
||||
},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
visibleStep: 8,
|
||||
accountIdentifierType: 'phone',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(calls.contentMessages.length, 1);
|
||||
assert.equal(calls.resolvedStates.length, 1);
|
||||
assert.equal(calls.mailStates[0].email, 'new.user@example.com');
|
||||
assert.equal(calls.resolvedVerification.state.email, 'new.user@example.com');
|
||||
assert.equal(calls.resolvedVerification.options.targetEmail, 'new.user@example.com');
|
||||
assert.deepStrictEqual(calls.setStates, [
|
||||
{
|
||||
email: 'new.user@example.com',
|
||||
step8VerificationTargetEmail: 'new.user@example.com',
|
||||
},
|
||||
{
|
||||
step8VerificationTargetEmail: 'new.user@example.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
|
||||
let resolvedStep = null;
|
||||
let resolvedOptions = null;
|
||||
@@ -257,7 +415,13 @@ test('Plus login-code step reuses step 8 verification logic but completes visibl
|
||||
assert.equal(resolvedStep, 8);
|
||||
assert.equal(resolvedOptions.completionStep, 11);
|
||||
assert.equal(resolvedOptions.targetEmail, 'plus.user@example.com');
|
||||
assert.deepStrictEqual(readyOptions, { visibleStep: 11, authLoginStep: 10, timeoutMs: 9000 });
|
||||
assert.deepStrictEqual(readyOptions, {
|
||||
visibleStep: 11,
|
||||
authLoginStep: 10,
|
||||
allowPhoneVerificationPage: true,
|
||||
allowAddEmailPage: true,
|
||||
timeoutMs: 9000,
|
||||
});
|
||||
assert.deepStrictEqual(remainingStepCalls, [11, 11]);
|
||||
});
|
||||
|
||||
|
||||
@@ -124,6 +124,10 @@ function isAddPhonePageReady() {
|
||||
return ${JSON.stringify(Boolean(overrides.addPhonePage))};
|
||||
}
|
||||
|
||||
function isAddEmailPageReady() {
|
||||
return ${JSON.stringify(Boolean(overrides.addEmailPage))};
|
||||
}
|
||||
|
||||
function isVisibleElement() {
|
||||
return true;
|
||||
}
|
||||
@@ -261,6 +265,20 @@ return {
|
||||
assert.strictEqual(snapshot.state, 'phone_entry_page');
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
pathname: '/add-email',
|
||||
href: 'https://auth.openai.com/add-email',
|
||||
emailInput: { id: 'email' },
|
||||
submitButton: { id: 'submit' },
|
||||
addEmailPage: true,
|
||||
});
|
||||
|
||||
const snapshot = api.inspectLoginAuthState();
|
||||
assert.strictEqual(snapshot.state, 'add_email_page');
|
||||
assert.strictEqual(snapshot.addEmailPage, true);
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
extractFunction('inspectLoginAuthState').includes("state: 'oauth_consent_page'"),
|
||||
'inspectLoginAuthState 应产出 oauth_consent_page 状态'
|
||||
@@ -271,4 +289,9 @@ assert.ok(
|
||||
'inspectLoginAuthState 应产出 phone_entry_page 状态'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
extractFunction('inspectLoginAuthState').includes("state: 'add_email_page'"),
|
||||
'inspectLoginAuthState 应产出 add_email_page 状态'
|
||||
);
|
||||
|
||||
console.log('step6 login state tests passed');
|
||||
|
||||
@@ -68,13 +68,15 @@ function createPhoneLoginEntryApi(options = {}) {
|
||||
inputRootText = '',
|
||||
pageText = '',
|
||||
addPhoneForm = false,
|
||||
phoneUsernameKind = false,
|
||||
} = options;
|
||||
|
||||
return new Function(`
|
||||
${extractConst('ADD_PHONE_PAGE_PATTERN')}
|
||||
${extractConst('LOGIN_PHONE_ENTRY_PAGE_PATTERN')}
|
||||
|
||||
const location = {
|
||||
href: ${JSON.stringify(href)},
|
||||
href: ${JSON.stringify(phoneUsernameKind ? `${href}${href.includes('?') ? '&' : '?'}usernameKind=phone_number` : href)},
|
||||
pathname: ${JSON.stringify(pathname)},
|
||||
};
|
||||
|
||||
@@ -107,7 +109,7 @@ const document = {
|
||||
return null;
|
||||
},
|
||||
querySelectorAll(selector) {
|
||||
if (selector === 'input') return [phoneInput];
|
||||
if (String(selector || '').includes('input')) return [phoneInput];
|
||||
return [];
|
||||
},
|
||||
getElementById() {
|
||||
@@ -125,7 +127,18 @@ function isVisibleElement(element) {
|
||||
return Boolean(element);
|
||||
}
|
||||
|
||||
function isPhoneVerificationPageReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('isLoginPhoneUsernameKind')}
|
||||
${extractFunction('isLoginPhoneEntryPageText')}
|
||||
${extractFunction('isInsideHiddenPhoneControl')}
|
||||
${extractFunction('summarizePhoneInputCandidate')}
|
||||
${extractFunction('isUsablePhoneInputElement')}
|
||||
${extractFunction('collectPhoneInputCandidates')}
|
||||
${extractFunction('findUsablePhoneInput')}
|
||||
${extractFunction('getLoginPhoneInput')}
|
||||
${extractFunction('isAddPhonePageReady')}
|
||||
|
||||
@@ -155,6 +168,26 @@ test('step 7 does not mistake email entry with a phone switch action for phone i
|
||||
assert.equal(api.isAddPhonePageReady(), false);
|
||||
});
|
||||
|
||||
test('step 7 detects username text input when usernameKind is phone_number', () => {
|
||||
const api = createPhoneLoginEntryApi({
|
||||
phoneUsernameKind: true,
|
||||
pageText: '\u6b22\u8fce\u56de\u6765',
|
||||
inputAttributes: { type: 'text', name: 'username', autocomplete: 'username' },
|
||||
});
|
||||
|
||||
assert.ok(api.getLoginPhoneInput(), 'username text input should be treated as phone on phone login url');
|
||||
});
|
||||
|
||||
test('step 7 ignores hidden phone inputs while resolving login phone entry', () => {
|
||||
const api = createPhoneLoginEntryApi({
|
||||
phoneUsernameKind: true,
|
||||
pageText: '\u7535\u8bdd\u53f7\u7801',
|
||||
inputAttributes: { type: 'hidden', name: 'phone' },
|
||||
});
|
||||
|
||||
assert.equal(api.getLoginPhoneInput(), null);
|
||||
});
|
||||
|
||||
test('add-phone detection stays true for real add-phone urls and forms', () => {
|
||||
assert.equal(
|
||||
createPhoneLoginEntryApi({
|
||||
@@ -312,29 +345,73 @@ return {
|
||||
assert.deepEqual(api.getClicks(), ['\u6fb3\u5927\u5229\u4e9a (+61)', '\u82f1\u56fd +(44)']);
|
||||
});
|
||||
|
||||
function createPhoneFillApi(fillBehavior) {
|
||||
function createPhoneFillApi(fillBehavior, options = {}) {
|
||||
const {
|
||||
initialValue = '+44',
|
||||
} = options;
|
||||
|
||||
return new Function('fillBehavior', `
|
||||
const fills = [];
|
||||
const phoneInput = {
|
||||
value: '+44',
|
||||
value: ${JSON.stringify(initialValue)},
|
||||
form: null,
|
||||
getAttribute(name) {
|
||||
return name === 'value' ? this.value : '';
|
||||
},
|
||||
focus() {},
|
||||
closest() {
|
||||
return this.form;
|
||||
},
|
||||
};
|
||||
|
||||
const hiddenPhoneInput = {
|
||||
type: 'hidden',
|
||||
value: '',
|
||||
events: [],
|
||||
getAttribute(name) {
|
||||
if (name === 'type') return 'hidden';
|
||||
if (name === 'name') return 'phone';
|
||||
return '';
|
||||
},
|
||||
dispatchEvent(event) {
|
||||
this.events.push(event.type);
|
||||
},
|
||||
};
|
||||
|
||||
const root = {
|
||||
querySelectorAll(selector) {
|
||||
if (String(selector).includes('input[name="phone"]')) {
|
||||
return [hiddenPhoneInput];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
phoneInput.form = root;
|
||||
|
||||
function fillInput(input, value) {
|
||||
fills.push(value);
|
||||
fillBehavior(input, value);
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
function throwIfStopped() {}
|
||||
function isVisibleElement() { return false; }
|
||||
function log() {}
|
||||
|
||||
${extractFunction('normalizePhoneDigits')}
|
||||
${extractFunction('toNationalPhoneNumber')}
|
||||
${extractFunction('toE164PhoneNumber')}
|
||||
${extractFunction('getPhoneInputRenderedValue')}
|
||||
${extractFunction('isPhoneInputValueVerified')}
|
||||
${extractFunction('waitForPhoneInputValue')}
|
||||
${extractFunction('formatPhoneHiddenFormValue')}
|
||||
${extractFunction('getPhoneHiddenValueInput')}
|
||||
${extractFunction('setPhoneHiddenValue')}
|
||||
${extractFunction('syncPhoneHiddenFormValue')}
|
||||
${extractFunction('isPhoneInputValueComplete')}
|
||||
${extractFunction('getLoginPhoneFillCandidates')}
|
||||
${extractFunction('getLoginPhoneInputDiagnostics')}
|
||||
${extractFunction('getLoginPhoneHiddenValueDiagnostics')}
|
||||
${extractFunction('fillLoginPhoneInputAndConfirm')}
|
||||
|
||||
return {
|
||||
@@ -343,6 +420,7 @@ return {
|
||||
phoneNumber: '447780579093',
|
||||
dialCode: '44',
|
||||
visibleStep: 7,
|
||||
maxAttempts: 2,
|
||||
});
|
||||
},
|
||||
getFills() {
|
||||
@@ -351,13 +429,19 @@ return {
|
||||
getValue() {
|
||||
return phoneInput.value;
|
||||
},
|
||||
getHiddenValue() {
|
||||
return hiddenPhoneInput.value;
|
||||
},
|
||||
getHiddenEvents() {
|
||||
return hiddenPhoneInput.events.slice();
|
||||
},
|
||||
};
|
||||
`)(fillBehavior);
|
||||
}
|
||||
|
||||
test('step 7 retries phone fill with e164 when the auth input keeps only the dial code', async () => {
|
||||
test('step 7 keeps visible dial prefix when filling phone login and syncs the hidden value', async () => {
|
||||
const api = createPhoneFillApi((input, value) => {
|
||||
input.value = value === '7780579093' ? '+44' : value;
|
||||
input.value = value;
|
||||
});
|
||||
|
||||
const result = await api.run();
|
||||
@@ -365,7 +449,24 @@ test('step 7 retries phone fill with e164 when the auth input keeps only the dia
|
||||
assert.equal(result.inputValue, '7780579093');
|
||||
assert.equal(result.attemptedValue, '+447780579093');
|
||||
assert.equal(api.getValue(), '+447780579093');
|
||||
assert.deepEqual(api.getFills(), ['7780579093', '+447780579093']);
|
||||
assert.equal(api.getHiddenValue(), '+447780579093');
|
||||
assert.deepEqual(api.getHiddenEvents(), ['input', 'change']);
|
||||
assert.deepEqual(api.getFills(), ['+447780579093']);
|
||||
});
|
||||
|
||||
test('step 7 keeps national phone fill when visible login input has no dial prefix', async () => {
|
||||
const api = createPhoneFillApi((input, value) => {
|
||||
input.value = value;
|
||||
}, { initialValue: '' });
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.equal(result.inputValue, '7780579093');
|
||||
assert.equal(result.attemptedValue, '7780579093');
|
||||
assert.equal(api.getValue(), '7780579093');
|
||||
assert.equal(api.getHiddenValue(), '+447780579093');
|
||||
assert.deepEqual(api.getHiddenEvents(), ['input', 'change']);
|
||||
assert.deepEqual(api.getFills(), ['7780579093']);
|
||||
});
|
||||
|
||||
test('step 7 stops before submit when phone fill never includes the local number', async () => {
|
||||
@@ -375,5 +476,5 @@ test('step 7 stops before submit when phone fill never includes the local number
|
||||
|
||||
await assert.rejects(api.run, /7780579093/);
|
||||
assert.equal(api.getValue(), '+44');
|
||||
assert.deepEqual(api.getFills(), ['7780579093', '+447780579093', '447780579093']);
|
||||
assert.deepEqual(api.getFills(), ['+447780579093', '7780579093', '+447780579093', '7780579093']);
|
||||
});
|
||||
|
||||
@@ -115,6 +115,37 @@ return {
|
||||
);
|
||||
});
|
||||
|
||||
test('ensureStep8VerificationPageReady allows add-email handoff only when requested', async () => {
|
||||
const api = new Function(`
|
||||
function getLoginAuthStateLabel(state) {
|
||||
return state === 'add_email_page' ? '添加邮箱页' : 'unknown page';
|
||||
}
|
||||
|
||||
async function getLoginAuthStateFromContent() {
|
||||
return {
|
||||
state: 'add_email_page',
|
||||
url: 'https://auth.openai.com/add-email',
|
||||
};
|
||||
}
|
||||
|
||||
${extractFunction(backgroundSource, 'ensureStep8VerificationPageReady')}
|
||||
|
||||
return {
|
||||
run(options) {
|
||||
return ensureStep8VerificationPageReady(options || {});
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
await assert.rejects(
|
||||
() => api.run({}),
|
||||
/当前未进入登录验证码页面/
|
||||
);
|
||||
|
||||
const result = await api.run({ allowAddEmailPage: true });
|
||||
assert.equal(result.state, 'add_email_page');
|
||||
});
|
||||
|
||||
test('step 8 reruns step 7 when auth page enters login timeout retry state', async () => {
|
||||
const calls = {
|
||||
rerunStep7: 0,
|
||||
|
||||
Reference in New Issue
Block a user