fix: handle OpenAI choose account login
This commit is contained in:
@@ -46,6 +46,7 @@ test('logging/status add-phone detection ignores step 2 phone-entry switch failu
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('phone_verification_page'), '手机验证码页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('add_email_page'), '添加邮箱页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('oauth_consent_page'), 'OAuth 授权页');
|
||||
assert.equal(loggingStatus.getLoginAuthStateLabel('choose_account_page'), 'OpenAI choose account page');
|
||||
assert.equal(
|
||||
loggingStatus.getErrorMessage(new Error('GPC_TASK_ENDED::GPC OTP 超时,请重新创建任务')),
|
||||
'GPC OTP 超时,请重新创建任务'
|
||||
|
||||
@@ -143,6 +143,10 @@ function isOAuthConsentPage() {
|
||||
return ${JSON.stringify(Boolean(overrides.oauthConsentPage))};
|
||||
}
|
||||
|
||||
function isChooseAccountPage() {
|
||||
return ${JSON.stringify(Boolean(overrides.chooseAccountPage))};
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
@@ -287,6 +291,18 @@ return {
|
||||
assert.strictEqual(snapshot.state, 'entry_page');
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
pathname: '/choose-an-account',
|
||||
href: 'https://auth.openai.com/choose-an-account',
|
||||
chooseAccountPage: true,
|
||||
});
|
||||
|
||||
const snapshot = api.inspectLoginAuthState();
|
||||
assert.strictEqual(snapshot.state, 'choose_account_page');
|
||||
assert.strictEqual(snapshot.chooseAccountPage, true);
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
phoneInput: { id: 'phone' },
|
||||
@@ -326,4 +342,9 @@ assert.ok(
|
||||
'inspectLoginAuthState 应产出 add_email_page 状态'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
extractFunction('inspectLoginAuthState').includes("state: 'choose_account_page'"),
|
||||
'inspectLoginAuthState should produce choose_account_page state'
|
||||
);
|
||||
|
||||
console.log('step6 login state tests passed');
|
||||
|
||||
@@ -51,6 +51,15 @@ function extractFunction(name) {
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
function extractConst(name) {
|
||||
const pattern = new RegExp(`const\\s+${name}\\s*=\\s*[\\s\\S]*?;`);
|
||||
const match = source.match(pattern);
|
||||
if (!match) {
|
||||
throw new Error(`missing const ${name}`);
|
||||
}
|
||||
return match[0];
|
||||
}
|
||||
|
||||
test('password submit treats direct OAuth consent as a login-code skip', async () => {
|
||||
const api = new Function(`
|
||||
const location = { href: 'https://auth.openai.com/authorize' };
|
||||
@@ -132,3 +141,232 @@ return {
|
||||
assert.equal(result.directOAuthConsentPage, true);
|
||||
assert.equal(logs.some(({ level }) => level === 'ok'), true);
|
||||
});
|
||||
|
||||
test('step 7 clicks matching choose-account card and skips login code after OAuth consent', 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 targetCard = {
|
||||
id: 'target-card',
|
||||
textContent: 'Tall Slept Fancy tall-slept-fancy@duck.com',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'role') return 'button';
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const removeButton = {
|
||||
id: 'remove-button',
|
||||
textContent: '',
|
||||
value: '',
|
||||
parentElement: targetCard,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'aria-label') return 'Remove tall-slept-fancy@duck.com';
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const otherCard = {
|
||||
id: 'other-card',
|
||||
textContent: 'Other User other@example.com',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'role') return 'button';
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
innerText: 'Welcome back Choose an account tall-slept-fancy@duck.com other@example.com',
|
||||
textContent: 'Welcome back Choose an account tall-slept-fancy@duck.com other@example.com',
|
||||
},
|
||||
querySelectorAll(selector) {
|
||||
if (String(selector).includes('body *')) return [removeButton, targetCard, otherCard];
|
||||
return [removeButton, targetCard, otherCard];
|
||||
},
|
||||
};
|
||||
|
||||
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')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
${extractFunction('createStep6RecoverableResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('waitForChooseAccountTransition')}
|
||||
${extractFunction('step6ChooseExistingAccount')}
|
||||
|
||||
return {
|
||||
clicked,
|
||||
run() {
|
||||
return step6ChooseExistingAccount(
|
||||
{ email: 'TALL-SLEPT-FANCY@DUCK.COM', loginIdentifierType: 'email', visibleStep: 7 },
|
||||
{ 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);
|
||||
assert.equal(result.via, 'choose_account_oauth_consent_page');
|
||||
});
|
||||
|
||||
test('step 7 does not click choose-account page when target email is missing', async () => {
|
||||
const api = new Function(`
|
||||
const clicked = [];
|
||||
const location = {
|
||||
href: 'https://auth.openai.com/choose-an-account',
|
||||
pathname: '/choose-an-account',
|
||||
};
|
||||
const otherCard = {
|
||||
id: 'other-card',
|
||||
textContent: 'Other User other@example.com',
|
||||
value: '',
|
||||
parentElement: null,
|
||||
disabled: false,
|
||||
getAttribute(name) {
|
||||
if (name === 'role') return 'button';
|
||||
if (name === 'aria-disabled') return 'false';
|
||||
return '';
|
||||
},
|
||||
closest() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
const document = {
|
||||
body: {
|
||||
innerText: 'Welcome back Choose an account other@example.com',
|
||||
textContent: 'Welcome back Choose an account other@example.com',
|
||||
},
|
||||
querySelectorAll() {
|
||||
return [otherCard];
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
function inspectLoginAuthState() {
|
||||
return { state: 'choose_account_page', url: location.href, chooseAccountPage: true };
|
||||
}
|
||||
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')}
|
||||
${extractFunction('getPageTextSnapshot')}
|
||||
${extractFunction('normalizeAuthAccountIdentifier')}
|
||||
${extractFunction('getChooseAccountCandidateText')}
|
||||
${extractFunction('isChooseAccountPage')}
|
||||
${extractFunction('isChooseAccountRemovalAction')}
|
||||
${extractFunction('resolveChooseAccountClickTarget')}
|
||||
${extractFunction('findChooseAccountButtonForEmail')}
|
||||
${extractFunction('createStep6SuccessResult')}
|
||||
${extractFunction('createStep6OAuthConsentSuccessResult')}
|
||||
${extractFunction('createStep6AddEmailSuccessResult')}
|
||||
${extractFunction('createStep6RecoverableResult')}
|
||||
${extractFunction('normalizeStep6Snapshot')}
|
||||
${extractFunction('waitForChooseAccountTransition')}
|
||||
${extractFunction('step6ChooseExistingAccount')}
|
||||
|
||||
return {
|
||||
clicked,
|
||||
run() {
|
||||
return step6ChooseExistingAccount(
|
||||
{ email: 'target@example.com', loginIdentifierType: 'email', visibleStep: 7 },
|
||||
{ state: 'choose_account_page', url: location.href }
|
||||
);
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.run();
|
||||
|
||||
assert.deepEqual(api.clicked, []);
|
||||
assert.equal(result.step6Outcome, 'recoverable');
|
||||
assert.equal(result.reason, 'choose_account_target_not_found');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user