fix: route choose-account to target email login
This commit is contained in:
@@ -137,6 +137,45 @@ test('step 7 retries up to configured limit and then fails', async () => {
|
||||
assert.equal(events.completed, 0);
|
||||
});
|
||||
|
||||
test('step 7 preserves visible step when refreshing OAuth after retry', async () => {
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
const events = {
|
||||
refreshSteps: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep7Executor({
|
||||
addLog: async () => {},
|
||||
completeNodeFromBackground: async () => {},
|
||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable',
|
||||
isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
|
||||
refreshOAuthUrlBeforeStep6: async (state) => {
|
||||
events.refreshSteps.push(state.visibleStep);
|
||||
return `https://oauth.example/${events.refreshSteps.length}`;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
sendToContentScriptResilient: async () => ({
|
||||
step6Outcome: 'recoverable',
|
||||
state: 'email_page',
|
||||
message: '当前仍停留在邮箱页。',
|
||||
}),
|
||||
STEP6_MAX_ATTEMPTS: 3,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret', visibleStep: 9 }),
|
||||
/步骤 9:判断失败后已重试 2 次/
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(events.refreshSteps, [9, 9, 9]);
|
||||
});
|
||||
|
||||
test('step 7 hands add-phone to the dedicated post-login phone node without internal retry', async () => {
|
||||
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
@@ -241,13 +241,18 @@ ${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')}
|
||||
${extractConst('CHOOSE_ACCOUNT_CARD_SELECTOR')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('resolveChooseAccountCardTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('findChooseAccountOtherAccountButton')}
|
||||
${extractFunction('getChooseAccountListedEmails')}
|
||||
${extractFunction('resolveChooseAccountAction')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
@@ -348,13 +353,18 @@ ${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')}
|
||||
${extractConst('CHOOSE_ACCOUNT_CARD_SELECTOR')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('resolveChooseAccountCardTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('findChooseAccountOtherAccountButton')}
|
||||
${extractFunction('getChooseAccountListedEmails')}
|
||||
${extractFunction('resolveChooseAccountAction')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
@@ -386,6 +396,148 @@ return {
|
||||
assert.equal(result.via, 'choose_account_oauth_authorization_route');
|
||||
});
|
||||
|
||||
test('step 7 clicks matching choose-account card when email is inside a non-action div card', async () => {
|
||||
const api = new Function(`
|
||||
let pageState = 'choose_account_page';
|
||||
const clicked = [];
|
||||
const location = {
|
||||
href: 'https://auth.openai.com/choose-an-account',
|
||||
pathname: '/choose-an-account',
|
||||
};
|
||||
|
||||
function createElement(id, textContent = '', attrs = {}) {
|
||||
return {
|
||||
id,
|
||||
textContent,
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
tagName: attrs.tagName || 'DIV',
|
||||
tabIndex: attrs.tabIndex ?? -1,
|
||||
className: attrs.className || '',
|
||||
getAttribute(name) {
|
||||
if (name === 'class') return this.className;
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
if (name === 'role') return attrs.role || '';
|
||||
return attrs[name] || '';
|
||||
},
|
||||
closest(selector) {
|
||||
let current = this;
|
||||
while (current) {
|
||||
if (
|
||||
String(selector).includes('[class*="account" i]')
|
||||
&& String(current.className || '').toLowerCase().includes('account')
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const targetCard = createElement('target-card', 'Kenneth Wilson sedate-iodize-lisp@duck.com', {
|
||||
className: 'account-card rounded-xl',
|
||||
});
|
||||
const targetEmail = createElement('target-email', 'sedate-iodize-lisp@duck.com');
|
||||
targetEmail.parentElement = targetCard;
|
||||
const removeButton = createElement('remove-button', '', {
|
||||
tagName: 'BUTTON',
|
||||
'aria-label': 'Remove sedate-iodize-lisp@duck.com',
|
||||
});
|
||||
removeButton.parentElement = targetCard;
|
||||
const otherCard = createElement('other-card', 'Other User other@example.com', {
|
||||
className: 'account-card rounded-xl',
|
||||
});
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
innerText: '欢迎回来 选择一个帐户 sedate-iodize-lisp@duck.com other@example.com',
|
||||
textContent: '欢迎回来 选择一个帐户 sedate-iodize-lisp@duck.com other@example.com',
|
||||
},
|
||||
querySelectorAll(selector) {
|
||||
if (String(selector).includes('body *')) return [removeButton, targetEmail, targetCard, otherCard];
|
||||
return [removeButton];
|
||||
},
|
||||
};
|
||||
|
||||
function getOperationDelayRunner() {
|
||||
return async (_metadata, operation) => operation();
|
||||
}
|
||||
function isVisibleElement(element) {
|
||||
return Boolean(element);
|
||||
}
|
||||
function isActionEnabled(element) {
|
||||
return Boolean(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true';
|
||||
}
|
||||
function simulateClick(element) {
|
||||
clicked.push(element.id);
|
||||
if (element === targetCard) {
|
||||
pageState = 'oauth_consent_page';
|
||||
location.href = 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent';
|
||||
location.pathname = '/sign-in-with-chatgpt/codex/consent';
|
||||
}
|
||||
}
|
||||
function inspectLoginAuthState() {
|
||||
return { state: pageState, url: location.href, chooseAccountPage: pageState === 'choose_account_page' };
|
||||
}
|
||||
function throwIfStopped() {}
|
||||
async function sleep() {}
|
||||
async function humanPause() {}
|
||||
function log() {}
|
||||
async function finalizeStep6VerificationReady() { return { routed: 'verification' }; }
|
||||
async function step6LoginFromPasswordPage() { return { routed: 'password' }; }
|
||||
async function step6LoginFromEmailPage() { return { routed: 'email' }; }
|
||||
async function step6LoginFromPhonePage() { return { routed: 'phone' }; }
|
||||
async function createStep6LoginTimeoutRecoveryTransition() { return { action: 'recoverable', result: { routed: 'timeout' } }; }
|
||||
|
||||
${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')}
|
||||
${extractConst('CHOOSE_ACCOUNT_CARD_SELECTOR')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('resolveChooseAccountCardTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('findChooseAccountOtherAccountButton')}
|
||||
${extractFunction('getChooseAccountListedEmails')}
|
||||
${extractFunction('resolveChooseAccountAction')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
${extractFunction('createStep6RecoverableResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('isOpenAiOAuthAuthorizationRoute')}
|
||||
${extractFunction('isPostChooseAccountOAuthRoute')}
|
||||
${extractFunction('waitForChooseAccountTransition')}
|
||||
${extractFunction('step6ChooseExistingAccount')}
|
||||
|
||||
return {
|
||||
clicked,
|
||||
run() {
|
||||
return step6ChooseExistingAccount(
|
||||
{ email: 'sedate-iodize-lisp@duck.com', loginIdentifierType: 'email', visibleStep: 9 },
|
||||
{ state: 'choose_account_page', url: location.href }
|
||||
);
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.deepEqual(api.clicked, ['target-card']);
|
||||
assert.equal(result.step6Outcome, 'success');
|
||||
assert.equal(result.state, 'oauth_consent_page');
|
||||
assert.equal(result.skipLoginVerificationStep, true);
|
||||
assert.equal(result.directOAuthConsentPage, true);
|
||||
});
|
||||
|
||||
test('step 7 does not click choose-account page when target email is missing', async () => {
|
||||
const api = new Function(`
|
||||
const clicked = [];
|
||||
@@ -447,13 +599,18 @@ ${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')}
|
||||
${extractConst('CHOOSE_ACCOUNT_CARD_SELECTOR')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('resolveChooseAccountCardTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('findChooseAccountOtherAccountButton')}
|
||||
${extractFunction('getChooseAccountListedEmails')}
|
||||
${extractFunction('resolveChooseAccountAction')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
@@ -481,3 +638,167 @@ return {
|
||||
assert.equal(result.step6Outcome, 'recoverable');
|
||||
assert.equal(result.reason, 'choose_account_target_not_found');
|
||||
});
|
||||
|
||||
test('step 7 uses another-account login when choose-account lists a different email', async () => {
|
||||
const api = new Function(`
|
||||
let pageState = 'choose_account_page';
|
||||
const clicked = [];
|
||||
const location = {
|
||||
href: 'https://auth.openai.com/choose-an-account',
|
||||
pathname: '/choose-an-account',
|
||||
};
|
||||
const existingCard = {
|
||||
id: 'existing-card',
|
||||
tagName: 'BUTTON',
|
||||
textContent: 'p 选择帐户 pang-pushing-dealt@duck.com pang-pushing-dealt@duck.com',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
className: '_root_2sicu_62 _leftAlign_2sicu_99 _outline_2sicu_119',
|
||||
getAttribute(name) {
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
if (name === 'class') return this.className;
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const removeButton = {
|
||||
id: 'remove-button',
|
||||
tagName: 'BUTTON',
|
||||
textContent: '移除帐户:pang-pushing-dealt@duck.com',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'aria-label') return '移除帐户:pang-pushing-dealt@duck.com';
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const otherAccount = {
|
||||
id: 'other-account',
|
||||
tagName: 'A',
|
||||
textContent: '登录至另一个帐户',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const createAccount = {
|
||||
id: 'create-account',
|
||||
tagName: 'A',
|
||||
textContent: '创建帐户',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const document = {
|
||||
body: {
|
||||
innerText: '欢迎回来 选择一个帐户 pang-pushing-dealt@duck.com 登录至另一个帐户 创建帐户',
|
||||
textContent: '欢迎回来 选择一个帐户 pang-pushing-dealt@duck.com 登录至另一个帐户 创建帐户',
|
||||
},
|
||||
querySelectorAll(selector) {
|
||||
if (String(selector).includes('body *')) return [existingCard, removeButton, otherAccount, createAccount];
|
||||
return [existingCard, removeButton, otherAccount, createAccount];
|
||||
},
|
||||
};
|
||||
|
||||
function getOperationDelayRunner() {
|
||||
return async (_metadata, operation) => operation();
|
||||
}
|
||||
function isVisibleElement(element) {
|
||||
return Boolean(element);
|
||||
}
|
||||
function isActionEnabled(element) {
|
||||
return Boolean(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true';
|
||||
}
|
||||
function simulateClick(element) {
|
||||
clicked.push(element.id);
|
||||
if (element === otherAccount) {
|
||||
pageState = 'email_page';
|
||||
location.href = 'https://auth.openai.com/log-in';
|
||||
location.pathname = '/log-in';
|
||||
}
|
||||
}
|
||||
function inspectLoginAuthState() {
|
||||
return { state: pageState, url: location.href, chooseAccountPage: pageState === 'choose_account_page' };
|
||||
}
|
||||
function throwIfStopped() {}
|
||||
async function sleep() {}
|
||||
async function humanPause() {}
|
||||
function log() {}
|
||||
async function finalizeStep6VerificationReady() { return { routed: 'verification' }; }
|
||||
async function step6LoginFromPasswordPage() { return { routed: 'password' }; }
|
||||
async function step6LoginFromEmailPage(payload, snapshot) {
|
||||
return {
|
||||
routed: 'email',
|
||||
email: payload.email,
|
||||
state: snapshot.state,
|
||||
};
|
||||
}
|
||||
async function step6LoginFromPhonePage() { return { routed: 'phone' }; }
|
||||
async function step6OpenLoginEntry() { return { routed: 'entry' }; }
|
||||
async function createStep6LoginTimeoutRecoveryTransition() { return { action: 'recoverable', result: { routed: 'timeout' } }; }
|
||||
|
||||
${extractConst('CHOOSE_ACCOUNT_PAGE_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN')}
|
||||
${extractConst('CHOOSE_ACCOUNT_ACTION_SELECTOR')}
|
||||
${extractConst('CHOOSE_ACCOUNT_CARD_SELECTOR')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('resolveChooseAccountCardTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('findChooseAccountOtherAccountButton')}
|
||||
${extractFunction('getChooseAccountListedEmails')}
|
||||
${extractFunction('resolveChooseAccountAction')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
${extractFunction('createStep6RecoverableResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('isOpenAiOAuthAuthorizationRoute')}
|
||||
${extractFunction('isPostChooseAccountOAuthRoute')}
|
||||
${extractFunction('waitForChooseAccountTransition')}
|
||||
${extractFunction('step6ChooseExistingAccount')}
|
||||
|
||||
return {
|
||||
clicked,
|
||||
run() {
|
||||
return step6ChooseExistingAccount(
|
||||
{ email: 'sedate-iodize-lisp@duck.com', loginIdentifierType: 'email', visibleStep: 9 },
|
||||
{ state: 'choose_account_page', url: location.href }
|
||||
);
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.deepEqual(api.clicked, ['other-account']);
|
||||
assert.equal(result.routed, 'email');
|
||||
assert.equal(result.email, 'sedate-iodize-lisp@duck.com');
|
||||
assert.equal(result.state, 'email_page');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user