feat: 增强注册流程,处理隐藏的注册入口并优化窗口状态
This commit is contained in:
@@ -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 秒...`,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user