feat: 更新手机号验证设置UI,优化显示逻辑并添加相关测试
This commit is contained in:
@@ -10097,6 +10097,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
setEmailState,
|
setEmailState,
|
||||||
SIGNUP_ENTRY_URL,
|
SIGNUP_ENTRY_URL,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
|
waitForTabStableComplete,
|
||||||
waitForTabUrlMatch,
|
waitForTabUrlMatch,
|
||||||
});
|
});
|
||||||
const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.createVerificationFlowHelpers({
|
const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.createVerificationFlowHelpers({
|
||||||
@@ -10232,6 +10233,7 @@ const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({
|
|||||||
shouldUseCustomRegistrationEmail,
|
shouldUseCustomRegistrationEmail,
|
||||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
|
waitForTabStableComplete,
|
||||||
phoneVerificationHelpers,
|
phoneVerificationHelpers,
|
||||||
resolveSignupMethod,
|
resolveSignupMethod,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4079,15 +4079,21 @@
|
|||||||
if (!normalizedActivation) {
|
if (!normalizedActivation) {
|
||||||
throw new Error('步骤 2:接码平台返回的手机号订单无效。');
|
throw new Error('步骤 2:接码平台返回的手机号订单无效。');
|
||||||
}
|
}
|
||||||
|
const countryConfig = resolveCountryConfigFromActivation(normalizedActivation, state);
|
||||||
|
const signupActivation = normalizeActivation({
|
||||||
|
...normalizedActivation,
|
||||||
|
countryId: countryConfig?.id ?? normalizedActivation.countryId,
|
||||||
|
countryLabel: normalizedActivation.countryLabel || countryConfig?.label || '',
|
||||||
|
}) || normalizedActivation;
|
||||||
await persistSignupPhoneRuntimeState({
|
await persistSignupPhoneRuntimeState({
|
||||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
signupPhoneNumber: signupActivation.phoneNumber,
|
||||||
signupPhoneActivation: normalizedActivation,
|
signupPhoneActivation: signupActivation,
|
||||||
signupPhoneVerificationRequestedAt: null,
|
signupPhoneVerificationRequestedAt: null,
|
||||||
signupPhoneVerificationPurpose: 'signup',
|
signupPhoneVerificationPurpose: 'signup',
|
||||||
accountIdentifierType: 'phone',
|
accountIdentifierType: 'phone',
|
||||||
accountIdentifier: normalizedActivation.phoneNumber,
|
accountIdentifier: signupActivation.phoneNumber,
|
||||||
});
|
});
|
||||||
return normalizedActivation;
|
return signupActivation;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
setEmailState,
|
setEmailState,
|
||||||
SIGNUP_ENTRY_URL,
|
SIGNUP_ENTRY_URL,
|
||||||
SIGNUP_PAGE_INJECT_FILES,
|
SIGNUP_PAGE_INJECT_FILES,
|
||||||
|
waitForTabStableComplete = null,
|
||||||
waitForTabUrlMatch,
|
waitForTabUrlMatch,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
@@ -139,6 +140,22 @@
|
|||||||
throw new Error(`注册身份提交后未能识别当前页面,既不是密码页、验证码页,也不是资料页。URL: ${landingUrl || 'unknown'}`);
|
throw new Error(`注册身份提交后未能识别当前页面,既不是密码页、验证码页,也不是资料页。URL: ${landingUrl || 'unknown'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (landingState !== 'password_page' && typeof waitForTabStableComplete === 'function') {
|
||||||
|
const stableTab = await waitForTabStableComplete(tabId, {
|
||||||
|
timeoutMs: 45000,
|
||||||
|
retryDelayMs: 300,
|
||||||
|
stableMs: 800,
|
||||||
|
initialDelayMs: 300,
|
||||||
|
});
|
||||||
|
if (stableTab?.url) {
|
||||||
|
const stableState = resolveSignupPostIdentityState(stableTab.url);
|
||||||
|
if (stableState) {
|
||||||
|
landingUrl = stableTab.url;
|
||||||
|
landingState = stableState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await ensureContentScriptReadyOnTab('signup-page', tabId, {
|
await ensureContentScriptReadyOnTab('signup-page', tabId, {
|
||||||
inject: SIGNUP_PAGE_INJECT_FILES,
|
inject: SIGNUP_PAGE_INJECT_FILES,
|
||||||
injectSource: 'signup-page',
|
injectSource: 'signup-page',
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
shouldUseCustomRegistrationEmail,
|
shouldUseCustomRegistrationEmail,
|
||||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
|
waitForTabStableComplete = null,
|
||||||
phoneVerificationHelpers = null,
|
phoneVerificationHelpers = null,
|
||||||
resolveSignupMethod = () => 'email',
|
resolveSignupMethod = () => 'email',
|
||||||
} = deps;
|
} = deps;
|
||||||
@@ -119,6 +120,16 @@
|
|||||||
|
|
||||||
await chrome.tabs.update(signupTabId, { active: true });
|
await chrome.tabs.update(signupTabId, { active: true });
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
|
if (typeof waitForTabStableComplete === 'function') {
|
||||||
|
await addLog('步骤 4:等待注册验证码页面完成加载后再继续...', 'info');
|
||||||
|
await waitForTabStableComplete(signupTabId, {
|
||||||
|
timeoutMs: 45000,
|
||||||
|
retryDelayMs: 300,
|
||||||
|
stableMs: 800,
|
||||||
|
initialDelayMs: 300,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throwIfStopped();
|
||||||
await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...');
|
await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...');
|
||||||
|
|
||||||
const prepareRequest = {
|
const prepareRequest = {
|
||||||
|
|||||||
+378
-37
@@ -1147,8 +1147,8 @@ function normalizePhoneDigits(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractDialCodeFromText(value) {
|
function extractDialCodeFromText(value) {
|
||||||
const match = String(value || '').match(/\(\+\s*(\d{1,4})\s*\)|\+\s*(\d{1,4})\b/);
|
const match = String(value || '').match(/\(\+\s*(\d{1,4})\s*\)|\+\s*\(\s*(\d{1,4})\s*\)|\+\s*(\d{1,4})\b/);
|
||||||
return String(match?.[1] || match?.[2] || '').trim();
|
return String(match?.[1] || match?.[2] || match?.[3] || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatchSignupPhoneFieldEvents(element) {
|
function dispatchSignupPhoneFieldEvents(element) {
|
||||||
@@ -1162,12 +1162,48 @@ function normalizeSignupCountryLabel(value) {
|
|||||||
.normalize('NFKD')
|
.normalize('NFKD')
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
.replace(/&/g, ' and ')
|
.replace(/&/g, ' and ')
|
||||||
.replace(/[^\w\s]/g, ' ')
|
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSignupCountryLabelAliases(value) {
|
||||||
|
const aliases = new Set();
|
||||||
|
const addAlias = (alias) => {
|
||||||
|
const normalized = normalizeSignupCountryLabel(alias);
|
||||||
|
if (normalized) {
|
||||||
|
aliases.add(normalized);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const raw = String(value || '').trim();
|
||||||
|
addAlias(raw);
|
||||||
|
|
||||||
|
const normalized = normalizeSignupCountryLabel(raw);
|
||||||
|
const compact = normalized.replace(/\s+/g, '');
|
||||||
|
if (
|
||||||
|
/(?:^|\s)(?:gb|uk)(?:\s|$)/i.test(raw)
|
||||||
|
|| /england|united\s*kingdom|great\s*britain|\bbritain\b/i.test(raw)
|
||||||
|
|| /英国|英格兰|大不列颠/.test(raw)
|
||||||
|
|| ['gb', 'uk', 'england', 'unitedkingdom', 'greatbritain', 'britain'].includes(compact)
|
||||||
|
) {
|
||||||
|
[
|
||||||
|
'GB',
|
||||||
|
'UK',
|
||||||
|
'United Kingdom',
|
||||||
|
'Great Britain',
|
||||||
|
'Britain',
|
||||||
|
'England',
|
||||||
|
'英国',
|
||||||
|
'英格兰',
|
||||||
|
'大不列颠',
|
||||||
|
].forEach(addAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(aliases);
|
||||||
|
}
|
||||||
|
|
||||||
function getSignupPhoneOptionLabel(option) {
|
function getSignupPhoneOptionLabel(option) {
|
||||||
return String(option?.textContent || option?.label || '')
|
return String(option?.textContent || option?.label || '')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
@@ -1242,12 +1278,72 @@ function getSignupPhoneForm(phoneInput = getSignupPhoneInput()) {
|
|||||||
return phoneInput?.closest?.('form') || null;
|
return phoneInput?.closest?.('form') || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSignupPhoneCountrySelect(phoneInput = getSignupPhoneInput()) {
|
function getSignupPhoneControlRoots(phoneInput = getSignupPhoneInput()) {
|
||||||
const form = getSignupPhoneForm(phoneInput);
|
const roots = [];
|
||||||
if (!form || typeof form.querySelector !== 'function') {
|
const addRoot = (root) => {
|
||||||
return null;
|
if (root && !roots.includes(root)) {
|
||||||
|
roots.push(root);
|
||||||
}
|
}
|
||||||
return form.querySelector('select');
|
};
|
||||||
|
|
||||||
|
addRoot(phoneInput?.closest?.('form'));
|
||||||
|
addRoot(phoneInput?.closest?.('fieldset'));
|
||||||
|
addRoot(phoneInput?.closest?.('[data-rac]'));
|
||||||
|
addRoot(phoneInput?.closest?.('[role="group"]'));
|
||||||
|
addRoot(phoneInput?.parentElement);
|
||||||
|
addRoot(phoneInput?.parentElement?.parentElement);
|
||||||
|
addRoot(document);
|
||||||
|
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
function querySignupPhoneCountryElements(root, selector) {
|
||||||
|
if (!root || !selector) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (typeof root.querySelectorAll === 'function') {
|
||||||
|
const directMatches = Array.from(root.querySelectorAll(selector));
|
||||||
|
if (directMatches.length > 0) {
|
||||||
|
return directMatches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof root.querySelector === 'function') {
|
||||||
|
const selectors = String(selector || '')
|
||||||
|
.split(',')
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const matches = [];
|
||||||
|
for (const part of selectors) {
|
||||||
|
const element = root.querySelector(part);
|
||||||
|
if (element && !matches.includes(element)) {
|
||||||
|
matches.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupPhoneCountrySelect(select) {
|
||||||
|
if (!select) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Array.from(select.options || []).some((option) => (
|
||||||
|
extractDialCodeFromText(getSignupPhoneOptionLabel(option))
|
||||||
|
|| /^[A-Z]{2}$/.test(normalizeSignupCountryOptionValue(option?.value))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPhoneCountrySelect(phoneInput = getSignupPhoneInput()) {
|
||||||
|
const selects = [];
|
||||||
|
for (const root of getSignupPhoneControlRoots(phoneInput)) {
|
||||||
|
for (const select of querySignupPhoneCountryElements(root, 'select')) {
|
||||||
|
if (!selects.includes(select)) {
|
||||||
|
selects.push(select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selects.find(isSignupPhoneCountrySelect) || selects[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSignupPhoneSelectedCountryOption(phoneInput = getSignupPhoneInput()) {
|
function getSignupPhoneSelectedCountryOption(phoneInput = getSignupPhoneInput()) {
|
||||||
@@ -1259,9 +1355,7 @@ function getSignupPhoneSelectedCountryOption(phoneInput = getSignupPhoneInput())
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSignupPhoneCountryButtonText(phoneInput = getSignupPhoneInput()) {
|
function getSignupPhoneCountryButtonText(phoneInput = getSignupPhoneInput()) {
|
||||||
const form = getSignupPhoneForm(phoneInput);
|
const button = getSignupPhoneCountryButton(phoneInput);
|
||||||
if (!form || typeof form.querySelector !== 'function') return '';
|
|
||||||
const button = form.querySelector('button[aria-haspopup="listbox"]');
|
|
||||||
if (!button) return '';
|
if (!button) return '';
|
||||||
const valueNode = button.querySelector('.react-aria-SelectValue');
|
const valueNode = button.querySelector('.react-aria-SelectValue');
|
||||||
return String(valueNode?.textContent || button.textContent || '')
|
return String(valueNode?.textContent || button.textContent || '')
|
||||||
@@ -1269,6 +1363,24 @@ function getSignupPhoneCountryButtonText(phoneInput = getSignupPhoneInput()) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSignupPhoneCountryButton(phoneInput = getSignupPhoneInput()) {
|
||||||
|
const candidates = [];
|
||||||
|
for (const root of getSignupPhoneControlRoots(phoneInput)) {
|
||||||
|
const buttons = querySignupPhoneCountryElements(
|
||||||
|
root,
|
||||||
|
'button[aria-haspopup="listbox"], [role="button"][aria-haspopup="listbox"], [role="combobox"][aria-haspopup="listbox"], button[aria-expanded]'
|
||||||
|
);
|
||||||
|
for (const button of buttons) {
|
||||||
|
if (!candidates.includes(button)) {
|
||||||
|
candidates.push(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates.find((button) => isVisibleElement(button) && extractDialCodeFromText(getActionText(button)))
|
||||||
|
|| candidates.find(isVisibleElement)
|
||||||
|
|| null;
|
||||||
|
}
|
||||||
|
|
||||||
function getSignupPhoneDisplayedDialCode(phoneInput = getSignupPhoneInput()) {
|
function getSignupPhoneDisplayedDialCode(phoneInput = getSignupPhoneInput()) {
|
||||||
const buttonDialCode = extractDialCodeFromText(getSignupPhoneCountryButtonText(phoneInput));
|
const buttonDialCode = extractDialCodeFromText(getSignupPhoneCountryButtonText(phoneInput));
|
||||||
if (buttonDialCode) {
|
if (buttonDialCode) {
|
||||||
@@ -1295,29 +1407,134 @@ function getSignupPhoneHiddenNumberInput(phoneInput = getSignupPhoneInput()) {
|
|||||||
return form.querySelector('input[name="phoneNumber"]');
|
return form.querySelector('input[name="phoneNumber"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveSignupPhoneDialCodeFromNumber(phoneNumber = '', texts = []) {
|
||||||
|
const digits = normalizePhoneDigits(phoneNumber);
|
||||||
|
if (!digits) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const textDialCodes = texts
|
||||||
|
.map((text) => normalizePhoneDigits(extractDialCodeFromText(text)))
|
||||||
|
.filter((dialCode) => dialCode && digits.startsWith(dialCode) && digits.length > dialCode.length)
|
||||||
|
.sort((left, right) => right.length - left.length);
|
||||||
|
if (textDialCodes[0]) {
|
||||||
|
return textDialCodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const knownDialCodes = [
|
||||||
|
'1246', '1264', '1268', '1284', '1340', '1345', '1441', '1473', '1649', '1664', '1670', '1671', '1684',
|
||||||
|
'1721', '1758', '1767', '1784', '1809', '1829', '1849', '1868', '1869', '1876',
|
||||||
|
'971', '962', '886', '880', '856', '855', '852', '853', '673', '672', '670', '599', '598', '597', '596',
|
||||||
|
'595', '594', '593', '592', '591', '590', '509', '508', '507', '506', '505', '504', '503', '502', '501',
|
||||||
|
'423', '421', '420', '389', '387', '386', '385', '383', '382', '381', '380', '379', '378', '377', '376',
|
||||||
|
'375', '374', '373', '372', '371', '370', '359', '358', '357', '356', '355', '354', '353', '352', '351',
|
||||||
|
'350', '299', '298', '297', '291', '290', '269', '268', '267', '266', '265', '264', '263', '262', '261',
|
||||||
|
'260', '258', '257', '256', '255', '254', '253', '252', '251', '250', '249', '248', '247', '246', '245',
|
||||||
|
'244', '243', '242', '241', '240', '239', '238', '237', '236', '235', '234', '233', '232', '231', '230',
|
||||||
|
'229', '228', '227', '226', '225', '224', '223', '222', '221', '220', '218', '216', '213', '212', '211',
|
||||||
|
'98', '95', '94', '93', '92', '91', '90', '89', '88', '86', '84', '82', '81', '66', '65', '64', '63',
|
||||||
|
'62', '61', '60', '58', '57', '56', '55', '54', '53', '52', '51', '49', '48', '47', '46', '45', '44',
|
||||||
|
'43', '41', '40', '39', '36', '34', '33', '32', '31', '30', '27', '20', '7', '1',
|
||||||
|
];
|
||||||
|
return knownDialCodes.find((code) => digits.startsWith(code) && digits.length > code.length) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSignupPhoneTargetDialCode(options = {}, targetOption = null) {
|
||||||
|
const optionDialCode = extractDialCodeFromText(getSignupPhoneOptionLabel(targetOption));
|
||||||
|
if (optionDialCode) {
|
||||||
|
return optionDialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countryText = String(options.countryLabel || '').trim();
|
||||||
|
if (/australia|澳大利亚/i.test(countryText)) return '61';
|
||||||
|
if (/thailand|泰国/i.test(countryText)) return '66';
|
||||||
|
if (/vietnam|越南/i.test(countryText)) return '84';
|
||||||
|
if (/england|united\s*kingdom|great\s*britain|\bbritain\b|英国|英格兰|uk|gb/i.test(countryText)) return '44';
|
||||||
|
|
||||||
|
return resolveSignupPhoneDialCodeFromNumber(options.phoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPhoneCountryTargetLabels(targetOption, options = {}) {
|
||||||
|
const labels = new Set();
|
||||||
|
const addLabel = (value) => {
|
||||||
|
getSignupCountryLabelAliases(value).forEach((alias) => labels.add(alias));
|
||||||
|
};
|
||||||
|
|
||||||
|
addLabel(options.countryLabel);
|
||||||
|
if (targetOption) {
|
||||||
|
getSignupPhoneCountryMatchLabels(targetOption).forEach(addLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doesSignupPhoneCountryTextMatchTarget(text, targetOption, options = {}) {
|
||||||
|
const normalizedText = normalizeSignupCountryLabel(text);
|
||||||
|
if (!normalizedText) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = getSignupPhoneCountryTargetLabels(targetOption, options);
|
||||||
|
if (labels.some((label) => (
|
||||||
|
label
|
||||||
|
&& (
|
||||||
|
normalizedText === label
|
||||||
|
|| (label.length > 1 && normalizedText.includes(label))
|
||||||
|
|| (normalizedText.length > 2 && label.includes(normalizedText))
|
||||||
|
)
|
||||||
|
))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption);
|
||||||
|
return Boolean(targetDialCode && extractDialCodeFromText(text) === targetDialCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options = {}) {
|
||||||
|
const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption);
|
||||||
|
const displayedText = getSignupPhoneCountryButtonText(phoneInput);
|
||||||
|
const displayedDialCode = extractDialCodeFromText(displayedText);
|
||||||
|
|
||||||
|
if (targetDialCode && displayedDialCode) {
|
||||||
|
return displayedDialCode === targetDialCode
|
||||||
|
&& (!displayedText || doesSignupPhoneCountryTextMatchTarget(displayedText, targetOption, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayedText && doesSignupPhoneCountryTextMatchTarget(displayedText, targetOption, options)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOption = getSignupPhoneSelectedCountryOption(phoneInput);
|
||||||
|
if (selectedOption && targetOption && isSameSignupCountryOption(selectedOption, targetOption)) {
|
||||||
|
return !displayedDialCode || !targetDialCode || displayedDialCode === targetDialCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(selectedOption && !targetOption && targetDialCode && displayedDialCode === targetDialCode);
|
||||||
|
}
|
||||||
|
|
||||||
function findSignupPhoneCountryOptionByLabel(phoneInput, countryLabel) {
|
function findSignupPhoneCountryOptionByLabel(phoneInput, countryLabel) {
|
||||||
const select = getSignupPhoneCountrySelect(phoneInput);
|
const select = getSignupPhoneCountrySelect(phoneInput);
|
||||||
if (!select) {
|
if (!select) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const normalizedTarget = normalizeSignupCountryLabel(countryLabel);
|
const normalizedTargets = getSignupCountryLabelAliases(countryLabel);
|
||||||
if (!normalizedTarget) {
|
if (normalizedTargets.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = Array.from(select.options || []);
|
const options = Array.from(select.options || []);
|
||||||
return options.find((option) => (
|
return options.find((option) => (
|
||||||
getSignupPhoneCountryMatchLabels(option).some((label) => normalizeSignupCountryLabel(label) === normalizedTarget)
|
getSignupPhoneCountryMatchLabels(option).some((label) => normalizedTargets.includes(normalizeSignupCountryLabel(label)))
|
||||||
))
|
))
|
||||||
|| options.find((option) => {
|
|| options.find((option) => {
|
||||||
const normalizedLabels = getSignupPhoneCountryMatchLabels(option)
|
const normalizedLabels = getSignupPhoneCountryMatchLabels(option)
|
||||||
.map((label) => normalizeSignupCountryLabel(label))
|
.map((label) => normalizeSignupCountryLabel(label))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
return normalizedLabels.some((optionLabel) => (
|
return normalizedLabels.some((optionLabel) => normalizedTargets.some((normalizedTarget) => (
|
||||||
optionLabel.length > 2
|
optionLabel.length > 2
|
||||||
&& normalizedTarget.length > 2
|
&& normalizedTarget.length > 2
|
||||||
&& (optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel))
|
&& (optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel))
|
||||||
));
|
)));
|
||||||
})
|
})
|
||||||
|| null;
|
|| null;
|
||||||
}
|
}
|
||||||
@@ -1347,7 +1564,7 @@ function findSignupPhoneCountryOptionByPhoneNumber(phoneInput, phoneNumber) {
|
|||||||
return bestMatch;
|
return bestMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function trySelectSignupPhoneCountryOption(select, targetOption) {
|
async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInput = getSignupPhoneInput(), options = {}) {
|
||||||
if (!select || !targetOption) {
|
if (!select || !targetOption) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1355,47 +1572,123 @@ async function trySelectSignupPhoneCountryOption(select, targetOption) {
|
|||||||
? (select.options?.[select.selectedIndex] || null)
|
? (select.options?.[select.selectedIndex] || null)
|
||||||
: null;
|
: null;
|
||||||
if (selectedOption && isSameSignupCountryOption(selectedOption, targetOption)) {
|
if (selectedOption && isSameSignupCountryOption(selectedOption, targetOption)) {
|
||||||
return true;
|
dispatchSignupPhoneFieldEvents(select);
|
||||||
|
await sleep(120);
|
||||||
|
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
|
||||||
}
|
}
|
||||||
select.value = String(targetOption.value || '');
|
select.value = String(targetOption.value || '');
|
||||||
dispatchSignupPhoneFieldEvents(select);
|
dispatchSignupPhoneFieldEvents(select);
|
||||||
await sleep(250);
|
await sleep(250);
|
||||||
const nextSelectedOption = select.selectedIndex >= 0
|
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
|
||||||
? (select.options?.[select.selectedIndex] || null)
|
}
|
||||||
: null;
|
|
||||||
return Boolean(nextSelectedOption && isSameSignupCountryOption(nextSelectedOption, targetOption));
|
function getVisibleSignupPhoneCountryListboxOptions() {
|
||||||
|
const seen = new Set();
|
||||||
|
return Array.from(document.querySelectorAll('[role="listbox"] [role="option"], [role="option"]'))
|
||||||
|
.filter((option) => {
|
||||||
|
if (!option || seen.has(option)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(option);
|
||||||
|
return isVisibleElement(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSignupPhoneCountryListboxOption(targetOption, options = {}) {
|
||||||
|
const candidates = getVisibleSignupPhoneCountryListboxOptions();
|
||||||
|
const byLabel = candidates.find((option) => doesSignupPhoneCountryTextMatchTarget(getActionText(option), targetOption, options));
|
||||||
|
if (byLabel) {
|
||||||
|
return byLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption);
|
||||||
|
if (!targetDialCode) {
|
||||||
|
const digits = normalizePhoneDigits(options.phoneNumber);
|
||||||
|
let bestMatch = null;
|
||||||
|
let bestDialCodeLength = 0;
|
||||||
|
for (const option of candidates) {
|
||||||
|
const dialCode = normalizePhoneDigits(extractDialCodeFromText(getActionText(option)));
|
||||||
|
if (!dialCode || !digits.startsWith(dialCode) || dialCode.length <= bestDialCodeLength) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bestMatch = option;
|
||||||
|
bestDialCodeLength = dialCode.length;
|
||||||
|
}
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
|
return candidates.find((option) => extractDialCodeFromText(getActionText(option)) === targetDialCode) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options = {}) {
|
||||||
|
const button = getSignupPhoneCountryButton(phoneInput);
|
||||||
|
if (!button) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateClick(button);
|
||||||
|
await sleep(200);
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < 3000) {
|
||||||
|
throwIfStopped();
|
||||||
|
const option = findSignupPhoneCountryListboxOption(targetOption, options);
|
||||||
|
if (option) {
|
||||||
|
simulateClick(option);
|
||||||
|
await sleep(450);
|
||||||
|
if (isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await sleep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSignupPhoneCountrySelected(phoneInput, options = {}) {
|
async function ensureSignupPhoneCountrySelected(phoneInput, options = {}) {
|
||||||
const select = getSignupPhoneCountrySelect(phoneInput);
|
const select = getSignupPhoneCountrySelect(phoneInput);
|
||||||
if (!select) {
|
const hasCountryControl = Boolean(select || getSignupPhoneCountryButton(phoneInput));
|
||||||
|
if (!hasCountryControl) {
|
||||||
return {
|
return {
|
||||||
hasSelect: false,
|
hasSelect: false,
|
||||||
|
hasCountryControl: false,
|
||||||
matched: false,
|
matched: false,
|
||||||
selectedOption: null,
|
selectedOption: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const byLabel = findSignupPhoneCountryOptionByLabel(phoneInput, options.countryLabel);
|
const byLabel = findSignupPhoneCountryOptionByLabel(phoneInput, options.countryLabel);
|
||||||
if (await trySelectSignupPhoneCountryOption(select, byLabel)) {
|
|
||||||
return {
|
|
||||||
hasSelect: true,
|
|
||||||
matched: true,
|
|
||||||
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const byPhoneNumber = findSignupPhoneCountryOptionByPhoneNumber(phoneInput, options.phoneNumber);
|
const byPhoneNumber = findSignupPhoneCountryOptionByPhoneNumber(phoneInput, options.phoneNumber);
|
||||||
if (await trySelectSignupPhoneCountryOption(select, byPhoneNumber)) {
|
const targets = [byLabel, byPhoneNumber, null].filter((target, index, list) => (
|
||||||
|
index === list.findIndex((item) => (
|
||||||
|
(!item && !target)
|
||||||
|
|| (item && target && isSameSignupCountryOption(item, target))
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
for (const targetOption of targets) {
|
||||||
|
if (await trySelectSignupPhoneCountryOption(select, targetOption, phoneInput, options)) {
|
||||||
return {
|
return {
|
||||||
hasSelect: true,
|
hasSelect: Boolean(select),
|
||||||
|
hasCountryControl: true,
|
||||||
matched: true,
|
matched: true,
|
||||||
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options)) {
|
||||||
return {
|
return {
|
||||||
hasSelect: true,
|
hasSelect: Boolean(select),
|
||||||
|
hasCountryControl: true,
|
||||||
|
matched: true,
|
||||||
|
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasSelect: Boolean(select),
|
||||||
|
hasCountryControl: true,
|
||||||
matched: false,
|
matched: false,
|
||||||
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
selectedOption: getSignupPhoneSelectedCountryOption(phoneInput),
|
||||||
};
|
};
|
||||||
@@ -1549,8 +1842,9 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) {
|
|||||||
countryLabel,
|
countryLabel,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
});
|
});
|
||||||
if (countrySelection.hasSelect && !countrySelection.matched) {
|
if (countrySelection.hasCountryControl && !countrySelection.matched) {
|
||||||
log(`步骤 2:手机号国家下拉框未能自动切换到 ${countryLabel || phoneNumber},将继续使用当前国家。`, 'warn');
|
const currentCountryText = getSignupPhoneCountryButtonText(snapshot.phoneInput) || '未知';
|
||||||
|
throw new Error(`步骤 2:手机号国家下拉框未能自动切换到 ${countryLabel || phoneNumber},当前显示为 ${currentCountryText},已停止提交以避免区号不匹配。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialCode = resolveSignupPhoneDialCode(snapshot.phoneInput, {
|
const dialCode = resolveSignupPhoneDialCode(snapshot.phoneInput, {
|
||||||
@@ -2046,6 +2340,44 @@ function isPhoneVerificationPageReady() {
|
|||||||
&& /check\s+your\s+phone|phone\s+verification|verify\s+your\s+phone|sms|text\s+message|code\s+to\s+\+/.test(pageText);
|
&& /check\s+your\s+phone|phone\s+verification|verify\s+your\s+phone|sms|text\s+message|code\s+to\s+\+/.test(pageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDocumentReadyStateSnapshot() {
|
||||||
|
const readyState = typeof document !== 'undefined' && document
|
||||||
|
? String(document.readyState || '').trim().toLowerCase()
|
||||||
|
: '';
|
||||||
|
return readyState || 'complete';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDocumentLoadComplete() {
|
||||||
|
return getDocumentReadyStateSnapshot() === 'complete';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForDocumentLoadComplete(timeout = 15000, label = '页面') {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
throwIfStopped();
|
||||||
|
if (isDocumentLoadComplete()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await sleep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`${label}长时间未完成加载,当前 readyState=${getDocumentReadyStateSnapshot()}。URL: ${location.href}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupVerificationPageInteractiveReady(snapshot = null) {
|
||||||
|
if (!isDocumentLoadComplete()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedSnapshot = snapshot || inspectSignupVerificationState();
|
||||||
|
if (resolvedSnapshot?.state !== 'verification') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(getVerificationCodeTarget());
|
||||||
|
}
|
||||||
|
|
||||||
function isStep8Ready() {
|
function isStep8Ready() {
|
||||||
const continueBtn = getPrimaryContinueButton();
|
const continueBtn = getPrimaryContinueButton();
|
||||||
if (!continueBtn) return false;
|
if (!continueBtn) return false;
|
||||||
@@ -3191,6 +3523,10 @@ async function waitForSignupVerificationTransition(timeout = 5000) {
|
|||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
|
|
||||||
const snapshot = inspectSignupVerificationState();
|
const snapshot = inspectSignupVerificationState();
|
||||||
|
if (snapshot.state === 'verification' && !isSignupVerificationPageInteractiveReady(snapshot)) {
|
||||||
|
await sleep(200);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
snapshot.state === 'step5'
|
snapshot.state === 'step5'
|
||||||
|| snapshot.state === 'logged_in_home'
|
|| snapshot.state === 'logged_in_home'
|
||||||
@@ -3241,7 +3577,9 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.state === 'verification') {
|
if (snapshot.state === 'verification') {
|
||||||
log(`${prepareLogLabel}:验证码页面已就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok');
|
await waitForDocumentLoadComplete(15000, `${prepareLogLabel}:注册验证码页面`);
|
||||||
|
await waitForVerificationCodeTarget(15000);
|
||||||
|
log(`${prepareLogLabel}:验证码页面已完成加载并就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok');
|
||||||
return { ready: true, retried: recoveryRound, prepareSource };
|
return { ready: true, retried: recoveryRound, prepareSource };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3516,6 +3854,9 @@ async function fillVerificationCode(step, payload) {
|
|||||||
allowPhoneVerificationPage: payload?.purpose === 'login' || payload?.loginIdentifierType === 'phone',
|
allowPhoneVerificationPage: payload?.purpose === 'login' || payload?.loginIdentifierType === 'phone',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (step === 4) {
|
||||||
|
await waitForDocumentLoadComplete(15000, `步骤 ${step}:注册验证码页面`);
|
||||||
|
}
|
||||||
|
|
||||||
const combinedSignupProfilePage = step === 4
|
const combinedSignupProfilePage = step === 4
|
||||||
&& await waitForCombinedSignupVerificationProfilePage();
|
&& await waitForCombinedSignupVerificationProfilePage();
|
||||||
|
|||||||
@@ -2271,6 +2271,11 @@ header {
|
|||||||
min-height: 33px;
|
min-height: 33px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signup-phone-runtime-inline,
|
||||||
|
.signup-phone-runtime-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.hero-sms-price-preview-stack {
|
.hero-sms-price-preview-stack {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -492,12 +492,6 @@
|
|||||||
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
|
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-signup-phone" style="display:none;">
|
|
||||||
<span class="data-label">注册手机号</span>
|
|
||||||
<div class="data-inline">
|
|
||||||
<input type="text" id="input-signup-phone" class="data-input" placeholder="步骤 2 自动回填,也可手动填写当前注册手机号" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-row module-divider-start" id="row-auto-delay-settings">
|
<div class="data-row module-divider-start" id="row-auto-delay-settings">
|
||||||
<span class="data-label">延迟</span>
|
<span class="data-label">延迟</span>
|
||||||
<div class="data-inline setting-pair">
|
<div class="data-inline setting-pair">
|
||||||
@@ -1424,6 +1418,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="data-row" id="row-hero-sms-runtime-pair" style="display:none;">
|
<div class="data-row" id="row-hero-sms-runtime-pair" style="display:none;">
|
||||||
<span class="data-label">运行状态</span>
|
<span class="data-label">运行状态</span>
|
||||||
<div class="data-inline hero-sms-runtime-grid">
|
<div class="data-inline hero-sms-runtime-grid">
|
||||||
@@ -1447,7 +1444,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="data-row" id="row-signup-phone" style="display:none;">
|
||||||
|
<span class="data-label">注册手机号</span>
|
||||||
|
<div class="data-inline signup-phone-runtime-inline">
|
||||||
|
<input type="text" id="input-signup-phone" class="data-input mono signup-phone-runtime-input" placeholder="完整国际号码,如 +44..." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+20
-7
@@ -6851,11 +6851,6 @@ function updatePhoneVerificationSettingsUI() {
|
|||||||
typeof rowNexSmsServiceCode !== 'undefined' ? rowNexSmsServiceCode : null,
|
typeof rowNexSmsServiceCode !== 'undefined' ? rowNexSmsServiceCode : null,
|
||||||
typeof rowHeroSmsMaxPrice !== 'undefined' ? rowHeroSmsMaxPrice : null,
|
typeof rowHeroSmsMaxPrice !== 'undefined' ? rowHeroSmsMaxPrice : null,
|
||||||
typeof rowFiveSimOperator !== 'undefined' ? rowFiveSimOperator : null,
|
typeof rowFiveSimOperator !== 'undefined' ? rowFiveSimOperator : null,
|
||||||
typeof rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null,
|
|
||||||
typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null,
|
|
||||||
typeof rowHeroSmsCurrentCountdown !== 'undefined' ? rowHeroSmsCurrentCountdown : null,
|
|
||||||
typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : null,
|
|
||||||
typeof rowHeroSmsPreferredActivation !== 'undefined' ? rowHeroSmsPreferredActivation : null,
|
|
||||||
typeof rowPhoneCodeSettingsGroup !== 'undefined' ? rowPhoneCodeSettingsGroup : null,
|
typeof rowPhoneCodeSettingsGroup !== 'undefined' ? rowPhoneCodeSettingsGroup : null,
|
||||||
typeof rowPhoneVerificationResendCount !== 'undefined' ? rowPhoneVerificationResendCount : null,
|
typeof rowPhoneVerificationResendCount !== 'undefined' ? rowPhoneVerificationResendCount : null,
|
||||||
typeof rowPhoneReplacementLimit !== 'undefined' ? rowPhoneReplacementLimit : null,
|
typeof rowPhoneReplacementLimit !== 'undefined' ? rowPhoneReplacementLimit : null,
|
||||||
@@ -6885,6 +6880,21 @@ function updatePhoneVerificationSettingsUI() {
|
|||||||
if (rowFiveSimOperator) {
|
if (rowFiveSimOperator) {
|
||||||
rowFiveSimOperator.style.display = showSettings && fiveSimProvider ? '' : 'none';
|
rowFiveSimOperator.style.display = showSettings && fiveSimProvider ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
const runtimeVisible = enabled;
|
||||||
|
[
|
||||||
|
typeof rowHeroSmsRuntimePair !== 'undefined' ? rowHeroSmsRuntimePair : null,
|
||||||
|
typeof rowHeroSmsCurrentNumber !== 'undefined' ? rowHeroSmsCurrentNumber : null,
|
||||||
|
typeof rowHeroSmsCurrentCountdown !== 'undefined' ? rowHeroSmsCurrentCountdown : null,
|
||||||
|
typeof rowHeroSmsCurrentCode !== 'undefined' ? rowHeroSmsCurrentCode : null,
|
||||||
|
typeof rowHeroSmsPreferredActivation !== 'undefined' ? rowHeroSmsPreferredActivation : null,
|
||||||
|
].forEach((row) => {
|
||||||
|
if (row) {
|
||||||
|
row.style.display = runtimeVisible ? '' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (typeof syncSignupPhoneInputFromState === 'function') {
|
||||||
|
syncSignupPhoneInputFromState(latestState);
|
||||||
|
}
|
||||||
if (!showSettings && typeof rowHeroSmsPriceTiers !== 'undefined' && rowHeroSmsPriceTiers) {
|
if (!showSettings && typeof rowHeroSmsPriceTiers !== 'undefined' && rowHeroSmsPriceTiers) {
|
||||||
rowHeroSmsPriceTiers.style.display = 'none';
|
rowHeroSmsPriceTiers.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -7008,6 +7018,9 @@ function syncSignupPhoneInputFromState(state = latestState) {
|
|||||||
inputSignupPhone.value = signupPhone;
|
inputSignupPhone.value = signupPhone;
|
||||||
}
|
}
|
||||||
if (typeof rowSignupPhone !== 'undefined' && rowSignupPhone) {
|
if (typeof rowSignupPhone !== 'undefined' && rowSignupPhone) {
|
||||||
|
const phoneVerificationEnabled = typeof inputPhoneVerificationEnabled !== 'undefined' && inputPhoneVerificationEnabled
|
||||||
|
? Boolean(inputPhoneVerificationEnabled.checked)
|
||||||
|
: Boolean(state?.phoneVerificationEnabled || latestState?.phoneVerificationEnabled);
|
||||||
const rawSignupMethod = state?.signupMethod || (
|
const rawSignupMethod = state?.signupMethod || (
|
||||||
typeof getSelectedSignupMethod === 'function'
|
typeof getSelectedSignupMethod === 'function'
|
||||||
? getSelectedSignupMethod()
|
? getSelectedSignupMethod()
|
||||||
@@ -7016,7 +7029,7 @@ function syncSignupPhoneInputFromState(state = latestState) {
|
|||||||
const selectedMethod = typeof normalizeSignupMethod === 'function'
|
const selectedMethod = typeof normalizeSignupMethod === 'function'
|
||||||
? normalizeSignupMethod(rawSignupMethod)
|
? normalizeSignupMethod(rawSignupMethod)
|
||||||
: (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email');
|
: (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email');
|
||||||
rowSignupPhone.style.display = (selectedMethod === 'phone' || Boolean(signupPhone)) ? '' : 'none';
|
rowSignupPhone.style.display = phoneVerificationEnabled && (selectedMethod === 'phone' || Boolean(signupPhone)) ? '' : 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7044,7 +7057,7 @@ async function openPlusManualConfirmationDialog(options = {}) {
|
|||||||
}
|
}
|
||||||
const result = await sharedFormDialog.open({
|
const result = await sharedFormDialog.open({
|
||||||
title: String(options.title || '').trim() || 'GPC OTP 验证',
|
title: String(options.title || '').trim() || 'GPC OTP 验证',
|
||||||
message: String(options.message || '').trim() || '请输入收到的 OTP 验证码。',
|
message: String(options.message || '').trim() || '请在WhatsApp里面获取验证码(耐心等待三十秒左右)',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
key: 'otp',
|
key: 'otp',
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ test('sidepanel html exposes phone verification toggle and multi-provider SMS ro
|
|||||||
assert.match(html, /id="row-signup-method"/);
|
assert.match(html, /id="row-signup-method"/);
|
||||||
assert.match(html, /id="row-signup-phone"/);
|
assert.match(html, /id="row-signup-phone"/);
|
||||||
assert.match(html, /id="input-signup-phone"/);
|
assert.match(html, /id="input-signup-phone"/);
|
||||||
|
assert.ok(
|
||||||
|
html.indexOf('id="row-signup-phone"') > html.indexOf('id="phone-verification-section"'),
|
||||||
|
'signup phone runtime row should live inside the phone verification card'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
html.indexOf('id="row-signup-phone"') > html.indexOf('id="row-hero-sms-runtime-pair"'),
|
||||||
|
'signup phone runtime row should sit below the SMS order runtime row'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
html.indexOf('id="row-signup-phone"') > html.indexOf('hero-sms-runtime-grid'),
|
||||||
|
'signup phone runtime row should not be embedded in the SMS order runtime grid'
|
||||||
|
);
|
||||||
assert.match(html, /data-signup-method="email"/);
|
assert.match(html, /data-signup-method="email"/);
|
||||||
assert.match(html, /data-signup-method="phone"/);
|
assert.match(html, /data-signup-method="phone"/);
|
||||||
assert.match(html, /id="row-phone-sms-provider"/);
|
assert.match(html, /id="row-phone-sms-provider"/);
|
||||||
@@ -229,12 +241,13 @@ return {
|
|||||||
|
|
||||||
test('updatePhoneVerificationSettingsUI toggles SMS rows from the sms switch and provider selection', () => {
|
test('updatePhoneVerificationSettingsUI toggles SMS rows from the sms switch and provider selection', () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const phoneVerificationSectionExpanded = true;
|
let phoneVerificationSectionExpanded = false;
|
||||||
let latestState = {};
|
let latestState = {};
|
||||||
const inputPhoneVerificationEnabled = { checked: false };
|
const inputPhoneVerificationEnabled = { checked: false };
|
||||||
const rowPhoneVerificationEnabled = { style: { display: 'none' } };
|
const rowPhoneVerificationEnabled = { style: { display: 'none' } };
|
||||||
const rowPhoneVerificationFold = { style: { display: 'none' } };
|
const rowPhoneVerificationFold = { style: { display: 'none' } };
|
||||||
const rowSignupMethod = { style: { display: 'none' } };
|
const rowSignupMethod = { style: { display: 'none' } };
|
||||||
|
const rowSignupPhone = { style: { display: 'none' } };
|
||||||
const rowPhoneSmsProvider = { style: { display: 'none' } };
|
const rowPhoneSmsProvider = { style: { display: 'none' } };
|
||||||
const rowPhoneSmsProviderOrder = { style: { display: 'none' } };
|
const rowPhoneSmsProviderOrder = { style: { display: 'none' } };
|
||||||
const rowPhoneSmsProviderOrderActions = { style: { display: 'none' } };
|
const rowPhoneSmsProviderOrderActions = { style: { display: 'none' } };
|
||||||
@@ -289,6 +302,7 @@ const rowNexSmsApiKey = { style: { display: 'none' } };
|
|||||||
const rowNexSmsCountry = { style: { display: 'none' } };
|
const rowNexSmsCountry = { style: { display: 'none' } };
|
||||||
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
const rowNexSmsCountryFallback = { style: { display: 'none' } };
|
||||||
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
const rowNexSmsServiceCode = { style: { display: 'none' } };
|
||||||
|
const rowHeroSmsRuntimePair = { style: { display: 'none' } };
|
||||||
const rowHeroSmsCurrentNumber = { style: { display: 'none' } };
|
const rowHeroSmsCurrentNumber = { style: { display: 'none' } };
|
||||||
const rowHeroSmsCurrentCountdown = { style: { display: 'none' } };
|
const rowHeroSmsCurrentCountdown = { style: { display: 'none' } };
|
||||||
const rowHeroSmsPriceTiers = { style: { display: 'none' } };
|
const rowHeroSmsPriceTiers = { style: { display: 'none' } };
|
||||||
@@ -309,14 +323,19 @@ function updateHeroSmsPlatformDisplay() {}
|
|||||||
function updateSignupMethodUI() {
|
function updateSignupMethodUI() {
|
||||||
rowSignupMethod.style.display = inputPhoneVerificationEnabled.checked ? '' : 'none';
|
rowSignupMethod.style.display = inputPhoneVerificationEnabled.checked ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
function syncSignupPhoneInputFromState() {
|
||||||
|
rowSignupPhone.style.display = inputPhoneVerificationEnabled.checked && latestState.signupPhoneNumber ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
${extractFunction('updatePhoneVerificationSettingsUI')}
|
${extractFunction('updatePhoneVerificationSettingsUI')}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
setExpanded(value) { phoneVerificationSectionExpanded = Boolean(value); },
|
||||||
setLatestState: (state) => { latestState = state || {}; },
|
setLatestState: (state) => { latestState = state || {}; },
|
||||||
rowPhoneVerificationEnabled,
|
rowPhoneVerificationEnabled,
|
||||||
rowPhoneVerificationFold,
|
rowPhoneVerificationFold,
|
||||||
rowSignupMethod,
|
rowSignupMethod,
|
||||||
|
rowSignupPhone,
|
||||||
rowPhoneSmsProvider,
|
rowPhoneSmsProvider,
|
||||||
rowPhoneSmsProviderOrder,
|
rowPhoneSmsProviderOrder,
|
||||||
rowPhoneSmsProviderOrderActions,
|
rowPhoneSmsProviderOrderActions,
|
||||||
@@ -338,6 +357,7 @@ return {
|
|||||||
rowNexSmsCountry,
|
rowNexSmsCountry,
|
||||||
rowNexSmsCountryFallback,
|
rowNexSmsCountryFallback,
|
||||||
rowNexSmsServiceCode,
|
rowNexSmsServiceCode,
|
||||||
|
rowHeroSmsRuntimePair,
|
||||||
rowHeroSmsCurrentNumber,
|
rowHeroSmsCurrentNumber,
|
||||||
rowHeroSmsCurrentCountdown,
|
rowHeroSmsCurrentCountdown,
|
||||||
rowHeroSmsPriceTiers,
|
rowHeroSmsPriceTiers,
|
||||||
@@ -358,12 +378,14 @@ return {
|
|||||||
assert.equal(api.rowPhoneVerificationEnabled.style.display, '');
|
assert.equal(api.rowPhoneVerificationEnabled.style.display, '');
|
||||||
assert.equal(api.rowPhoneVerificationFold.style.display, 'none');
|
assert.equal(api.rowPhoneVerificationFold.style.display, 'none');
|
||||||
assert.equal(api.rowSignupMethod.style.display, 'none');
|
assert.equal(api.rowSignupMethod.style.display, 'none');
|
||||||
|
assert.equal(api.rowSignupPhone.style.display, 'none');
|
||||||
assert.equal(api.rowPhoneSmsProvider.style.display, 'none');
|
assert.equal(api.rowPhoneSmsProvider.style.display, 'none');
|
||||||
assert.equal(api.rowPhoneSmsProviderOrder.style.display, 'none');
|
assert.equal(api.rowPhoneSmsProviderOrder.style.display, 'none');
|
||||||
assert.equal(api.rowPhoneSmsProviderOrderActions.style.display, 'none');
|
assert.equal(api.rowPhoneSmsProviderOrderActions.style.display, 'none');
|
||||||
assert.equal(api.btnTogglePhoneVerificationSection.disabled, true);
|
assert.equal(api.btnTogglePhoneVerificationSection.disabled, true);
|
||||||
assert.equal(api.btnTogglePhoneVerificationSection.textContent, '展开设置');
|
assert.equal(api.btnTogglePhoneVerificationSection.textContent, '展开设置');
|
||||||
assert.equal(api.rowHeroSmsPlatform.style.display, '');
|
assert.equal(api.rowHeroSmsPlatform.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsRuntimePair.style.display, 'none');
|
||||||
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
assert.equal(api.rowHeroSmsCountry.style.display, 'none');
|
||||||
assert.equal(api.rowHeroSmsCountryFallback.style.display, 'none');
|
assert.equal(api.rowHeroSmsCountryFallback.style.display, 'none');
|
||||||
assert.equal(api.rowHeroSmsAcquirePriority.style.display, 'none');
|
assert.equal(api.rowHeroSmsAcquirePriority.style.display, 'none');
|
||||||
@@ -392,6 +414,19 @@ return {
|
|||||||
assert.equal(api.rowNexSmsServiceCode.style.display, 'none');
|
assert.equal(api.rowNexSmsServiceCode.style.display, 'none');
|
||||||
|
|
||||||
api.inputPhoneVerificationEnabled.checked = true;
|
api.inputPhoneVerificationEnabled.checked = true;
|
||||||
|
api.setLatestState({ signupPhoneNumber: '66959916439' });
|
||||||
|
api.updatePhoneVerificationSettingsUI();
|
||||||
|
assert.equal(api.rowPhoneVerificationFold.style.display, 'none');
|
||||||
|
assert.equal(api.rowSignupMethod.style.display, '');
|
||||||
|
assert.equal(api.rowSignupPhone.style.display, '');
|
||||||
|
assert.equal(api.rowPhoneSmsProvider.style.display, 'none');
|
||||||
|
assert.equal(api.rowHeroSmsRuntimePair.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsCurrentNumber.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsCurrentCountdown.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsCurrentCode.style.display, '');
|
||||||
|
assert.equal(api.rowHeroSmsPreferredActivation.style.display, '');
|
||||||
|
|
||||||
|
api.setExpanded(true);
|
||||||
api.updatePhoneVerificationSettingsUI();
|
api.updatePhoneVerificationSettingsUI();
|
||||||
assert.equal(api.rowPhoneVerificationFold.style.display, '');
|
assert.equal(api.rowPhoneVerificationFold.style.display, '');
|
||||||
assert.equal(api.rowSignupMethod.style.display, '');
|
assert.equal(api.rowSignupMethod.style.display, '');
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ let latestState = {
|
|||||||
plusManualConfirmationStep: 7,
|
plusManualConfirmationStep: 7,
|
||||||
plusManualConfirmationMethod: 'gopay-otp',
|
plusManualConfirmationMethod: 'gopay-otp',
|
||||||
plusManualConfirmationTitle: 'GPC OTP 验证',
|
plusManualConfirmationTitle: 'GPC OTP 验证',
|
||||||
plusManualConfirmationMessage: '请输入 OTP。',
|
plusManualConfirmationMessage: '',
|
||||||
};
|
};
|
||||||
let activePlusManualConfirmationRequestId = '';
|
let activePlusManualConfirmationRequestId = '';
|
||||||
let plusManualConfirmationDialogInFlight = false;
|
let plusManualConfirmationDialogInFlight = false;
|
||||||
@@ -364,6 +364,7 @@ return { events, syncPlusManualConfirmationDialog };
|
|||||||
await api.syncPlusManualConfirmationDialog();
|
await api.syncPlusManualConfirmationDialog();
|
||||||
|
|
||||||
assert.equal(api.events[0].type, 'form');
|
assert.equal(api.events[0].type, 'form');
|
||||||
|
assert.equal(api.events[0].options.message, '请在WhatsApp里面获取验证码(耐心等待三十秒左右)');
|
||||||
assert.equal(api.events[0].options.confirmLabel, '提交 OTP');
|
assert.equal(api.events[0].options.confirmLabel, '提交 OTP');
|
||||||
const sendEvent = api.events.find((event) => event.type === 'send');
|
const sendEvent = api.events.find((event) => event.type === 'send');
|
||||||
assert.deepEqual(sendEvent.message.payload, {
|
assert.deepEqual(sendEvent.message.payload, {
|
||||||
|
|||||||
@@ -709,6 +709,9 @@ const countryButton = {
|
|||||||
get textContent() {
|
get textContent() {
|
||||||
return selectValueNode.textContent;
|
return selectValueNode.textContent;
|
||||||
},
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 240, height: 48 };
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
@@ -862,20 +865,33 @@ ${extractFunction('normalizePhoneDigits')}
|
|||||||
${extractFunction('extractDialCodeFromText')}
|
${extractFunction('extractDialCodeFromText')}
|
||||||
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
||||||
${extractFunction('normalizeSignupCountryLabel')}
|
${extractFunction('normalizeSignupCountryLabel')}
|
||||||
|
${extractFunction('getSignupCountryLabelAliases')}
|
||||||
${extractFunction('getSignupPhoneOptionLabel')}
|
${extractFunction('getSignupPhoneOptionLabel')}
|
||||||
${extractFunction('normalizeSignupCountryOptionValue')}
|
${extractFunction('normalizeSignupCountryOptionValue')}
|
||||||
${extractFunction('getSignupRegionDisplayName')}
|
${extractFunction('getSignupRegionDisplayName')}
|
||||||
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
||||||
${extractFunction('isSameSignupCountryOption')}
|
${extractFunction('isSameSignupCountryOption')}
|
||||||
${extractFunction('getSignupPhoneForm')}
|
${extractFunction('getSignupPhoneForm')}
|
||||||
|
${extractFunction('getSignupPhoneControlRoots')}
|
||||||
|
${extractFunction('querySignupPhoneCountryElements')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelect')}
|
||||||
${extractFunction('getSignupPhoneCountrySelect')}
|
${extractFunction('getSignupPhoneCountrySelect')}
|
||||||
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
||||||
${extractFunction('getSignupPhoneCountryButtonText')}
|
${extractFunction('getSignupPhoneCountryButtonText')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButton')}
|
||||||
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
||||||
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCodeFromNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneTargetDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneCountryTargetLabels')}
|
||||||
|
${extractFunction('doesSignupPhoneCountryTextMatchTarget')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelectionSynced')}
|
||||||
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
||||||
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
||||||
${extractFunction('trySelectSignupPhoneCountryOption')}
|
${extractFunction('trySelectSignupPhoneCountryOption')}
|
||||||
|
${extractFunction('getVisibleSignupPhoneCountryListboxOptions')}
|
||||||
|
${extractFunction('findSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryListboxOption')}
|
||||||
${extractFunction('ensureSignupPhoneCountrySelected')}
|
${extractFunction('ensureSignupPhoneCountrySelected')}
|
||||||
${extractFunction('toNationalPhoneNumber')}
|
${extractFunction('toNationalPhoneNumber')}
|
||||||
${extractFunction('toE164PhoneNumber')}
|
${extractFunction('toE164PhoneNumber')}
|
||||||
@@ -918,6 +934,584 @@ return {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('submitSignupPhoneNumberAndContinue clicks the visible country listbox when the hidden select does not update the button', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const logs = [];
|
||||||
|
const clicks = [];
|
||||||
|
const filled = [];
|
||||||
|
const selectEvents = [];
|
||||||
|
let now = 0;
|
||||||
|
let listboxOpen = false;
|
||||||
|
let visibleCountryValue = 'AU';
|
||||||
|
|
||||||
|
const continueButton = {
|
||||||
|
textContent: 'Continue',
|
||||||
|
value: '',
|
||||||
|
disabled: false,
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'submit';
|
||||||
|
if (name === 'aria-disabled') return 'false';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 200, height: 48 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const countryOptions = [
|
||||||
|
{ value: 'AU', textContent: 'Australia (+61)', buttonText: '澳大利亚 (+61)' },
|
||||||
|
{ value: 'GB', textContent: 'United Kingdom (+44)', buttonText: '英国 (+44)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const countrySelect = {
|
||||||
|
options: countryOptions,
|
||||||
|
selectedIndex: 0,
|
||||||
|
dispatchEvent(event) {
|
||||||
|
selectEvents.push(event?.type || '');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(countrySelect, 'value', {
|
||||||
|
get() {
|
||||||
|
return countryOptions[countrySelect.selectedIndex]?.value || '';
|
||||||
|
},
|
||||||
|
set(nextValue) {
|
||||||
|
const nextIndex = countryOptions.findIndex((option) => option.value === String(nextValue || ''));
|
||||||
|
if (nextIndex >= 0) {
|
||||||
|
countrySelect.selectedIndex = nextIndex;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hiddenPhoneInput = {
|
||||||
|
kind: 'hidden-phone',
|
||||||
|
value: '',
|
||||||
|
getAttribute() {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const phoneInput = {
|
||||||
|
kind: 'phone',
|
||||||
|
value: '',
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'tel';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
closest(selector) {
|
||||||
|
if (selector === 'form') {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
return form;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectValueNode = {
|
||||||
|
get textContent() {
|
||||||
|
return countryOptions.find((option) => option.value === visibleCountryValue)?.buttonText || '';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const countryButton = {
|
||||||
|
querySelector(selector) {
|
||||||
|
return selector === '.react-aria-SelectValue' ? selectValueNode : null;
|
||||||
|
},
|
||||||
|
get textContent() {
|
||||||
|
return selectValueNode.textContent;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const gbOption = {
|
||||||
|
textContent: '英国 (+44)',
|
||||||
|
value: '',
|
||||||
|
getAttribute() {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === 'select') return countrySelect;
|
||||||
|
if (selector === 'input[name="phoneNumber"]') return hiddenPhoneInput;
|
||||||
|
if (selector === 'button[aria-haspopup="listbox"]') return countryButton;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
documentElement: {
|
||||||
|
lang: 'zh-CN',
|
||||||
|
getAttribute(name) {
|
||||||
|
return name === 'lang' ? 'zh-CN' : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === SIGNUP_PHONE_INPUT_SELECTOR) {
|
||||||
|
return phoneInput;
|
||||||
|
}
|
||||||
|
if (selector === 'button[type="submit"], input[type="submit"]') {
|
||||||
|
return continueButton;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"]') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (selector === 'a, button, [role="button"], [role="link"]') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
||||||
|
return [continueButton];
|
||||||
|
}
|
||||||
|
if (selector === 'input') {
|
||||||
|
return [phoneInput];
|
||||||
|
}
|
||||||
|
if (selector.includes('[role="option"]')) {
|
||||||
|
return listboxOpen ? [gbOption] : [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const location = {
|
||||||
|
href: 'https://chatgpt.com/',
|
||||||
|
};
|
||||||
|
|
||||||
|
const window = {
|
||||||
|
setTimeout(fn) {
|
||||||
|
fn();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Date = {
|
||||||
|
now() {
|
||||||
|
return now;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
constructor(type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordInput() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupPasswordPage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordSubmitButton() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSignupEntryTrigger() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordDisplayedEmail() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageTextSnapshot() {
|
||||||
|
return countryButton.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfStopped() {}
|
||||||
|
function isStopError() { return false; }
|
||||||
|
|
||||||
|
function log(message, level = 'info') {
|
||||||
|
logs.push({ message, level });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function humanPause() {}
|
||||||
|
|
||||||
|
function simulateClick(target) {
|
||||||
|
clicks.push(getActionText(target));
|
||||||
|
if (target === countryButton) {
|
||||||
|
listboxOpen = true;
|
||||||
|
}
|
||||||
|
if (target === gbOption) {
|
||||||
|
visibleCountryValue = 'GB';
|
||||||
|
countrySelect.value = 'GB';
|
||||||
|
listboxOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillInput(target, value) {
|
||||||
|
target.value = value;
|
||||||
|
filled.push({ target: target.kind, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
now += ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
${extractConst('SIGNUP_ENTRY_TRIGGER_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_EMAIL_INPUT_SELECTOR')}
|
||||||
|
${extractConst('SIGNUP_PHONE_INPUT_SELECTOR')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_TO_EMAIL_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_EMAIL_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_WORK_EMAIL_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_PHONE_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_TO_PHONE_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_MORE_OPTIONS_PATTERN')}
|
||||||
|
|
||||||
|
${extractFunction('getSignupEmailInput')}
|
||||||
|
${extractFunction('getSignupPhoneInput')}
|
||||||
|
${extractFunction('findSignupUseEmailTrigger')}
|
||||||
|
${extractFunction('findSignupUsePhoneTrigger')}
|
||||||
|
${extractFunction('findSignupMoreOptionsTrigger')}
|
||||||
|
${extractFunction('getSignupEmailContinueButton')}
|
||||||
|
${extractFunction('inspectSignupEntryState')}
|
||||||
|
${extractFunction('getSignupEntryStateSummary')}
|
||||||
|
function getSignupEntryDiagnostics() { return {}; }
|
||||||
|
${extractFunction('normalizePhoneDigits')}
|
||||||
|
${extractFunction('extractDialCodeFromText')}
|
||||||
|
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
||||||
|
${extractFunction('normalizeSignupCountryLabel')}
|
||||||
|
${extractFunction('getSignupCountryLabelAliases')}
|
||||||
|
${extractFunction('getSignupPhoneOptionLabel')}
|
||||||
|
${extractFunction('normalizeSignupCountryOptionValue')}
|
||||||
|
${extractFunction('getSignupRegionDisplayName')}
|
||||||
|
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
||||||
|
${extractFunction('isSameSignupCountryOption')}
|
||||||
|
${extractFunction('getSignupPhoneForm')}
|
||||||
|
${extractFunction('getSignupPhoneControlRoots')}
|
||||||
|
${extractFunction('querySignupPhoneCountryElements')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelect')}
|
||||||
|
${extractFunction('getSignupPhoneCountrySelect')}
|
||||||
|
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButtonText')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButton')}
|
||||||
|
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCodeFromNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneTargetDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneCountryTargetLabels')}
|
||||||
|
${extractFunction('doesSignupPhoneCountryTextMatchTarget')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelectionSynced')}
|
||||||
|
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
||||||
|
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryOption')}
|
||||||
|
${extractFunction('getVisibleSignupPhoneCountryListboxOptions')}
|
||||||
|
${extractFunction('findSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('ensureSignupPhoneCountrySelected')}
|
||||||
|
${extractFunction('toNationalPhoneNumber')}
|
||||||
|
${extractFunction('toE164PhoneNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCode')}
|
||||||
|
${extractFunction('waitForSignupPhoneEntryState')}
|
||||||
|
${extractFunction('submitSignupPhoneNumberAndContinue')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
return submitSignupPhoneNumberAndContinue({
|
||||||
|
phoneNumber: '+447859232013',
|
||||||
|
countryLabel: 'United Kingdom',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getClicks() {
|
||||||
|
return clicks.slice();
|
||||||
|
},
|
||||||
|
getFilled() {
|
||||||
|
return filled.slice();
|
||||||
|
},
|
||||||
|
getSelectValue() {
|
||||||
|
return countrySelect.value;
|
||||||
|
},
|
||||||
|
getVisibleCountryText() {
|
||||||
|
return countryButton.textContent;
|
||||||
|
},
|
||||||
|
getSelectEvents() {
|
||||||
|
return selectEvents.slice();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.run();
|
||||||
|
|
||||||
|
assert.equal(result.submitted, true);
|
||||||
|
assert.equal(result.phoneInputValue, '7859232013');
|
||||||
|
assert.equal(api.getSelectValue(), 'GB');
|
||||||
|
assert.equal(api.getVisibleCountryText(), '英国 (+44)');
|
||||||
|
assert.deepEqual(api.getSelectEvents(), ['input', 'change']);
|
||||||
|
assert.deepEqual(api.getClicks(), ['澳大利亚 (+61)', '英国 (+44)', 'Continue']);
|
||||||
|
assert.deepEqual(api.getFilled(), [
|
||||||
|
{ target: 'phone', value: '7859232013' },
|
||||||
|
{ target: 'hidden-phone', value: '+447859232013' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('submitSignupPhoneNumberAndContinue can select country by phone dial code without country label or hidden select', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const clicks = [];
|
||||||
|
const filled = [];
|
||||||
|
let now = 0;
|
||||||
|
let listboxOpen = false;
|
||||||
|
let visibleCountryText = '印度尼西亚 +(62)';
|
||||||
|
|
||||||
|
const continueButton = {
|
||||||
|
textContent: 'Continue',
|
||||||
|
value: '',
|
||||||
|
disabled: false,
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'submit';
|
||||||
|
if (name === 'aria-disabled') return 'false';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 200, height: 48 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const phoneInput = {
|
||||||
|
kind: 'phone',
|
||||||
|
value: '',
|
||||||
|
parentElement: null,
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'type') return 'tel';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
closest() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const countryButton = {
|
||||||
|
textContent: '',
|
||||||
|
querySelector(selector) {
|
||||||
|
return selector === '.react-aria-SelectValue' ? { textContent: visibleCountryText } : null;
|
||||||
|
},
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'aria-haspopup') return 'listbox';
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 240, height: 48 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const gbOption = {
|
||||||
|
textContent: '英国 +(44)',
|
||||||
|
getAttribute() {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 200, height: 36 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const idOption = {
|
||||||
|
textContent: '印度尼西亚 +(62)',
|
||||||
|
getAttribute() {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 200, height: 36 };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const document = {
|
||||||
|
documentElement: {
|
||||||
|
lang: 'zh-CN',
|
||||||
|
getAttribute(name) {
|
||||||
|
return name === 'lang' ? 'zh-CN' : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === SIGNUP_PHONE_INPUT_SELECTOR) return phoneInput;
|
||||||
|
if (selector === 'button[type="submit"], input[type="submit"]') return continueButton;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"]') return [];
|
||||||
|
if (selector === 'a, button, [role="button"], [role="link"]') return [];
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') return [continueButton];
|
||||||
|
if (selector === 'input') return [phoneInput];
|
||||||
|
if (selector === 'select') return [];
|
||||||
|
if (selector.includes('aria-haspopup="listbox"') || selector.includes('aria-expanded')) return [countryButton];
|
||||||
|
if (selector.includes('[role="option"]')) return listboxOpen ? [idOption, gbOption] : [];
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const location = {
|
||||||
|
href: 'https://chatgpt.com/',
|
||||||
|
};
|
||||||
|
|
||||||
|
const window = {
|
||||||
|
setTimeout(fn) {
|
||||||
|
fn();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Date = {
|
||||||
|
now() {
|
||||||
|
return now;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
constructor(type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisibleElement(el) {
|
||||||
|
return Boolean(el) && (!el.getBoundingClientRect || el.getBoundingClientRect().width > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordInput() { return null; }
|
||||||
|
function isSignupPasswordPage() { return false; }
|
||||||
|
function getSignupPasswordSubmitButton() { return null; }
|
||||||
|
function findSignupEntryTrigger() { return null; }
|
||||||
|
function getSignupPasswordDisplayedEmail() { return ''; }
|
||||||
|
function getPageTextSnapshot() { return visibleCountryText; }
|
||||||
|
function throwIfStopped() {}
|
||||||
|
function isStopError() { return false; }
|
||||||
|
function log() {}
|
||||||
|
async function humanPause() {}
|
||||||
|
|
||||||
|
function simulateClick(target) {
|
||||||
|
clicks.push(getActionText(target) || visibleCountryText);
|
||||||
|
if (target === countryButton) {
|
||||||
|
listboxOpen = true;
|
||||||
|
}
|
||||||
|
if (target === gbOption) {
|
||||||
|
visibleCountryText = '英国 +(44)';
|
||||||
|
listboxOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillInput(target, value) {
|
||||||
|
target.value = value;
|
||||||
|
filled.push({ target: target.kind, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
now += ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
${extractConst('SIGNUP_ENTRY_TRIGGER_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_EMAIL_INPUT_SELECTOR')}
|
||||||
|
${extractConst('SIGNUP_PHONE_INPUT_SELECTOR')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_TO_EMAIL_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_EMAIL_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_WORK_EMAIL_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_PHONE_ACTION_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_SWITCH_TO_PHONE_PATTERN')}
|
||||||
|
${extractConst('SIGNUP_MORE_OPTIONS_PATTERN')}
|
||||||
|
|
||||||
|
${extractFunction('getSignupEmailInput')}
|
||||||
|
${extractFunction('getSignupPhoneInput')}
|
||||||
|
${extractFunction('findSignupUseEmailTrigger')}
|
||||||
|
${extractFunction('findSignupUsePhoneTrigger')}
|
||||||
|
${extractFunction('findSignupMoreOptionsTrigger')}
|
||||||
|
${extractFunction('getSignupEmailContinueButton')}
|
||||||
|
${extractFunction('inspectSignupEntryState')}
|
||||||
|
${extractFunction('getSignupEntryStateSummary')}
|
||||||
|
function getSignupEntryDiagnostics() { return {}; }
|
||||||
|
${extractFunction('normalizePhoneDigits')}
|
||||||
|
${extractFunction('extractDialCodeFromText')}
|
||||||
|
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
||||||
|
${extractFunction('normalizeSignupCountryLabel')}
|
||||||
|
${extractFunction('getSignupCountryLabelAliases')}
|
||||||
|
${extractFunction('getSignupPhoneOptionLabel')}
|
||||||
|
${extractFunction('normalizeSignupCountryOptionValue')}
|
||||||
|
${extractFunction('getSignupRegionDisplayName')}
|
||||||
|
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
||||||
|
${extractFunction('isSameSignupCountryOption')}
|
||||||
|
${extractFunction('getSignupPhoneForm')}
|
||||||
|
${extractFunction('getSignupPhoneControlRoots')}
|
||||||
|
${extractFunction('querySignupPhoneCountryElements')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelect')}
|
||||||
|
${extractFunction('getSignupPhoneCountrySelect')}
|
||||||
|
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButtonText')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButton')}
|
||||||
|
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCodeFromNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneTargetDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneCountryTargetLabels')}
|
||||||
|
${extractFunction('doesSignupPhoneCountryTextMatchTarget')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelectionSynced')}
|
||||||
|
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
||||||
|
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryOption')}
|
||||||
|
${extractFunction('getVisibleSignupPhoneCountryListboxOptions')}
|
||||||
|
${extractFunction('findSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('ensureSignupPhoneCountrySelected')}
|
||||||
|
${extractFunction('toNationalPhoneNumber')}
|
||||||
|
${extractFunction('toE164PhoneNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCode')}
|
||||||
|
${extractFunction('waitForSignupPhoneEntryState')}
|
||||||
|
${extractFunction('submitSignupPhoneNumberAndContinue')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
return submitSignupPhoneNumberAndContinue({
|
||||||
|
phoneNumber: '447423278610',
|
||||||
|
countryLabel: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getClicks() {
|
||||||
|
return clicks.slice();
|
||||||
|
},
|
||||||
|
getFilled() {
|
||||||
|
return filled.slice();
|
||||||
|
},
|
||||||
|
getVisibleCountryText() {
|
||||||
|
return visibleCountryText;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.run();
|
||||||
|
|
||||||
|
assert.equal(result.submitted, true);
|
||||||
|
assert.equal(result.phoneInputValue, '7423278610');
|
||||||
|
assert.equal(api.getVisibleCountryText(), '英国 +(44)');
|
||||||
|
assert.deepEqual(api.getClicks(), ['印度尼西亚 +(62)', '英国 +(44)', 'Continue']);
|
||||||
|
assert.deepEqual(api.getFilled(), [{ target: 'phone', value: '7423278610' }]);
|
||||||
|
});
|
||||||
|
|
||||||
test('submitSignupPhoneNumberAndContinue switches from email mode to phone mode and submits local number', async () => {
|
test('submitSignupPhoneNumberAndContinue switches from email mode to phone mode and submits local number', async () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const logs = [];
|
const logs = [];
|
||||||
@@ -1110,20 +1704,33 @@ ${extractFunction('normalizePhoneDigits')}
|
|||||||
${extractFunction('extractDialCodeFromText')}
|
${extractFunction('extractDialCodeFromText')}
|
||||||
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
${extractFunction('dispatchSignupPhoneFieldEvents')}
|
||||||
${extractFunction('normalizeSignupCountryLabel')}
|
${extractFunction('normalizeSignupCountryLabel')}
|
||||||
|
${extractFunction('getSignupCountryLabelAliases')}
|
||||||
${extractFunction('getSignupPhoneOptionLabel')}
|
${extractFunction('getSignupPhoneOptionLabel')}
|
||||||
${extractFunction('normalizeSignupCountryOptionValue')}
|
${extractFunction('normalizeSignupCountryOptionValue')}
|
||||||
${extractFunction('getSignupRegionDisplayName')}
|
${extractFunction('getSignupRegionDisplayName')}
|
||||||
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
${extractFunction('getSignupPhoneCountryMatchLabels')}
|
||||||
${extractFunction('isSameSignupCountryOption')}
|
${extractFunction('isSameSignupCountryOption')}
|
||||||
${extractFunction('getSignupPhoneForm')}
|
${extractFunction('getSignupPhoneForm')}
|
||||||
|
${extractFunction('getSignupPhoneControlRoots')}
|
||||||
|
${extractFunction('querySignupPhoneCountryElements')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelect')}
|
||||||
${extractFunction('getSignupPhoneCountrySelect')}
|
${extractFunction('getSignupPhoneCountrySelect')}
|
||||||
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
${extractFunction('getSignupPhoneSelectedCountryOption')}
|
||||||
${extractFunction('getSignupPhoneCountryButtonText')}
|
${extractFunction('getSignupPhoneCountryButtonText')}
|
||||||
|
${extractFunction('getSignupPhoneCountryButton')}
|
||||||
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
${extractFunction('getSignupPhoneDisplayedDialCode')}
|
||||||
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
${extractFunction('getSignupPhoneHiddenNumberInput')}
|
||||||
|
${extractFunction('resolveSignupPhoneDialCodeFromNumber')}
|
||||||
|
${extractFunction('resolveSignupPhoneTargetDialCode')}
|
||||||
|
${extractFunction('getSignupPhoneCountryTargetLabels')}
|
||||||
|
${extractFunction('doesSignupPhoneCountryTextMatchTarget')}
|
||||||
|
${extractFunction('isSignupPhoneCountrySelectionSynced')}
|
||||||
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
${extractFunction('findSignupPhoneCountryOptionByLabel')}
|
||||||
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
${extractFunction('findSignupPhoneCountryOptionByPhoneNumber')}
|
||||||
${extractFunction('trySelectSignupPhoneCountryOption')}
|
${extractFunction('trySelectSignupPhoneCountryOption')}
|
||||||
|
${extractFunction('getVisibleSignupPhoneCountryListboxOptions')}
|
||||||
|
${extractFunction('findSignupPhoneCountryListboxOption')}
|
||||||
|
${extractFunction('trySelectSignupPhoneCountryListboxOption')}
|
||||||
${extractFunction('ensureSignupPhoneCountrySelected')}
|
${extractFunction('ensureSignupPhoneCountrySelected')}
|
||||||
${extractFunction('toNationalPhoneNumber')}
|
${extractFunction('toNationalPhoneNumber')}
|
||||||
${extractFunction('toE164PhoneNumber')}
|
${extractFunction('toE164PhoneNumber')}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ function fillInput(el, value) {
|
|||||||
filledValues.push(value);
|
filledValues.push(value);
|
||||||
}
|
}
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
|
async function waitForDocumentLoadComplete() {}
|
||||||
function isStep5Ready() { return false; }
|
function isStep5Ready() { return false; }
|
||||||
function isStep8Ready() { return false; }
|
function isStep8Ready() { return false; }
|
||||||
function isAddPhonePageReady() { return false; }
|
function isAddPhonePageReady() { return false; }
|
||||||
@@ -177,6 +178,7 @@ function is405MethodNotAllowedPage() { return false; }
|
|||||||
async function handle405ResendError() {}
|
async function handle405ResendError() {}
|
||||||
function fillInput() {}
|
function fillInput() {}
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
|
async function waitForDocumentLoadComplete() {}
|
||||||
function isStep5Ready() { return true; }
|
function isStep5Ready() { return true; }
|
||||||
function isStep8Ready() { return false; }
|
function isStep8Ready() { return false; }
|
||||||
function isAddPhonePageReady() { return false; }
|
function isAddPhonePageReady() { return false; }
|
||||||
@@ -336,6 +338,7 @@ function fillInput(el, value) {
|
|||||||
filledValues.push({ target: el === nameInput ? 'name' : (el === ageInput ? 'age' : 'code'), value });
|
filledValues.push({ target: el === nameInput ? 'name' : (el === ageInput ? 'age' : 'code'), value });
|
||||||
}
|
}
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
|
async function waitForDocumentLoadComplete() {}
|
||||||
function isStep5Ready() { return true; }
|
function isStep5Ready() { return true; }
|
||||||
function isStep8Ready() { return false; }
|
function isStep8Ready() { return false; }
|
||||||
function isAddPhonePageReady() { return false; }
|
function isAddPhonePageReady() { return false; }
|
||||||
@@ -531,6 +534,7 @@ function fillInput(el, value) {
|
|||||||
el.value = value;
|
el.value = value;
|
||||||
}
|
}
|
||||||
async function sleep() {}
|
async function sleep() {}
|
||||||
|
async function waitForDocumentLoadComplete() {}
|
||||||
function isStep5Ready() { return false; }
|
function isStep5Ready() { return false; }
|
||||||
function isStep8Ready() { return false; }
|
function isStep8Ready() { return false; }
|
||||||
function isAddPhonePageReady() { return false; }
|
function isAddPhonePageReady() { return false; }
|
||||||
@@ -614,3 +618,102 @@ return {
|
|||||||
assert.equal(snapshot.submitClicked, true);
|
assert.equal(snapshot.submitClicked, true);
|
||||||
assert.equal(snapshot.nameQueryCount >= 3, true);
|
assert.equal(snapshot.nameQueryCount >= 3, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('prepareSignupVerificationFlow waits for complete verification page before reporting ready', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const logs = [];
|
||||||
|
let now = 0;
|
||||||
|
let sleepCalls = 0;
|
||||||
|
let targetChecks = 0;
|
||||||
|
const location = {
|
||||||
|
href: 'https://auth.openai.com/email-verification',
|
||||||
|
pathname: '/email-verification',
|
||||||
|
};
|
||||||
|
const document = {
|
||||||
|
readyState: 'loading',
|
||||||
|
title: '',
|
||||||
|
body: {
|
||||||
|
textContent: 'Enter the verification code we just sent',
|
||||||
|
innerText: 'Enter the verification code we just sent',
|
||||||
|
},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === 'form[action*="email-verification" i]') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Date.now = () => now;
|
||||||
|
function throwIfStopped() {}
|
||||||
|
function log(message, level = 'info') { logs.push({ message, level }); }
|
||||||
|
async function sleep(ms = 0) {
|
||||||
|
sleepCalls += 1;
|
||||||
|
now += ms || 200;
|
||||||
|
if (sleepCalls >= 3) {
|
||||||
|
document.readyState = 'complete';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isVisibleElement() { return true; }
|
||||||
|
function isActionEnabled() { return true; }
|
||||||
|
function getActionText(el) { return el?.textContent || ''; }
|
||||||
|
function getCurrentAuthRetryPageState() { return null; }
|
||||||
|
function isPhoneVerificationPageReady() { return false; }
|
||||||
|
function findResendVerificationCodeTrigger() { return null; }
|
||||||
|
function isEmailVerificationPage() { return true; }
|
||||||
|
function getPageTextSnapshot() { return document.body.textContent; }
|
||||||
|
function getVerificationCodeTarget() {
|
||||||
|
targetChecks += 1;
|
||||||
|
return document.readyState === 'complete'
|
||||||
|
? { type: 'single', element: { value: '' } }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
function is405MethodNotAllowedPage() { return false; }
|
||||||
|
async function recoverCurrentAuthRetryPage() {}
|
||||||
|
function createSignupUserAlreadyExistsError() { return new Error('user already exists'); }
|
||||||
|
function getSignupPasswordInput() { return null; }
|
||||||
|
function getSignupPasswordSubmitButton() { return null; }
|
||||||
|
function isSignupEmailAlreadyExistsPage() { return false; }
|
||||||
|
function isSignupPasswordErrorPage() { return false; }
|
||||||
|
function getSignupPasswordTimeoutErrorPageState() { return null; }
|
||||||
|
function isStep5Ready() { return false; }
|
||||||
|
|
||||||
|
const VERIFICATION_PAGE_PATTERN = /verification code/i;
|
||||||
|
|
||||||
|
${extractFunction('getDocumentReadyStateSnapshot')}
|
||||||
|
${extractFunction('isDocumentLoadComplete')}
|
||||||
|
${extractFunction('waitForDocumentLoadComplete')}
|
||||||
|
${extractFunction('isSignupVerificationPageInteractiveReady')}
|
||||||
|
${extractFunction('isVerificationPageStillVisible')}
|
||||||
|
${extractFunction('isSignupProfilePageUrl')}
|
||||||
|
${extractFunction('isLikelyLoggedInChatgptHomeUrl')}
|
||||||
|
${extractFunction('getStep4PostVerificationState')}
|
||||||
|
${extractFunction('inspectSignupVerificationState')}
|
||||||
|
${extractFunction('waitForVerificationCodeTarget')}
|
||||||
|
${extractFunction('waitForSignupVerificationTransition')}
|
||||||
|
${extractFunction('prepareSignupVerificationFlow')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return prepareSignupVerificationFlow({ prepareLogLabel: '步骤 4 执行' }, 10000);
|
||||||
|
},
|
||||||
|
snapshot() {
|
||||||
|
return { logs, sleepCalls, targetChecks, readyState: document.readyState };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.run();
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
|
||||||
|
assert.equal(result.ready, true);
|
||||||
|
assert.equal(snapshot.readyState, 'complete');
|
||||||
|
assert.equal(snapshot.sleepCalls >= 3, true);
|
||||||
|
assert.equal(snapshot.targetChecks >= 1, true);
|
||||||
|
});
|
||||||
|
|||||||
+1
-1
@@ -326,7 +326,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功
|
|||||||
2. 打开或复用注册页
|
2. 打开或复用注册页
|
||||||
3. 邮箱注册时,如果注册弹窗默认停留在手机号输入模式,会先尝试点击 `继续使用电子邮件地址登录 / Continue using email address` 一类按钮切回邮箱输入模式
|
3. 邮箱注册时,如果注册弹窗默认停留在手机号输入模式,会先尝试点击 `继续使用电子邮件地址登录 / Continue using email address` 一类按钮切回邮箱输入模式
|
||||||
4. 邮箱注册分支:提交邮箱;邮箱输入框识别同时兼容本地化占位与 `aria-label`
|
4. 邮箱注册分支:提交邮箱;邮箱输入框识别同时兼容本地化占位与 `aria-label`
|
||||||
5. 手机号注册分支:先点击官网“免费注册”并切换到手机号注册入口,必要时展开更多选项;确认手机号输入页就绪后,再从接码平台申请号码,填写手机号并提交
|
5. 手机号注册分支:先点击官网“免费注册”并切换到手机号注册入口,必要时展开更多选项;确认手机号输入页就绪后,再从接码平台申请号码;内容脚本会按号码区号和平台国家名切换手机号国家下拉框,必须确认可视下拉按钮的区号已同步后才填写并提交,若仍显示旧国家则停止提交
|
||||||
6. 以当前统一账号标识先写入一条“停止(流程尚未完成)”的记录占位;邮箱账号占位邮箱,phone-only 账号占位手机号
|
6. 以当前统一账号标识先写入一条“停止(流程尚未完成)”的记录占位;邮箱账号占位邮箱,phone-only 账号占位手机号
|
||||||
7. 等待身份提交后的真实落地页
|
7. 等待身份提交后的真实落地页
|
||||||
8. 如果进入密码页,则继续执行 Step 3
|
8. 如果进入密码页,则继续执行 Step 3
|
||||||
|
|||||||
+2
-2
@@ -92,7 +92,7 @@
|
|||||||
- `content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。
|
- `content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。
|
||||||
- `content/plus-checkout.js`:ChatGPT Plus checkout 页面脚本,负责读取 `/api/auth/session` 创建 Plus checkout、选择 PayPal、填写账单姓名、触发 Google 地址推荐、校验结构化地址字段并点击订阅。
|
- `content/plus-checkout.js`:ChatGPT Plus checkout 页面脚本,负责读取 `/api/auth/session` 创建 Plus checkout、选择 PayPal、填写账单姓名、触发 Google 地址推荐、校验结构化地址字段并点击订阅。
|
||||||
- `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
|
- `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
|
||||||
- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击;当前 Step 2 同时支持邮箱注册与手机号注册入口切换、手机号填写与提交;Step 7 登录链路会额外识别手机号输入页、手机号登录入口和 `phone-verification` 页面,避免把手机验证码页误判成邮箱验证码页或普通未知页。
|
- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击;当前 Step 2 同时支持邮箱注册与手机号注册入口切换、手机号国家下拉框同步校验、手机号填写与提交;Step 7 登录链路会额外识别手机号输入页、手机号登录入口和 `phone-verification` 页面,避免把手机验证码页误判成邮箱验证码页或普通未知页。
|
||||||
- `content/sub2api-panel.js`:SUB2API 后台内容脚本,负责获取 OAuth 地址和提交 localhost 回调;平台验证请求会读取 `visibleStep`,普通模式承接步骤 10,Plus 模式承接步骤 13。
|
- `content/sub2api-panel.js`:SUB2API 后台内容脚本,负责获取 OAuth 地址和提交 localhost 回调;平台验证请求会读取 `visibleStep`,普通模式承接步骤 10,Plus 模式承接步骤 13。
|
||||||
- `content/utils.js`:内容脚本公共工具层,负责结构化日志、READY / COMPLETE / ERROR 上报、元素等待、输入与点击;内容脚本日志通过 `{ step, stepKey }` 上报,正文不再承担步骤号解析职责。
|
- `content/utils.js`:内容脚本公共工具层,负责结构化日志、READY / COMPLETE / ERROR 上报、元素等待、输入与点击;内容脚本日志通过 `{ step, stepKey }` 上报,正文不再承担步骤号解析职责。
|
||||||
- `content/vps-panel.js`:CPA 面板内容脚本,负责获取 OAuth 地址、提交回调 URL,并基于精确成功徽标与错误态做平台验证判定;当前支持普通步骤 10 与 Plus 可见步骤 13。
|
- `content/vps-panel.js`:CPA 面板内容脚本,负责获取 OAuth 地址、提交回调 URL,并基于精确成功徽标与错误态做平台验证判定;当前支持普通步骤 10 与 Plus 可见步骤 13。
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
- `tests/sidepanel-mail2925-mode.test.js`:测试侧边栏保留 `2925 provide / receive` 模式行与独立的号池配置行,并验证 sidepanel 只在 provide 模式下把 2925 视为别名邮箱 provider。
|
- `tests/sidepanel-mail2925-mode.test.js`:测试侧边栏保留 `2925 provide / receive` 模式行与独立的号池配置行,并验证 sidepanel 只在 provide 模式下把 2925 视为别名邮箱 provider。
|
||||||
- `tests/sidepanel-paypal-manager.test.js`:测试侧边栏公共表单弹窗脚本与 PayPal manager 的加载顺序,以及 PayPal 账号保存后会立即选中当前账号。
|
- `tests/sidepanel-paypal-manager.test.js`:测试侧边栏公共表单弹窗脚本与 PayPal manager 的加载顺序,以及 PayPal 账号保存后会立即选中当前账号。
|
||||||
- `tests/signup-entry-diagnostics.test.js`:测试注册入口诊断快照输出。
|
- `tests/signup-entry-diagnostics.test.js`:测试注册入口诊断快照输出。
|
||||||
- `tests/signup-step2-email-switch.test.js`:测试 Step 2 在手机号输入模式下切回邮箱输入模式,以及本地化邮箱输入框识别。
|
- `tests/signup-step2-email-switch.test.js`:测试 Step 2 在手机号输入模式下切回邮箱输入模式、本地化邮箱输入框识别,以及手机号注册时国家下拉框可视区号同步。
|
||||||
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
||||||
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
||||||
- `tests/step3-direct-complete.test.js`:测试步骤 3 在提交前先完成上报,并保留延迟提交回调的可执行性验证。
|
- `tests/step3-direct-complete.test.js`:测试步骤 3 在提交前先完成上报,并保留延迟提交回调的可执行性验证。
|
||||||
|
|||||||
Reference in New Issue
Block a user