diff --git a/content/paypal-flow.js b/content/paypal-flow.js
index 9f34e51..7a505e6 100644
--- a/content/paypal-flow.js
+++ b/content/paypal-flow.js
@@ -227,6 +227,18 @@ function getPayPalLoginPhase(emailInput, passwordInput) {
return '';
}
+function refillPayPalEmailInput(emailInput, email) {
+ if (!emailInput) return;
+ if (typeof emailInput.focus === 'function') {
+ emailInput.focus();
+ }
+ fillInput(emailInput, '');
+ fillInput(emailInput, email);
+ if (typeof emailInput.blur === 'function') {
+ emailInput.blur();
+ }
+}
+
async function submitPayPalLogin(payload = {}) {
await waitForDocumentComplete();
@@ -241,9 +253,7 @@ async function submitPayPalLogin(payload = {}) {
const emailNextButton = findEmailNextButton();
if (emailInput && emailNextButton && isEnabledControl(emailNextButton) && (!passwordInput || !findPasswordLoginButton())) {
- if (normalizeText(emailInput.value || '') !== email) {
- fillInput(emailInput, email);
- }
+ refillPayPalEmailInput(emailInput, email);
simulateClick(emailNextButton);
return {
submitted: false,
@@ -253,9 +263,7 @@ async function submitPayPalLogin(payload = {}) {
}
if (!passwordInput && emailInput && email) {
- if (normalizeText(emailInput.value || '') !== email) {
- fillInput(emailInput, email);
- }
+ refillPayPalEmailInput(emailInput, email);
const nextButton = await waitUntil(() => {
const button = findEmailNextButton() || findLoginNextButton();
return button && isEnabledControl(button) ? button : null;
@@ -272,8 +280,8 @@ async function submitPayPalLogin(payload = {}) {
};
} else if (!passwordInput && emailInput && !email) {
throw new Error('PayPal 账号为空,请先在侧边栏配置。');
- } else if (emailInput && email && normalizeText(emailInput.value || '') !== email) {
- fillInput(emailInput, email);
+ } else if (emailInput && email) {
+ refillPayPalEmailInput(emailInput, email);
}
passwordInput = passwordInput || await waitUntil(() => findPasswordInput(), {
diff --git a/docs/使用教程.md b/docs/使用教程.md
index 0512821..f9721a6 100644
--- a/docs/使用教程.md
+++ b/docs/使用教程.md
@@ -1,6 +1,6 @@
# Codex 注册扩展相关项目、更新、邮箱、PayPal 与 Clash Verge 配置教程
-本教程用于说明相关项目地址、扩展更新方式、`Cloudflare Temp Email`、`iCloud 隐私邮箱` 与 `QQ 邮箱` 的使用方法、`PayPal` 注册绑卡流程、节点检测与纯净度检查,以及 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` 配置方法。
+本教程用于说明相关项目地址、扩展更新方式、`Cloudflare Temp Email`、`iCloud 隐私邮箱` 与 `QQ 邮箱` 的使用方法、`PayPal` 注册绑卡流程、扩展内置动态 IP 代理、节点检测与纯净度检查,以及 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` 配置方法。
## 适用场景
@@ -11,6 +11,7 @@
- 需要临时切换 `QQ 邮箱` 地址继续使用
- 需要使用 `网易邮箱`、`网易邮箱大师` 注册多个邮箱或替身邮箱
- 需要注册并使用 `PayPal` 个人账户
+- 需要在扩展内使用 `711Proxy` 动态 IP,并在自动流程中按轮次切换出口
- 需要检查当前节点的出口 IP、纯净度、泄露情况和访问速度
- 需要在 `Clash Verge` 中启用 `🔁 非港轮询`
@@ -26,6 +27,7 @@
- 手机端已安装 `网易邮箱` 或 `网易邮箱大师`
- 一个可正常接收短信的手机号
- 一张可在线支付的借记卡或信用卡
+- 如需使用扩展内置动态 IP,需准备 `711Proxy` 的 Host、Port、代理账号和代理密码
- 如需部署 `cpa`,部署环境必须可以访问 `OpenAI`
- 已安装 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev),并已导入可用订阅
@@ -426,7 +428,113 @@
- **PayPal 登录后页面无法继续跳转**
稍等片刻让页面加载完毕。如果长时间未响应,检查浏览器是否有弹窗被隐藏,或尝试刷新页面。
-### 第九部分:节点检测与纯净度检查网站
+### 第九部分:扩展内置动态 IP 代理使用教程
+
+本部分说明扩展侧边栏里的 `IP代理` 功能。
+它和 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 这类系统代理不一样:扩展会通过浏览器代理 API 给当前浏览器设置 PAC 代理,并自动回填代理鉴权。当前首版只开放 `711Proxy` 的账号密码模式,`API 拉取` 和多账号列表入口暂未开放。
+
+#### 一、什么时候需要打开 `IP代理`
+
+如果你已经在 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 或其他客户端里稳定使用节点,可以继续使用外部代理。
+如果你希望在扩展里直接控制动态 IP、检查当前出口、按成功轮次自动切换,或者不想依赖系统全局代理,就使用这里的 `IP代理`。
+
+开启 `IP代理` 后,扩展会接管浏览器代理。
+关闭 `IP代理` 后,浏览器会恢复默认代理或外部全局代理。
+
+#### 二、注册或登录 `711Proxy`
+
+在侧边栏打开 `IP代理` 后,`代理服务` 当前固定为 `711Proxy(首版)`。
+点击旁边的 `注册` 按钮,按页面提示完成注册或登录。
+
+在网站中完成注册、登录和套餐准备后,进入代理信息页面,找到可用于账号密码代理的 `Host`、`Port`、`Username` 和 `Password`。
+
+#### 三、填写固定账号代理
+
+1. 在扩展侧边栏打开 `IP代理` 开关。
+2. `代理模式` 保持 `账号密码`。
+3. 填写 `代理 Host`,例如 `global.rotgb.711proxy.com`。
+4. 填写 `代理 Port`,例如 `10000`。
+5. `代理协议` 一般保持 `HTTP`,除非你的代理信息明确要求 `HTTPS`、`SOCKS5` 或 `SOCKS4`。
+6. 填写 `代理账号` 和 `代理密码`。
+7. 按需填写 `地区参数`,例如 `US`、`DE`、`HK`。
+8. 按需填写 `会话(session)` 和 `时长(life)`。
+
+`地区参数` 只接受两个字母的国家或地区代码。
+`会话(session)` 会被追加或写回到用户名里的 `session-xxxx`。
+`时长(life)` 会被追加或写回到用户名里的 `sessTime-xx`,711Proxy 的有效范围按 `5-180` 分钟使用更稳。
+
+如果你的 `711Proxy` 用户名里已经带了 `region-US`、`session-xxxx` 或 `sessTime-30`,扩展会优先识别用户名里的值。
+如果你在表单里修改地区、session 或时长,扩展会把它们同步回最终生效的代理用户名。
+
+#### 四、同步、下一条、Change 和检测出口
+
+填写或修改代理信息后,不要只保存后就直接开跑。建议按下面顺序操作:
+
+1. 点击 `同步`
+ 扩展会保存当前配置,生成代理规则,应用到浏览器,并自动做一次出口检测。
+ 这是修改 Host、Port、账号、密码、地区、session 或时长后最常用的按钮。
+
+2. 查看 `代理状态`
+ 状态卡会显示当前代理、当前出口 IP、出口地区和诊断详情。
+ 如果显示 `当前代理` 和 `当前出口`,说明扩展已经能看到代理出口。
+
+3. 点击 `检测出口`
+ 它只重新检测当前出口,不主动更换节点。
+ 如果你怀疑检测结果没刷新,或者刚刚切换过外部网络,可以点它复测。
+
+4. 点击 `检查IP`
+ 它会打开 `https://ipinfo.io/what-is-my-ip`,用于人工确认网页看到的公网 IP。
+
+5. 点击 `下一条`
+ 当前首版主要是固定账号模式。如果只有一条可用代理,`下一条` 会重绑当前节点并复测,不保证一定换出口。
+ 如果后续开放多账号列表或 API 池,`下一条` 才会真正切到池里的下一条。
+
+6. 点击 `Change`
+ `Change` 只支持 `711Proxy + 账号密码模式 + 用户名包含 session` 的场景。
+ 它会保持当前 session,强制重绑代理链路并刷新出口,适合想换出口但不想改整条账号配置时使用。
+
+#### 五、任务切换阈值
+
+`任务切换阈值` 表示自动流程成功多少轮后尝试自动切换一次代理。
+默认是 `20` 轮,可填写 `1-500`。
+
+需要注意:
+
+- 当前只有一条固定账号时,即使命中阈值,也会跳过自动切换。
+- 如果后续有多条可切换代理,命中阈值后会自动执行 `下一条`。
+- 这个阈值只影响自动流程成功后的切换节奏,不会替代你手动点击 `同步` 或 `检测出口`。
+
+#### 六、状态卡怎么看
+
+常见状态含义如下:
+
+- `未开启`:扩展没有接管浏览器代理,继续使用默认网络或外部全局代理。
+- `当前代理:host:port;当前出口:x.x.x.x`:代理已经应用,并检测到出口 IP。
+- `已启用,但账号模式没有可用代理`:Host/Port 为空或格式不完整,需要先补齐代理信息。
+- `连通性失败`:代理应用了,但没有检测到可用出口,优先检查 Host、Port、协议、账号和密码。
+- `地区校验未通过`:检测到的出口地区和你填写的地区参数不一致。711Proxy 场景下扩展会保留接管并给出警告,你需要决定是否继续使用这个出口。
+- `当前链路未收到 407 代理鉴权挑战`:代理服务没有按浏览器扩展代理 API 预期返回标准鉴权挑战,可能导致账号密码无法自动回填。可以尝试 `Change`、关闭再开启 `IP代理`、换 session,或者更换代理节点。
+
+如果代理不可用,扩展会启用目标站点阻断保护,避免在代理失效时继续直连访问目标站点。修好代理后重新点击 `同步` 或 `检测出口` 即可恢复。
+
+#### 七、推荐使用顺序
+
+首次配置建议按这个顺序:
+
+1. 点击 `注册` 打开 `711Proxy`,准备代理账号信息。
+2. 打开扩展侧边栏的 `IP代理`。
+3. 填写 Host、Port、协议、账号、密码。
+4. 填写地区参数,例如 `US`。
+5. 填写 session 和时长,例如 session 为随机前缀、时长为 `30`。
+6. 点击 `同步`。
+7. 确认 `代理状态` 里有出口 IP。
+8. 点击 `检查IP`,人工确认网页检测结果。
+9. 再继续执行注册、登录或自动流程。
+
+如果后续要换出口,优先尝试 `Change`。
+如果 `Change` 不可用,再调整 session 或代理账号后点击 `同步`。
+
+### 第十部分:节点检测与纯净度检查网站
本部分用于检查当前代理节点的出口 IP、归属地、风险标签、泄露情况和网站访问速度。
建议每次切换节点后都重新打开这些网站检查一次。
@@ -460,7 +568,7 @@
接着打开 [IPPure](https://ippure.com/) 检查纯净度、黑名单、代理识别和泄露情况。
最后用 [TCPTest 网站测速](https://www.tcptest.cn/?cckey=e62f31db) 测试目标网站的访问速度和连通性。
-### 第十部分:配置 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询`
+### 第十一部分:配置 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询`
#### 第一步:添加扩展脚本
@@ -569,7 +677,7 @@ function main(config, profileName) {
5. 确认左侧 `设置` 中的 `系统代理`(`System Proxy`)已经开启。

-### 第十一部分:订阅节点与自建推荐
+### 第十二部分:订阅节点与自建推荐
如果你还没有订阅节点或想要寻找稳定、便宜的科学上网方式,可以参考以下三种方案获取。
@@ -661,6 +769,23 @@ function main(config, profileName) {
请先确认脚本已经完整粘贴并保存,然后回到 `代理` 页面重新查看。若仍未出现,请继续确认当前订阅可用、`代理模式` 为 `规则模式`,并且 `系统代理` 已开启。
+### 扩展 `IP代理` 开启后,为什么状态还是没有出口 IP?
+
+先确认 Host、Port、协议、代理账号和代理密码都已填写正确。
+修改这些字段后,需要点击 `同步` 才会重新应用代理并检测出口。
+如果诊断提示没有收到 `407` 代理鉴权挑战,可以尝试点击 `Change`、关闭再开启 `IP代理`、更换 session,或者换一条代理节点。
+
+### `Change` 按钮为什么点不了?
+
+`Change` 只在 `711Proxy` 的账号密码模式下可用,并且当前生效用户名必须包含 `session-xxxx` 这类 session 参数。
+如果没有 session,请先在 `会话(session)` 中填写一个随机前缀,再点击 `同步`。
+
+### `IP代理` 和 Clash Verge 应该同时开吗?
+
+通常二选一即可。
+如果开启扩展内置 `IP代理`,浏览器会优先使用扩展设置的 PAC 代理;如果关闭它,浏览器会回到默认网络或外部全局代理。
+排查问题时建议先只保留一种代理方式,确认出口 IP 稳定后再继续流程。
+
### 网站测速结果好,是否代表节点纯净?
不代表。
@@ -688,6 +813,9 @@ function main(config, profileName) {
- 中国大陆居民注册 `PayPal` 个人账户时,姓名请按身份证上的中文姓名填写,不要使用拼音。
- `PayPal` 官方说明,绑定新卡时可能会向发卡行发送最高 `1 USD` 或等值货币的临时授权验证,所以完全没有余额的卡也可能因为授权失败而无法绑定。
- 为了控制风险,更稳妥的做法是使用单独管理、余额较低的借记卡,并在绑卡后及时检查 `钱包` 页面和右上角通知。
+- 使用扩展内置 `IP代理` 时,修改 Host、Port、账号、密码、地区、session 或时长后,请点击 `同步` 让新配置真正生效。
+- `711Proxy` 的 `Change` 需要用户名中包含 session;没有 session 时请先填写 `会话(session)` 并同步。
+- 扩展 `IP代理` 报连通性失败时,不要直接继续自动流程,先用 `检测出口` 和 `检查IP` 确认网页看到的出口 IP。
- 检查节点时,先确认出口 IP,再看风险标签和泄露检测,最后再看网站测速。
- [TCPTest 网站测速](https://www.tcptest.cn/?cckey=e62f31db) 主要用于判断访问速度和连通性,不要单独用它判断节点纯净度。
- [IPPure](https://ippure.com/) 如果提示 `WebRTC` 或 `DNS` 泄露,请优先检查浏览器和代理客户端的相关设置。
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index 7db0a30..40e9a1a 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -219,7 +219,7 @@
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 69e94f0..08bdbf5 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -748,7 +748,7 @@ const IP_PROXY_SERVICE_LOGIN_CONFIGS = {
'711proxy': {
label: '711Proxy',
url: 'https://www.711proxy.com/signup?code=AD2497',
- buttonLabel: '登录',
+ buttonLabel: '注册',
},
};
@@ -3844,9 +3844,10 @@ function updateIpProxyServiceLoginButtonState(options = {}) {
? Boolean(options.enabled)
: Boolean(getSelectedIpProxyEnabled());
btnIpProxyServiceLogin.disabled = !enabled || !loginUrl;
- btnIpProxyServiceLogin.textContent = loginConfig?.buttonLabel || '登录';
+ const buttonLabel = loginConfig?.buttonLabel || '登录';
+ btnIpProxyServiceLogin.textContent = buttonLabel;
btnIpProxyServiceLogin.title = loginUrl
- ? `打开 ${loginConfig?.label || service} 登录页`
+ ? `打开 ${loginConfig?.label || service} ${buttonLabel}页`
: '当前代理服务没有可跳转的登录页';
}
diff --git a/tests/paypal-flow-content.test.js b/tests/paypal-flow-content.test.js
index d2f0ffd..f5995f1 100644
--- a/tests/paypal-flow-content.test.js
+++ b/tests/paypal-flow-content.test.js
@@ -145,6 +145,57 @@ return {
`)(document, window);
}
+function createSubmitApi(overrides = {}) {
+ const bindings = {
+ waitForDocumentComplete: async () => {},
+ normalizeText: (text = '') => String(text || '').replace(/\s+/g, ' ').trim(),
+ findPasswordInput: () => null,
+ findEmailInput: () => null,
+ findEmailNextButton: () => null,
+ isEnabledControl: () => true,
+ findPasswordLoginButton: () => null,
+ fillInput: () => {},
+ simulateClick: () => {},
+ waitUntil: async (predicate) => predicate(),
+ findLoginNextButton: () => null,
+ sleep: async () => {},
+ ...overrides,
+ };
+
+ return new Function(
+ 'waitForDocumentComplete',
+ 'normalizeText',
+ 'findPasswordInput',
+ 'findEmailInput',
+ 'findEmailNextButton',
+ 'isEnabledControl',
+ 'findPasswordLoginButton',
+ 'fillInput',
+ 'simulateClick',
+ 'waitUntil',
+ 'findLoginNextButton',
+ 'sleep',
+ `
+${extractFunction('refillPayPalEmailInput')}
+${extractFunction('submitPayPalLogin')}
+return { refillPayPalEmailInput, submitPayPalLogin };
+`
+ )(
+ bindings.waitForDocumentComplete,
+ bindings.normalizeText,
+ bindings.findPasswordInput,
+ bindings.findEmailInput,
+ bindings.findEmailNextButton,
+ bindings.isEnabledControl,
+ bindings.findPasswordLoginButton,
+ bindings.fillInput,
+ bindings.simulateClick,
+ bindings.waitUntil,
+ bindings.findLoginNextButton,
+ bindings.sleep
+ );
+}
+
test('PayPal email page ignores hidden pre-rendered password input', () => {
const hiddenPanel = createElement({ attrs: { 'aria-hidden': 'true' } });
const emailInput = createElement({
@@ -203,3 +254,57 @@ test('PayPal combined login page still sees visible password input', () => {
assert.equal(api.findPasswordLoginButton(), loginButton);
assert.equal(api.getPayPalLoginPhase(emailInput, passwordInput), 'login_combined');
});
+
+test('PayPal email submit refills a prefilled email before clicking next', async () => {
+ const emailInput = createElement({
+ tag: 'input',
+ type: 'text',
+ id: 'login_email',
+ name: 'login_email',
+ value: 'user@example.com',
+ placeholder: 'Email',
+ });
+ const nextButton = createElement({
+ tag: 'button',
+ id: 'btnNext',
+ text: 'Next',
+ });
+ const fillValues = [];
+ const clicked = [];
+ let focusCount = 0;
+ let blurCount = 0;
+
+ emailInput.focus = () => {
+ focusCount += 1;
+ };
+ emailInput.blur = () => {
+ blurCount += 1;
+ };
+
+ const api = createSubmitApi({
+ findEmailInput: () => emailInput,
+ findEmailNextButton: () => nextButton,
+ fillInput: (element, value) => {
+ fillValues.push(value);
+ element.value = value;
+ },
+ simulateClick: (element) => {
+ clicked.push(element);
+ },
+ });
+
+ const result = await api.submitPayPalLogin({
+ email: 'user@example.com',
+ password: 'secret',
+ });
+
+ assert.deepEqual(fillValues, ['', 'user@example.com']);
+ assert.equal(focusCount, 1);
+ assert.equal(blurCount, 1);
+ assert.deepEqual(clicked, [nextButton]);
+ assert.deepEqual(result, {
+ submitted: false,
+ phase: 'email_submitted',
+ awaiting: 'password_page',
+ });
+});
diff --git a/项目完整链路说明.md b/项目完整链路说明.md
index 77acb92..909a646 100644
--- a/项目完整链路说明.md
+++ b/项目完整链路说明.md
@@ -500,7 +500,7 @@ Plus 模式可见步骤:
1. 第 1~5 步:沿用普通注册入口、邮箱、密码、注册验证码、资料填写链路。
2. 第 6 步 `创建 Plus Checkout`:打开已登录 ChatGPT 页面,通过 `/api/auth/session` 读取 accessToken,再请求 `https://chatgpt.com/backend-api/payments/checkout` 创建 `chatgptplusplan` 的 checkout session,并打开 `https://chatgpt.com/checkout/openai_ie/{checkout_session_id}`。
3. 第 7 步 `填写账单并提交订阅`:选择 PayPal,生成账单全名,从 `data/address-sources.js` 读取同国家 seed query,触发 checkout 内置 Google 地址推荐,选择推荐项并校验地址第 1 行、城市、州/省、邮编等结构化字段,再点击“订阅”。运行时会按 Stripe iframe 拆分执行:付款方式在 `elements-inner-payment` frame,账单地址在 `elements-inner-address` frame,Google 推荐在 `elements-inner-autocompl` frame 时单独点击推荐项。
-4. 第 8 步 `PayPal 登录与授权`:在 PayPal 页面填写 `paypalEmail / paypalPassword`,登录前固定等待 1 秒,关闭可见通行密钥提示,点击“同意并继续”。
+4. 第 8 步 `PayPal 登录与授权`:在 PayPal 页面填写 `paypalEmail / paypalPassword`。当页面处于账号输入阶段时,内容脚本会固定对邮箱输入框执行 `focus -> clear -> fill -> blur -> click next`,即使文本框里已经预填了同一个账号,也会重新触发输入事件,避免 PayPal 因跳过重填而停在邮箱页;登录前固定等待 1 秒,关闭可见通行密钥提示,点击“同意并继续”。
5. 第 9 步 `订阅回跳确认`:等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒。
6. 第 10 步:复用原 Step 7 OAuth 登录执行器,但状态和日志按 Plus 可见第 10 步记录。
7. 第 11 步:复用原 Step 8 登录验证码执行器,但状态和日志按 Plus 可见第 11 步记录。
diff --git a/项目文件结构说明.md b/项目文件结构说明.md
index db94a0d..15be872 100644
--- a/项目文件结构说明.md
+++ b/项目文件结构说明.md
@@ -86,7 +86,7 @@
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
- `content/mail-163.js`:163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。
-- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。
+- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。
- `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/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
@@ -202,6 +202,8 @@
- `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。
- `tests/phone-auth-country-match.test.js`:测试认证页手机号国家选择在页面本地化时,仍能用 HeroSMS 的英文国家名匹配对应国家选项。
- `tests/phone-verification-flow.test.js`:测试手机号验证共享流程对 HeroSMS 的取号、复用、重发、换号与 Step 7 重开错误分支。
+- `tests/paypal-approve-detection.test.js`:测试 Plus 第 8 步后台执行器对 PayPal 标签页发现、分离式账号/密码页识别、联合登录页识别,以及登录后直接离开 PayPal 页的分支判断。
+- `tests/paypal-flow-content.test.js`:测试 PayPal 内容脚本对可见邮箱/密码输入框的识别,并覆盖邮箱页即使已预填相同账号也会先清空再重填后继续下一步。
- `tests/plus-checkout-billing-tab-resolution.test.js`:测试 Plus 第 7 步可直接接管当前 checkout 标签页,并把 PayPal、账单地址、Google 地址推荐和订阅按钮操作路由到对应 Stripe iframe。
- `tests/cloudflare-temp-email-provider.test.js`:测试 Cloudflare Temp Email provider 的轮询与目标邮箱选择逻辑。
- `tests/cloudflare-temp-email-utils.test.js`:测试 Cloudflare Temp Email 工具层的 URL、域名、邮件解析逻辑。