Files
auto-login/tests/detect-captcha.test.js
T

196 lines
6.1 KiB
JavaScript
Raw Normal View History

2026-09-14 13:45:17 +08:00
// tests/detect-captcha.test.js
// 测试 content.js 中的 detectCaptcha:各类验证码场景识别
// 通过 vm 在沙箱中加载 content.js,注入 mock document
// 运行:node --test tests/detect-captcha.test.js
const { describe, it, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
// 创建一个最小化的 mock document,支持 querySelector / querySelectorAll
// elements 是一个选择器 -> 元素对象的映射;querySelector 返回第一个匹配
function createMockDoc(elements) {
return {
querySelector(selector) {
// 简化:把逗号分隔的选择器拆开,返回第一个命中的
const parts = selector.split(",").map((s) => s.trim());
for (const sel of parts) {
if (elements[sel]) return elements[sel];
}
return null;
},
querySelectorAll(selector) {
const parts = selector.split(",").map((s) => s.trim());
const result = [];
for (const sel of parts) {
if (elements[sel]) result.push(elements[sel]);
}
return result;
},
body: {},
};
}
// 加载 content.js 到 vm 沙箱,返回沙箱上下文
function loadContentJs(mockDoc) {
const code = fs.readFileSync(path.join(__dirname, "..", "content.js"), "utf8");
const sandbox = {
URL: globalThis.URL,
window: { location: { href: "https://example.com/login" } },
document: mockDoc,
chrome: {
2026-09-14 15:38:07 +08:00
runtime: {
onMessage: { addListener() {} },
},
// i18n mock:getMessage 返回 key 自身作为文案(fallback)
i18n: { getMessage: (key) => key || "" },
2026-09-14 13:45:17 +08:00
storage: {
local: { get: async () => ({}), set: async () => {}, remove: async () => {} },
session: { get: async () => ({}), set: async () => {}, remove: async () => {} },
},
},
MutationObserver: class {
observe() {}
},
setTimeout,
clearTimeout,
console,
};
// 让 utils.js 提供的 isDomainMatch / escapeHtml 在沙箱中可用
sandbox.globalThis = sandbox;
vm.createContext(sandbox);
// 先加载 utils.js
const utilsCode = fs.readFileSync(path.join(__dirname, "..", "utils.js"), "utf8");
vm.runInContext(utilsCode, sandbox);
vm.runInContext(code, sandbox);
return sandbox;
}
describe("detectCaptcha", () => {
let sandbox;
beforeEach(() => {
// 默认 mock:无任何验证码元素
const doc = createMockDoc({});
sandbox = loadContentJs(doc);
});
it("无验证码元素时返回 null", () => {
assert.equal(sandbox.detectCaptcha(), null);
});
it("检测到 reCAPTCHA(.g-recaptcha)", () => {
const doc = createMockDoc({
".g-recaptcha": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.ok(r);
assert.equal(r.type, "reCAPTCHA");
2026-09-14 15:38:07 +08:00
// reason 由 i18n 提供,测试环境 fallback 到 key,断言非空字符串
assert.ok(typeof r.reason === "string" && r.reason.length > 0);
2026-09-14 13:45:17 +08:00
});
it("检测到 reCAPTCHA iframe", () => {
const doc = createMockDoc({
"iframe[src*='recaptcha']": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "reCAPTCHA");
});
it("检测到 hCaptcha", () => {
const doc = createMockDoc({
".h-captcha": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "hCaptcha");
});
it("检测到滑块验证码(#slider)", () => {
const doc = createMockDoc({
"#slider": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "slider/captcha");
});
it("检测到图形验证码容器(.captcha)", () => {
const doc = createMockDoc({
".captcha": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "slider/captcha");
});
it("检测到 geetest 极验验证码", () => {
const doc = createMockDoc({
".geetest": { offsetParent: {} },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "slider/captcha");
});
it("检测到验证码输入框(name=captcha)", () => {
const doc = createMockDoc({
"input[name*='captcha' i]": { offsetParent: {}, type: "text" },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "captcha-input");
});
it("检测到 placeholder 含「验证码」的输入框", () => {
const doc = createMockDoc({
"input[placeholder*='验证码' i]": { offsetParent: {}, type: "text" },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "captcha-input");
});
it("hidden 类型的验证码输入框不被识别(忽略隐藏域)", () => {
const doc = createMockDoc({
"input[name*='captcha' i]": { offsetParent: {}, type: "hidden" },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r, null);
});
it("offsetParent 为 null 的滑块元素不可见,不被识别", () => {
// 滑块/输入框分支会检查 offsetParent !== null,不可见元素不触发
const doc = createMockDoc({
"#slider": { offsetParent: null },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r, null);
});
it("reCAPTCHA 优先级高于滑块/输入框", () => {
const doc = createMockDoc({
".g-recaptcha": { offsetParent: {} },
"#slider": { offsetParent: {} },
"input[name*='captcha' i]": { offsetParent: {}, type: "text" },
});
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.equal(r.type, "reCAPTCHA");
});
it("返回对象包含 reason 字段说明原因", () => {
const doc = createMockDoc({ "#captcha": { offsetParent: {} } });
const sb = loadContentJs(doc);
const r = sb.detectCaptcha();
assert.ok(r && typeof r.reason === "string" && r.reason.length > 0);
});
});