fix: route choose-account to target email login

This commit is contained in:
QLHazyCoder
2026-05-30 03:12:55 +08:00
parent 9b7a6dcfbb
commit 932d1e965b
4 changed files with 549 additions and 11 deletions
@@ -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 = {};
+321
View File
@@ -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');
});