Files
FlowPilot/tests/step5-direct-complete.test.js
T
2026-05-15 03:56:02 +08:00

1071 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => source.indexOf(marker))
.find((index) => index >= 0);
if (start < 0) {
throw new Error(`missing function ${name}`);
}
let parenDepth = 0;
let signatureEnded = false;
let braceStart = -1;
for (let i = start; i < source.length; i += 1) {
const ch = source[i];
if (ch === '(') {
parenDepth += 1;
} else if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnded = true;
}
} else if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
if (braceStart < 0) {
throw new Error(`missing body for function ${name}`);
}
let depth = 0;
let end = braceStart;
for (; end < source.length; end += 1) {
const ch = source[end];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return source.slice(start, end);
}
function getStep5OutcomeBundle() {
return [
extractFunction('getStep5ProfilePathPatterns'),
extractFunction('getStep5AuthRetryPathPatterns'),
extractFunction('isStep5ProfilePageUrl'),
extractFunction('getStep5AuthRetryPageState'),
extractFunction('getStep5SubmitButton'),
extractFunction('waitForStep5SubmitButton'),
extractFunction('isStep5SubmitButtonClickable'),
extractFunction('isStep5ProfileStillVisible'),
extractFunction('getStep5PostSubmitSuccessState'),
extractFunction('installStep5NavigationCompletionReporter'),
extractFunction('waitForStep5SubmitOutcome'),
].join('\n');
}
function getStep5Bundle() {
return [
extractFunction('isSignupProfilePageUrl'),
getStep5OutcomeBundle(),
extractFunction('getStep5DirectCompletionPayload'),
extractFunction('isStep5AllConsentText'),
extractFunction('findStep5AllConsentCheckbox'),
extractFunction('isStep5CheckboxChecked'),
extractFunction('step5_fillNameBirthday'),
].join('\n');
}
test('step 5 clicks submit and completes after confirmed birthday page result', async () => {
const step5Source = extractFunction('step5_fillNameBirthday');
assert.ok(
step5Source.includes('waitForStep5SubmitOutcome('),
'Step 5 提交后应等待页面结果,避免资料提交假完成'
);
const api = new Function(`
const logs = [];
const completions = [];
const clicks = [];
const selectedBirthday = {};
const nameInput = { value: '', hidden: false };
const hiddenBirthday = {
value: '',
hidden: false,
dispatchEvent() {},
};
const completeButton = {
tagName: 'BUTTON',
textContent: '完成帐户创建',
hidden: false,
getAttribute() { return ''; },
};
const birthdaySelects = {
'年': { label: '年', button: { hidden: false }, nativeSelect: {} },
'月': { label: '月', button: { hidden: false }, nativeSelect: {} },
'天': { label: '天', button: { hidden: false }, nativeSelect: {} },
};
const document = {
querySelector(selector) {
switch (selector) {
case '[role="spinbutton"][data-type="year"]':
case '[role="spinbutton"][data-type="month"]':
case '[role="spinbutton"][data-type="day"]':
case 'input[name="age"]':
return null;
case 'input[name="birthday"]':
return hiddenBirthday;
case 'button[type="submit"], input[type="submit"]':
case 'button[type="submit"]':
return completeButton;
default:
return null;
}
},
querySelectorAll(selector) {
if (selector === 'input[name="allCheckboxes"][type="checkbox"]') {
return [];
}
if (selector === 'input[type="checkbox"]') {
return [];
}
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') {
return [completeButton];
}
return [];
},
execCommand() {},
};
const location = {
href: 'https://auth.openai.com/u/signup/profile',
};
function Event(type, init = {}) {
this.type = type;
this.bubbles = Boolean(init.bubbles);
}
function log(message, level = 'info') {
logs.push({ message, level });
}
function throwIfStopped() {}
async function waitForElement() {
return nameInput;
}
async function humanPause() {}
async function sleep() {}
function fillInput(input, value) {
input.value = value;
}
function findBirthdayReactAriaSelect(label) {
return birthdaySelects[label] || null;
}
function isVisibleElement(el) {
return Boolean(el) && !el.hidden;
}
function isActionEnabled(el) {
return Boolean(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true';
}
function getActionText(el) {
return el.textContent || '';
}
async function setReactAriaBirthdaySelect(select, value) {
selectedBirthday[select.label] = String(value).padStart(select.label === '年' ? 4 : 2, '0');
if (selectedBirthday['年'] && selectedBirthday['月'] && selectedBirthday['天']) {
hiddenBirthday.value = \`\${selectedBirthday['年']}-\${selectedBirthday['月']}-\${selectedBirthday['天']}\`;
}
}
async function waitForElementByText() {
throw new Error('waitForElementByText should not run in this test');
}
function simulateClick(el) {
clicks.push(el.textContent || el.tagName || 'element');
if (el === completeButton) {
location.href = 'https://chatgpt.com/';
}
}
function reportComplete(step, payload) {
completions.push({ step, payload });
}
function normalizeInlineText(text) {
return String(text || '').replace(/\\s+/g, ' ').trim();
}
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState() { return null; }
async function recoverCurrentAuthRetryPage() { throw new Error('should not recover retry page'); }
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href); }
function isLikelyLoggedInChatgptHomeUrl() { return /^https:\\/\\/chatgpt\\.com\\//.test(location.href); }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${getStep5Bundle()}
return {
async run(payload) {
return step5_fillNameBirthday(payload);
},
snapshot() {
return {
logs,
completions,
clicks,
nameValue: nameInput.value,
birthdayValue: hiddenBirthday.value,
};
},
};
`)();
const result = await api.run({
firstName: 'Test',
lastName: 'User',
year: 2003,
month: 6,
day: 19,
});
const snapshot = api.snapshot();
assert.deepStrictEqual(
result,
{
profileSubmitted: true,
postSubmitChecked: true,
outcome: 'logged_in_home',
url: 'https://chatgpt.com/',
},
'生日模式点击提交后应等待并确认页面结果'
);
assert.deepStrictEqual(snapshot.completions, [
{
step: 5,
payload: {
profileSubmitted: true,
postSubmitChecked: true,
outcome: 'logged_in_home',
url: 'https://chatgpt.com/',
},
},
]);
assert.deepStrictEqual(snapshot.clicks, ['完成帐户创建']);
assert.equal(snapshot.nameValue, 'Test User');
assert.equal(snapshot.birthdayValue, '2003-06-19');
assert.ok(
snapshot.logs.some(({ message }) => /资料提交结果已确认/.test(message)),
'日志应明确说明 Step 5 已完成提交后检测'
);
});
test('step 5 routes profile fill and submit operations through operation delay', async () => {
const api = new Function(`
const logs = [];
const completions = [];
const profileEvents = [];
const nameInput = { value: '', hidden: false };
const ageInput = { value: '', hidden: false };
const completeButton = {
tagName: 'BUTTON',
textContent: '完成帐户创建',
hidden: false,
getAttribute() { return ''; },
};
const window = {
CodexOperationDelay: {
async performOperationWithDelay(metadata, operation) {
profileEvents.push(\`operation:\${metadata.label}:start\`);
const result = await operation();
profileEvents.push(\`operation:\${metadata.label}:end\`);
profileEvents.push(\`delay:\${metadata.label}:2000\`);
return result;
},
},
};
const document = {
querySelector(selector) {
switch (selector) {
case '[role="spinbutton"][data-type="year"]':
case '[role="spinbutton"][data-type="month"]':
case '[role="spinbutton"][data-type="day"]':
case 'input[name="birthday"]':
return null;
case 'input[name="age"]':
return ageInput;
case 'button[type="submit"], input[type="submit"]':
case 'button[type="submit"]':
return completeButton;
default:
return null;
}
},
querySelectorAll(selector) {
if (selector === 'input[name="allCheckboxes"][type="checkbox"]') {
return [];
}
if (selector === 'input[type="checkbox"]') {
return [];
}
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') {
return [completeButton];
}
return [];
},
execCommand() {},
};
const location = {
href: 'https://auth.openai.com/u/signup/profile',
};
function log(message, level = 'info') {
logs.push({ message, level });
}
function throwIfStopped() {}
async function waitForElement() {
return nameInput;
}
async function humanPause() {}
async function sleep() {}
function fillInput(input, value) {
input.value = value;
profileEvents.push(input === nameInput ? 'fill:name' : 'fill:birthday');
}
function findBirthdayReactAriaSelect() {
return null;
}
function isVisibleElement(el) {
return Boolean(el) && !el.hidden;
}
function isActionEnabled(el) {
return Boolean(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true';
}
function getActionText(el) {
return el.textContent || '';
}
async function setReactAriaBirthdaySelect() {}
async function waitForElementByText() {
throw new Error('waitForElementByText should not run in this test');
}
function simulateClick() {
profileEvents.push('click:complete');
location.href = 'https://chatgpt.com/';
}
function reportComplete(step, payload) {
completions.push({ step, payload });
}
function normalizeInlineText(text) {
return String(text || '').replace(/\s+/g, ' ').trim();
}
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState() { return null; }
async function recoverCurrentAuthRetryPage() { throw new Error('should not recover retry page'); }
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href); }
function isLikelyLoggedInChatgptHomeUrl() { return /^https:\\/\\/chatgpt\\.com\\//.test(location.href); }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${getStep5Bundle()}
return {
async run(payload) {
return step5_fillNameBirthday(payload);
},
snapshot() {
return {
completions,
profileEvents,
nameValue: nameInput.value,
ageValue: ageInput.value,
};
},
};
`)();
await api.run({
firstName: 'Test',
lastName: 'User',
age: 23,
});
const snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.profileEvents, [
'operation:fill-name:start',
'fill:name',
'operation:fill-name:end',
'delay:fill-name:2000',
'operation:fill-birthday:start',
'fill:birthday',
'operation:fill-birthday:end',
'delay:fill-birthday:2000',
'operation:submit-profile:start',
'click:complete',
'operation:submit-profile:end',
'delay:submit-profile:2000',
]);
assert.ok(snapshot.profileEvents.indexOf('operation:fill-name:start') < snapshot.profileEvents.indexOf('delay:fill-name:2000'));
assert.equal(snapshot.nameValue, 'Test User');
assert.equal(snapshot.ageValue, '23');
});
test('step 5 routes React Aria birthday dropdown selections through per-field operation delays', async () => {
const api = new Function(`
const operationEvents = [];
const selectedBirthday = {};
let activeOperationLabel = '';
const nameInput = { value: '', hidden: false };
const hiddenBirthday = { value: '', hidden: false, dispatchEvent() {} };
const birthdaySelects = {
'\\u5e74': { field: 'year', label: '\\u5e74', button: { hidden: false }, nativeSelect: {} },
'\\u6708': { field: 'month', label: '\\u6708', button: { hidden: false }, nativeSelect: {} },
'\\u5929': { field: 'day', label: '\\u5929', button: { hidden: false }, nativeSelect: {} },
};
const window = {
CodexOperationDelay: {
async performOperationWithDelay(metadata, operation) {
operationEvents.push(\`operation:\${metadata.label}:start\`);
activeOperationLabel = metadata.label;
const result = await operation();
activeOperationLabel = '';
operationEvents.push(\`operation:\${metadata.label}:end\`);
operationEvents.push(\`delay:\${metadata.label}:2000\`);
return result;
},
},
};
const document = {
querySelector(selector) {
switch (selector) {
case '[role="spinbutton"][data-type="year"]':
case '[role="spinbutton"][data-type="month"]':
case '[role="spinbutton"][data-type="day"]':
case 'input[name="age"]':
case 'button[type="submit"]':
return null;
case 'input[name="birthday"]':
return hiddenBirthday;
default:
return null;
}
},
querySelectorAll() { return []; },
execCommand() {},
};
const location = { href: 'https://auth.openai.com/u/signup/profile' };
function Event(type, init = {}) { this.type = type; this.bubbles = Boolean(init.bubbles); }
function log() {}
async function waitForElement() { return nameInput; }
async function humanPause() {}
async function sleep() {}
function fillInput(input, value) { input.value = value; }
function findBirthdayReactAriaSelect(label) { return birthdaySelects[label] || null; }
function isVisibleElement(el) { return Boolean(el) && !el.hidden; }
async function setReactAriaBirthdaySelect(select, value) {
operationEvents.push(\`select:\${select.field}:\${activeOperationLabel || 'outside'}\`);
selectedBirthday[select.field] = String(value).padStart(select.field === 'year' ? 4 : 2, '0');
if (selectedBirthday.year && selectedBirthday.month && selectedBirthday.day) {
hiddenBirthday.value = \`\${selectedBirthday.year}-\${selectedBirthday.month}-\${selectedBirthday.day}\`;
}
}
async function waitForElementByText() { throw new Error('waitForElementByText should not run in prefill test'); }
function simulateClick() { throw new Error('simulateClick should not run in prefill test'); }
function reportComplete() {}
function normalizeInlineText(text) { return String(text || '').replace(/\\s+/g, ' ').trim(); }
${getStep5Bundle()}
return {
async run(payload) { return step5_fillNameBirthday(payload); },
snapshot() { return { operationEvents, birthdayValue: hiddenBirthday.value }; },
};
`)();
const result = await api.run({
firstName: 'Test',
lastName: 'User',
year: 2003,
month: 6,
day: 19,
prefillOnly: true,
});
const snapshot = api.snapshot();
assert.deepStrictEqual(result, { prefilled: true });
assert.equal(snapshot.birthdayValue, '2003-06-19');
assert.deepStrictEqual(snapshot.operationEvents, [
'operation:fill-name:start',
'operation:fill-name:end',
'delay:fill-name:2000',
'operation:select-birthday-year:start',
'select:year:select-birthday-year',
'operation:select-birthday-year:end',
'delay:select-birthday-year:2000',
'operation:select-birthday-month:start',
'select:month:select-birthday-month',
'operation:select-birthday-month:end',
'delay:select-birthday-month:2000',
'operation:select-birthday-day:start',
'select:day:select-birthday-day',
'operation:select-birthday-day:end',
'delay:select-birthday-day:2000',
'operation:profile-dom-sync:start',
'operation:profile-dom-sync:end',
'delay:profile-dom-sync:2000',
]);
});
test('step 5 routes birthday spinbutton fields through per-field operation delays', async () => {
const api = new Function(`
const operationEvents = [];
let activeOperationLabel = '';
function createSpinner(name) {
return {
name,
hidden: false,
value: '',
focus() {},
blur() {},
dispatchEvent(event) {
if (event?.type === 'input' && event.data) {
operationEvents.push(\`spin:\${name}:\${event.data}:\${activeOperationLabel || 'outside'}\`);
this.value += event.data;
}
},
};
}
const nameInput = { value: '', hidden: false };
const yearSpinner = createSpinner('year');
const monthSpinner = createSpinner('month');
const daySpinner = createSpinner('day');
const window = {
CodexOperationDelay: {
async performOperationWithDelay(metadata, operation) {
operationEvents.push(\`operation:\${metadata.label}:start\`);
activeOperationLabel = metadata.label;
const result = await operation();
activeOperationLabel = '';
operationEvents.push(\`operation:\${metadata.label}:end\`);
operationEvents.push(\`delay:\${metadata.label}:2000\`);
return result;
},
},
};
const document = {
querySelector(selector) {
switch (selector) {
case '[role="spinbutton"][data-type="year"]':
return yearSpinner;
case '[role="spinbutton"][data-type="month"]':
return monthSpinner;
case '[role="spinbutton"][data-type="day"]':
return daySpinner;
case 'input[name="birthday"]':
case 'input[name="age"]':
case 'button[type="submit"]':
return null;
default:
return null;
}
},
querySelectorAll() { return []; },
execCommand(command) {
if (command === 'selectAll' && activeOperationLabel) {
const target = {
'fill-birthday-year': yearSpinner,
'fill-birthday-month': monthSpinner,
'fill-birthday-day': daySpinner,
}[activeOperationLabel];
if (target) target.value = '';
}
},
};
const location = { href: 'https://auth.openai.com/u/signup/profile' };
function KeyboardEvent(type, init = {}) { this.type = type; Object.assign(this, init); }
function InputEvent(type, init = {}) { this.type = type; Object.assign(this, init); }
function log() {}
async function waitForElement() { return nameInput; }
async function humanPause() {}
async function sleep() {}
function fillInput(input, value) { input.value = value; }
function findBirthdayReactAriaSelect() { return null; }
function isVisibleElement(el) { return Boolean(el) && !el.hidden; }
async function setReactAriaBirthdaySelect() { throw new Error('setReactAriaBirthdaySelect should not run in spinbutton test'); }
async function waitForElementByText() { throw new Error('waitForElementByText should not run in prefill test'); }
function simulateClick() { throw new Error('simulateClick should not run in prefill test'); }
function reportComplete() {}
function normalizeInlineText(text) { return String(text || '').replace(/\\s+/g, ' ').trim(); }
${getStep5Bundle()}
return {
async run(payload) { return step5_fillNameBirthday(payload); },
snapshot() {
return {
operationEvents,
yearValue: yearSpinner.value,
monthValue: monthSpinner.value,
dayValue: daySpinner.value,
};
},
};
`)();
const result = await api.run({
firstName: 'Test',
lastName: 'User',
year: 2003,
month: 6,
day: 19,
prefillOnly: true,
});
const snapshot = api.snapshot();
assert.deepStrictEqual(result, { prefilled: true });
assert.equal(snapshot.yearValue, '2003');
assert.equal(snapshot.monthValue, '06');
assert.equal(snapshot.dayValue, '19');
assert.deepStrictEqual(snapshot.operationEvents, [
'operation:fill-name:start',
'operation:fill-name:end',
'delay:fill-name:2000',
'operation:fill-birthday-year:start',
'spin:year:2:fill-birthday-year',
'spin:year:0:fill-birthday-year',
'spin:year:0:fill-birthday-year',
'spin:year:3:fill-birthday-year',
'operation:fill-birthday-year:end',
'delay:fill-birthday-year:2000',
'operation:fill-birthday-month:start',
'spin:month:0:fill-birthday-month',
'spin:month:6:fill-birthday-month',
'operation:fill-birthday-month:end',
'delay:fill-birthday-month:2000',
'operation:fill-birthday-day:start',
'spin:day:1:fill-birthday-day',
'spin:day:9:fill-birthday-day',
'operation:fill-birthday-day:end',
'delay:fill-birthday-day:2000',
]);
});
test('step 5 retries submit while profile page remains visible', async () => {
const api = new Function(`
const realDateNow = Date.now;
let now = 0;
const logs = [];
const completions = [];
const clicks = [];
const nameInput = { value: '', hidden: false };
const ageInput = { value: '', hidden: false };
const completeButton = {
tagName: 'BUTTON',
textContent: '完成帐户创建',
hidden: false,
getAttribute() { return ''; },
};
const location = {
href: 'https://auth.openai.com/u/signup/profile',
};
const document = {
querySelector(selector) {
switch (selector) {
case '[role="spinbutton"][data-type="year"]':
case '[role="spinbutton"][data-type="month"]':
case '[role="spinbutton"][data-type="day"]':
case 'input[name="birthday"]':
return null;
case 'input[name="age"]':
return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href) ? ageInput : null;
case 'button[type="submit"], input[type="submit"]':
case 'button[type="submit"]':
return completeButton;
default:
return null;
}
},
querySelectorAll(selector) {
if (selector === 'input[name="allCheckboxes"][type="checkbox"]') return [];
if (selector === 'input[type="checkbox"]') return [];
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') return [completeButton];
return [];
},
execCommand() {},
};
Date.now = () => now;
function log(message, level = 'info') { logs.push({ message, level }); }
function throwIfStopped() {}
async function waitForElement() { return nameInput; }
async function humanPause() {}
async function sleep(ms = 0) { now += ms || 250; }
function fillInput(input, value) { input.value = value; }
function findBirthdayReactAriaSelect() { return null; }
function isVisibleElement(el) { return Boolean(el) && !el.hidden; }
function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true'; }
function getActionText(el) { return el.textContent || ''; }
async function setReactAriaBirthdaySelect() { throw new Error('setReactAriaBirthdaySelect should not run'); }
async function waitForElementByText() { return completeButton; }
function simulateClick(el) {
clicks.push(el.textContent || el.tagName || 'element');
if (el === completeButton && clicks.filter((text) => text === '完成帐户创建').length >= 3) {
location.href = 'https://chatgpt.com/';
}
}
function reportComplete(step, payload) { completions.push({ step, payload }); }
function normalizeInlineText(text) { return String(text || '').replace(/\\s+/g, ' ').trim(); }
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState() { return null; }
async function recoverCurrentAuthRetryPage() { throw new Error('should not recover retry page'); }
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href); }
function isLikelyLoggedInChatgptHomeUrl() { return /^https:\\/\\/chatgpt\\.com\\//.test(location.href); }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${getStep5Bundle()}
return {
run() {
return step5_fillNameBirthday({
firstName: 'Mia',
lastName: 'Harris',
age: 19,
});
},
snapshot() {
return { logs, completions, clicks, nameValue: nameInput.value, ageValue: ageInput.value };
},
restore() {
Date.now = realDateNow;
},
};
`)();
let result;
try {
result = await api.run();
} finally {
api.restore();
}
const snapshot = api.snapshot();
assert.deepStrictEqual(result, {
profileSubmitted: true,
postSubmitChecked: true,
ageMode: true,
outcome: 'logged_in_home',
url: 'https://chatgpt.com/',
});
assert.equal(snapshot.nameValue, 'Mia Harris');
assert.equal(snapshot.ageValue, '19');
assert.equal(snapshot.clicks.filter((text) => text === '完成帐户创建').length, 3);
assert.equal(snapshot.logs.some(({ message }) => /仍停留在资料页正在重新点击/.test(message)), true);
});
test('step 5 waits instead of retrying while profile submit is pending', async () => {
const api = new Function(`
const realDateNow = Date.now;
let now = 0;
const clicks = [];
const completeButton = {
tagName: 'BUTTON',
type: 'submit',
textContent: 'Complete account creation',
hidden: false,
disabled: false,
getAttribute(name) {
if (name === 'aria-disabled') return 'false';
return '';
},
closest() {
return null;
},
};
const location = { href: 'https://auth.openai.com/u/signup/profile' };
const document = {
querySelector(selector) {
if (selector === 'button[type="submit"], input[type="submit"]') return completeButton;
return null;
},
querySelectorAll(selector) {
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') return [completeButton];
return [];
},
};
const window = {
getComputedStyle() {
return { opacity: '0.5', pointerEvents: 'auto' };
},
};
Date.now = () => now;
function throwIfStopped() {}
function log() {}
async function sleep(ms = 0) { now += ms || 250; }
async function humanPause() {}
function simulateClick(el) { clicks.push(el?.textContent || 'clicked'); }
function isVisibleElement(el) { return Boolean(el) && !el.hidden; }
function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute?.('aria-disabled') !== 'true'; }
function getActionText(el) { return el?.textContent || ''; }
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState() { return null; }
async function recoverCurrentAuthRetryPage() { throw new Error('should not recover retry page'); }
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return true; }
function isLikelyLoggedInChatgptHomeUrl() { return false; }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${extractFunction('isSignupProfilePageUrl')}
${getStep5OutcomeBundle()}
return {
run() {
return waitForStep5SubmitOutcome({ timeoutMs: 4000, retryClickIntervalMs: 3500 });
},
snapshot() {
return { clicks, now };
},
restore() {
Date.now = realDateNow;
},
};
`)();
try {
await assert.rejects(
() => api.run(),
/已点击提交 1\/3 次/
);
const snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.clicks, []);
assert.ok(snapshot.now >= 4000);
} finally {
api.restore();
}
});
test('step 5 recovers auth retry page after profile submit', async () => {
const api = new Function(`
let retryVisible = true;
let recoverCalls = 0;
const location = { href: 'https://auth.openai.com/create-account/profile' };
const document = {
querySelector() { return null; },
querySelectorAll() { return []; },
};
function throwIfStopped() {}
function log() {}
async function sleep() {}
async function humanPause() {}
function simulateClick() {}
function isVisibleElement() { return true; }
function isActionEnabled() { return true; }
function getActionText(el) { return el?.textContent || ''; }
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState() {
if (!retryVisible) return null;
return {
retryEnabled: true,
userAlreadyExistsBlocked: false,
maxCheckAttemptsBlocked: false,
};
}
async function recoverCurrentAuthRetryPage() {
recoverCalls += 1;
retryVisible = false;
location.href = 'https://chatgpt.com/';
}
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href); }
function isLikelyLoggedInChatgptHomeUrl() { return /^https:\\/\\/chatgpt\\.com\\//.test(location.href); }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${extractFunction('isSignupProfilePageUrl')}
${getStep5OutcomeBundle()}
return {
run() {
return waitForStep5SubmitOutcome({ timeoutMs: 1000 });
},
snapshot() {
return { recoverCalls };
},
};
`)();
const result = await api.run();
assert.deepStrictEqual(result, {
state: 'logged_in_home',
url: 'https://chatgpt.com/',
});
assert.equal(api.snapshot().recoverCalls, 1);
});
test('step 5 profile url helper treats about-you as profile page', () => {
const api = new Function(`
${extractFunction('isSignupProfilePageUrl')}
return {
run(url) {
return isSignupProfilePageUrl(url);
},
};
`)();
assert.equal(api.run('https://auth.openai.com/about-you'), true);
assert.equal(api.run('https://auth.openai.com/create-account/profile'), true);
assert.equal(api.run('https://chatgpt.com/about-you'), false);
});
test('step 5 recovers about-you auth retry page after profile submit', async () => {
const api = new Function(`
let retryVisible = true;
let recoverCalls = 0;
const location = {
href: 'https://auth.openai.com/about-you',
pathname: '/about-you',
};
const document = {
querySelector() { return null; },
querySelectorAll() { return []; },
};
function throwIfStopped() {}
function log() {}
async function sleep() {}
async function humanPause() {}
function simulateClick() {}
function isVisibleElement() { return true; }
function isActionEnabled() { return true; }
function getActionText(el) { return el?.textContent || ''; }
function getSignupAuthRetryPathPatterns() { return []; }
function getAuthTimeoutErrorPageState(options) {
const matchesAboutYou = Array.isArray(options?.pathPatterns)
&& options.pathPatterns.some((pattern) => pattern.test(location.pathname));
if (!retryVisible || !matchesAboutYou) return null;
return {
retryEnabled: true,
userAlreadyExistsBlocked: false,
maxCheckAttemptsBlocked: false,
};
}
async function recoverCurrentAuthRetryPage() {
recoverCalls += 1;
retryVisible = false;
location.href = 'https://chatgpt.com/';
location.pathname = '/';
}
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
function createAuthMaxCheckAttemptsError() { return new Error('max_check_attempts'); }
function getStep5ErrorText() { return ''; }
function isStep5Ready() { return /^https:\\/\\/auth\\.openai\\.com\\//.test(location.href); }
function isLikelyLoggedInChatgptHomeUrl() { return /^https:\\/\\/chatgpt\\.com\\//.test(location.href); }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
${extractFunction('isSignupProfilePageUrl')}
${getStep5OutcomeBundle()}
return {
run() {
return waitForStep5SubmitOutcome({ timeoutMs: 1000 });
},
snapshot() {
return { recoverCalls };
},
};
`)();
const result = await api.run();
assert.deepStrictEqual(result, {
state: 'logged_in_home',
url: 'https://chatgpt.com/',
});
assert.equal(api.snapshot().recoverCalls, 1);
});
test('step 5 does not treat unknown auth page as left_profile success', () => {
const api = new Function(`
const location = {
href: 'https://auth.openai.com/unexpected-state',
};
function getStep5AuthRetryPageState() { return null; }
function isLikelyLoggedInChatgptHomeUrl() { return false; }
function isOAuthConsentPage() { return false; }
function isAddPhonePageReady() { return false; }
function isStep5ProfileStillVisible() { return false; }
${extractFunction('getStep5PostSubmitSuccessState')}
return {
run() {
return getStep5PostSubmitSuccessState();
},
};
`)();
assert.equal(api.run(), null);
});