feat: 增强注册流程,处理隐藏的注册入口并优化窗口状态

This commit is contained in:
QLHazyCoder
2026-05-10 19:55:13 +08:00
parent 48d3d5fc12
commit d058676f95
4 changed files with 220 additions and 7 deletions
+17
View File
@@ -35,6 +35,23 @@
return null;
}
if (typeof chrome?.tabs?.get === 'function' && typeof chrome?.windows?.update === 'function') {
try {
const tab = await chrome.tabs.get(tabId);
const windowId = Number(tab?.windowId);
if (Number.isInteger(windowId) && windowId >= 0) {
await chrome.windows.update(windowId, { state: 'normal', focused: true }).catch(() => {});
await chrome.windows.update(windowId, {
focused: true,
width: 1200,
height: 900,
}).catch(() => {});
}
} catch {
// Best-effort only. Step 2 still has content-side entry retries.
}
}
if (typeof addLog === 'function') {
await addLog(
`步骤 ${step}:注册页已打开,正在等待页面加载完成并额外稳定 3 秒...`,
+28
View File
@@ -185,6 +185,33 @@
});
}
async function normalizeSignupTabWindowForStep2(tabId) {
if (
!Number.isInteger(tabId)
|| typeof chrome?.tabs?.get !== 'function'
|| typeof chrome?.windows?.update !== 'function'
) {
return;
}
try {
const tab = await chrome.tabs.get(tabId);
const windowId = Number(tab?.windowId);
if (!Number.isInteger(windowId) || windowId < 0) {
return;
}
await chrome.windows.update(windowId, { state: 'normal', focused: true }).catch(() => {});
await chrome.windows.update(windowId, {
focused: true,
width: 1200,
height: 900,
}).catch(() => {});
} catch {
// Best-effort only: content-side retries still handle layout recovery.
}
}
async function ensureSignupPhoneEntryReady(tabId) {
if (!Number.isInteger(tabId)) {
throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。');
@@ -230,6 +257,7 @@
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
} else {
await chrome.tabs.update(signupTabId, { active: true });
await normalizeSignupTabWindowForStep2(signupTabId);
await waitForStep2SignupTabToSettle(
signupTabId,
'步骤 2:已切换到注册页标签,正在等待页面加载完成并额外稳定 3 秒...'
+42 -7
View File
@@ -602,12 +602,38 @@ function getSignupEmailContinueButton({ allowDisabled = false } = {}) {
}) || null;
}
function findSignupEntryTrigger() {
function findSignupEntryTrigger(options = {}) {
const { allowHiddenFallback = true } = options || {};
const candidates = document.querySelectorAll('a, button, [role="button"], [role="link"]');
return Array.from(candidates).find((el) => {
if (!isVisibleElement(el) || !isActionEnabled(el)) return false;
return SIGNUP_ENTRY_TRIGGER_PATTERN.test(getActionText(el));
}) || null;
let hiddenSignupTrigger = null;
for (const el of Array.from(candidates)) {
if (!isActionEnabled(el)) continue;
if (!SIGNUP_ENTRY_TRIGGER_PATTERN.test(getActionText(el))) continue;
if (isVisibleElement(el)) {
return el;
}
if (!hiddenSignupTrigger) {
hiddenSignupTrigger = el;
}
}
if (!allowHiddenFallback || !hiddenSignupTrigger) {
return null;
}
const view = typeof window !== 'undefined' ? window : globalThis;
const collapsedViewport = Boolean(
Math.round(Number(view?.innerWidth) || 0) < 240
|| Math.round(Number(view?.innerHeight) || 0) < 160
|| Math.round(Number(view?.outerWidth) || 0) < 320
|| Math.round(Number(view?.outerHeight) || 0) < 180
);
const pageText = typeof getPageTextSnapshot === 'function'
? getPageTextSnapshot()
: '';
const looksLikeLoggedOutHome = /登录|登入|log\s*in|sign\s*in/i.test(pageText);
return collapsedViewport || looksLikeLoggedOutHome ? hiddenSignupTrigger : null;
}
function getSignupPasswordDisplayedEmail() {
@@ -719,6 +745,7 @@ function getSignupEntryStateSummary(snapshot = inspectSignupEntryState()) {
summary.signupTrigger = {
tag: (snapshot.signupTrigger.tagName || '').toLowerCase(),
text: getActionText(snapshot.signupTrigger).slice(0, 80),
visible: isVisibleElement(snapshot.signupTrigger),
};
}
@@ -1085,9 +1112,13 @@ async function waitForSignupEntryState(options = {}) {
: `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`);
await sleep(3000);
throwIfStopped();
const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger;
if (!isVisibleElement(clickTarget)) {
log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn');
}
await humanPause(350, 900);
await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
simulateClick(snapshot.signupTrigger);
simulateClick(clickTarget);
});
}
}
@@ -2380,9 +2411,13 @@ async function waitForSignupPhoneEntryState(options = {}) {
: `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`);
await sleep(3000);
throwIfStopped();
const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger;
if (!isVisibleElement(clickTarget)) {
log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn');
}
await humanPause(350, 900);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
simulateClick(snapshot.signupTrigger);
simulateClick(clickTarget);
});
}
await sleep(250);
+133
View File
@@ -561,6 +561,139 @@ return {
assert.equal(api.getLogs().some(({ message, level }) => level === 'warn' && /已完成 5 次重试/.test(message)), true);
});
test('waitForSignupEntryState treats hidden signup action on collapsed logged-out home as retryable entry', async () => {
const api = new Function(`
const logs = [];
const clicks = [];
let now = 0;
const signupButton = {
textContent: '免费注册',
value: '',
disabled: false,
getAttribute(name) {
if (name === 'type') return 'button';
return '';
},
getBoundingClientRect() {
return { width: 0, height: 0 };
},
};
const document = {
querySelector() {
return null;
},
querySelectorAll(selector) {
if (selector === 'a, button, [role="button"], [role="link"]') {
return [signupButton];
}
return [];
},
};
const window = {
innerWidth: 0,
innerHeight: 0,
outerWidth: 159,
outerHeight: 27,
};
const location = {
href: 'https://chatgpt.com/',
};
const Date = {
now() {
return now;
},
};
${extractConst('SIGNUP_ENTRY_TRIGGER_PATTERN')}
${extractConst('SIGNUP_EMAIL_INPUT_SELECTOR')}
${extractConst('SIGNUP_PHONE_INPUT_SELECTOR')}
function isVisibleElement(el) {
const rect = el?.getBoundingClientRect?.() || { width: 0, height: 0 };
return rect.width > 0 && rect.height > 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 getPageTextSnapshot() {
return '跳至内容 ChatGPT 登录 我们先从哪里开始呢?';
}
function getSignupPasswordInput() {
return null;
}
function isSignupPasswordPage() {
return false;
}
function getSignupPasswordSubmitButton() {
return null;
}
function getSignupPasswordDisplayedEmail() {
return '';
}
function throwIfStopped() {}
function log(message, level = 'info') {
logs.push({ message, level });
}
async function humanPause() {}
function simulateClick(target) {
clicks.push(getActionText(target));
}
async function sleep(ms) {
now += ms;
}
${extractFunction('getSignupEmailInput')}
${extractFunction('getSignupPhoneInput')}
${extractFunction('getSignupEmailContinueButton')}
${extractFunction('findSignupEntryTrigger')}
${extractFunction('inspectSignupEntryState')}
${extractFunction('waitForSignupEntryState')}
return {
async run() {
return waitForSignupEntryState({ timeout: 30000, autoOpenEntry: true, step: 2 });
},
getClicks() {
return clicks.slice();
},
getLogs() {
return logs.slice();
},
};
`)();
const snapshot = await api.run();
assert.equal(snapshot.state, 'entry_home');
assert.equal(api.getClicks().length, 6);
assert.equal(api.getLogs().some(({ message }) => message.includes('注册入口仍处于不可见状态')), true);
assert.equal(api.getLogs().some(({ message, level }) => level === 'warn' && /已完成 5 次重试/.test(message)), true);
});
test('ensureSignupPhoneEntryReady opens free signup before switching to the phone entry', async () => {
const api = new Function(`
const logs = [];