330 lines
9.9 KiB
JavaScript
330 lines
9.9 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
|
|
const source = fs.readFileSync('flows/openai/content/openai-auth.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 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('login step recognizes Japanese email and phone entry actions', () => {
|
|
const api = new Function(`
|
|
const emailButton = {
|
|
textContent: 'メールアドレスで続行',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const phoneButton = {
|
|
textContent: '電話番号で続行',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const codeButton = {
|
|
textContent: 'コードでサインイン',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const document = {
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
|
return [codeButton, phoneButton, emailButton];
|
|
}
|
|
return [];
|
|
},
|
|
};
|
|
|
|
${extractConst('ONE_TIME_CODE_LOGIN_PATTERN')}
|
|
${extractConst('LOGIN_ENTRY_ACTION_PATTERN')}
|
|
${extractConst('LOGIN_SWITCH_TO_PHONE_PATTERN')}
|
|
${extractConst('LOGIN_PHONE_ACTION_PATTERN')}
|
|
${extractConst('LOGIN_EXTERNAL_IDP_PATTERN')}
|
|
${extractConst('LOGIN_CODE_ONLY_ACTION_PATTERN')}
|
|
|
|
function isVisibleElement(el) { return Boolean(el); }
|
|
function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true'; }
|
|
function getActionText(el) { return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')].filter(Boolean).join(' ').replace(/\\s+/g, ' ').trim(); }
|
|
|
|
${extractFunction('findLoginEntryTrigger')}
|
|
${extractFunction('findLoginPhoneEntryTrigger')}
|
|
${extractFunction('findOneTimeCodeLoginTrigger')}
|
|
|
|
return {
|
|
email() { return getActionText(findLoginEntryTrigger()); },
|
|
phone() { return getActionText(findLoginPhoneEntryTrigger()); },
|
|
code() { return getActionText(findOneTimeCodeLoginTrigger()); },
|
|
};
|
|
`)();
|
|
|
|
assert.equal(api.email(), 'メールアドレスで続行');
|
|
assert.equal(api.phone(), '電話番号で続行');
|
|
assert.equal(api.code(), 'コードでサインイン');
|
|
});
|
|
|
|
test('login submit button recognizes Japanese login and continue labels', () => {
|
|
const api = new Function(`
|
|
const loginButton = {
|
|
textContent: 'ログイン',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'type') return 'button';
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const document = {
|
|
querySelector() { return null; },
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
|
return [loginButton];
|
|
}
|
|
return [];
|
|
},
|
|
};
|
|
|
|
${extractConst('ONE_TIME_CODE_LOGIN_PATTERN')}
|
|
|
|
function isVisibleElement(el) { return Boolean(el); }
|
|
function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true'; }
|
|
function getActionText(el) { return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')].filter(Boolean).join(' ').replace(/\\s+/g, ' ').trim(); }
|
|
|
|
${extractFunction('getLoginSubmitButton')}
|
|
|
|
return {
|
|
run() { return getActionText(getLoginSubmitButton()); },
|
|
};
|
|
`)();
|
|
|
|
assert.equal(api.run(), 'ログイン');
|
|
});
|
|
|
|
test('verification helpers recognize Japanese resend and submit actions', () => {
|
|
const api = new Function(`
|
|
const resendButton = {
|
|
textContent: 'コードを再送信',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const verifyButton = {
|
|
textContent: '確認',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const codeInput = {
|
|
form: {
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button[type="submit"], input[type="submit"]') return [];
|
|
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') return [verifyButton];
|
|
return [];
|
|
},
|
|
},
|
|
closest() { return null; },
|
|
};
|
|
const document = {
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
|
return [resendButton];
|
|
}
|
|
return [];
|
|
},
|
|
};
|
|
|
|
${extractConst('RESEND_VERIFICATION_CODE_PATTERN')}
|
|
|
|
function isVisibleElement(el) { return Boolean(el); }
|
|
function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true'; }
|
|
function getActionText(el) { return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')].filter(Boolean).join(' ').replace(/\\s+/g, ' ').trim(); }
|
|
|
|
${extractFunction('findResendVerificationCodeTrigger')}
|
|
${extractFunction('getVerificationSubmitButtonForTarget')}
|
|
|
|
return {
|
|
resend() { return getActionText(findResendVerificationCodeTrigger()); },
|
|
submit() { return getActionText(getVerificationSubmitButtonForTarget(codeInput)); },
|
|
};
|
|
`)();
|
|
|
|
assert.equal(api.resend(), 'コードを再送信');
|
|
assert.equal(api.submit(), '確認');
|
|
});
|
|
|
|
test('OAuth consent detection recognizes Japanese consent copy and continue button', () => {
|
|
const api = new Function(`
|
|
const continueButton = {
|
|
textContent: '続行',
|
|
disabled: false,
|
|
getAttribute(name) {
|
|
if (name === 'data-dd-action-name') return '';
|
|
if (name === 'aria-disabled') return 'false';
|
|
return '';
|
|
},
|
|
};
|
|
const consentForm = {
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button[type="submit"], input[type="submit"], [role="button"]') {
|
|
return [continueButton];
|
|
}
|
|
return [];
|
|
},
|
|
};
|
|
const document = {
|
|
body: {
|
|
innerText: 'ChatGPTを使用してCodexにサインイン',
|
|
textContent: 'ChatGPTを使用してCodexにサインイン',
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === OAUTH_CONSENT_FORM_SELECTOR) return consentForm;
|
|
return null;
|
|
},
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button, [role="button"]') return [continueButton];
|
|
return [];
|
|
},
|
|
};
|
|
|
|
${extractConst('OAUTH_CONSENT_PAGE_PATTERN')}
|
|
${extractConst('OAUTH_CONSENT_FORM_SELECTOR')}
|
|
${extractConst('CONTINUE_ACTION_PATTERN')}
|
|
|
|
function isVisibleElement(el) { return Boolean(el); }
|
|
function getActionText(el) { return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')].filter(Boolean).join(' ').replace(/\\s+/g, ' ').trim(); }
|
|
|
|
${extractFunction('getPageTextSnapshot')}
|
|
${extractFunction('getOAuthConsentForm')}
|
|
${extractFunction('getPrimaryContinueButton')}
|
|
${extractFunction('isOAuthConsentPage')}
|
|
|
|
return {
|
|
consent() { return isOAuthConsentPage(); },
|
|
button() { return getActionText(getPrimaryContinueButton()); },
|
|
};
|
|
`)();
|
|
|
|
assert.equal(api.consent(), true);
|
|
assert.equal(api.button(), '続行');
|
|
});
|
|
|
|
test('profile step recognizes Japanese account creation and day label', () => {
|
|
const api = new Function(`
|
|
const submitButton = {
|
|
textContent: 'アカウントを作成',
|
|
getAttribute(name) {
|
|
if (name === 'type') return 'button';
|
|
return '';
|
|
},
|
|
};
|
|
const dayLabel = { textContent: '日' };
|
|
const dayRoot = {
|
|
querySelectorAll(selector) {
|
|
if (selector === 'span') return [dayLabel];
|
|
return [];
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === 'button[aria-haspopup="listbox"]') return { id: 'day-button' };
|
|
if (selector === '.react-aria-SelectValue') return { textContent: '' };
|
|
return null;
|
|
},
|
|
closest() { return null; },
|
|
parentElement: {
|
|
querySelector(selector) {
|
|
if (selector === '[data-testid="hidden-select-container"] select') return { id: 'day-select' };
|
|
return null;
|
|
},
|
|
},
|
|
};
|
|
const document = {
|
|
querySelector(selector) {
|
|
if (selector === 'button[type="submit"], input[type="submit"]') return null;
|
|
return null;
|
|
},
|
|
querySelectorAll(selector) {
|
|
if (selector === 'button, [role="button"], input[type="button"], input[type="submit"]') return [submitButton];
|
|
if (selector === '.react-aria-Select') return [dayRoot];
|
|
return [];
|
|
},
|
|
};
|
|
|
|
function isVisibleElement(el) { return Boolean(el); }
|
|
function getActionText(el) { return [el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title')].filter(Boolean).join(' ').replace(/\\s+/g, ' ').trim(); }
|
|
|
|
${extractFunction('normalizeInlineText')}
|
|
${extractFunction('getStep5SubmitButton')}
|
|
${extractFunction('findBirthdayReactAriaSelect')}
|
|
|
|
return {
|
|
submit() { return getActionText(getStep5SubmitButton()); },
|
|
day() { return findBirthdayReactAriaSelect('日')?.nativeSelect?.id || ''; },
|
|
};
|
|
`)();
|
|
|
|
assert.equal(api.submit(), 'アカウントを作成');
|
|
assert.equal(api.day(), 'day-select');
|
|
});
|