Files
FlowPilot/tests/step5-direct-complete.test.js
T

451 lines
14 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('getStep5DirectCompletionPayload'),
extractFunction('isSignupProfilePageUrl'),
extractFunction('isStep5AllConsentText'),
extractFunction('findStep5AllConsentCheckbox'),
extractFunction('isStep5CheckboxChecked'),
getStep5OutcomeBundle(),
extractFunction('step5_fillNameBirthday'),
].join('\n');
}
test('step 5 waits for post-submit outcome before completing on birthday page', async () => {
const step5Source = extractFunction('step5_fillNameBirthday');
assert.ok(
step5Source.includes('waitForStep5SubmitOutcome('),
'Step 5 提交后必须等待页面结果'
);
assert.ok(
!step5Source.includes('不再等待页面结果'),
'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 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="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 === 'button, [role="button"], input[type="button"], input[type="submit"]') {
return [completeButton];
}
return [];
},
execCommand() {},
};
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 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 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);
});