前因
某天, 公司一个测试找到我说: ‘大佬, 极验那东西你这配置过吗?’;
我: ‘?’;
我一个前端, 你竟然问我配置过极验没有?!
我: ‘没有, 没配过, 咋了’;
测试: ‘我做自动化测试时, 卡到极验了, 你看前端这能不能把他给关掉, 要不然我过不去登录这一步’;
我内心: ‘WDFK’;
你这…
想起N年前在 puppeteer
刚出的时候, 自己使用 puppeteer
写了一个小测试文件, 里面就有自动化解锁极验的功能, 现在找出来, 修修补补应该还能用
我: ‘一会儿有时间我给你搞个脚本’;
测试: ‘行, 那我先去找下 python
怎么调用js文件’;
翻出N年前的 puppeteer
代码, 装包运行… 报错
没办法, 改吧
开整
- 下载插件
puppeteer-core
复制代码
-
本机需要有 chrome 并能找到安装地址
-
创建 xxx.js 文件, 在文件中编写代码
-
运行方式
node xxx.js
复制代码
调起测试窗口
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch({
headless: false,
// 本机的 chrome 浏览器安装地址
executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
defaultViewport: {
width: 1920,
height: 1080,
},
timeout: 3000,
// 操作延迟
slowMo: 10,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-features=site-per-process', '--window-size=1400,900'],
});
// 新开标签页
const page = await browser.newPage();
// 打开指定网址
await page.goto('https://www.geetest.com/demo/slide-popup.html');
// 等待极验出现
await page.waitForSelector(".geetest_holder")
// 点击显示极验
await page.click(".geetest_btn")
await page.waitForTimeout(800);
// 开始滑动
await slider();
复制代码
开始滑动
思路: 获取滑块按钮, 滑动到图片的阴影缺口处
- 获取滑块
async function slider() {
// 等待canvas完成 并完成0.5s的移动动画 (验证出错也可为等待时间)
await page.waitForSelector('.geetest_ready', {
timeout: 0,
});
await page.waitForTimeout(500);
// 获取canvas的左上角X坐标作为滑动的基坐标
await page.waitForSelector('.geetest_canvas_bg');
let canvasCoordinate = await page.$('.geetest_canvas_bg');
let canvasBox = await canvasCoordinate.boundingBox();
let canvasX = canvasBox.x;
// 等待滑动按钮出现获取Y坐标
await page.waitForSelector('.geetest_slider_button');
let button = await page.$('.geetest_slider_button');
let box = await button.boundingBox();
let mouseY = Math.floor(box.y + box.height / 2);
}
复制代码
- 计算滑块需要滑动的距离
async function slider() {
...
// 计算位移
let moveDistance = await compare();
}
// 计算位移
// 逻辑: 通过比对在 极验dom中 获取的两个图片的像素差, 大致判断处阴影缺口处位置
async function compare() {
// 获取canvas
let moveDistance = await page.evaluate(() => {
let fullbgs = document.querySelector('.geetest_canvas_fullbg');
let bgs = document.querySelector('.geetest_canvas_bg');
let bgsCtx = bgs.getContext('2d');
let fullbgsCtx = fullbgs.getContext('2d');
let canvasWidth = bgsCtx.canvas.width;
let canvasHeight = bgsCtx.canvas.height;
// 最大像素差(阀值)
// let pixelsDifference = 100;
let pixelsDifference = 70;
// 第一个超过阀值的x坐标 最后一个超过阀值的x坐标
let firstX, lastX;
// 对比像素
for (let i = 1, k = 1; i < canvasWidth; i++) {
if (!firstX) {
// 找到第一个超过阀值的X坐标后 Y轴停止循环
for (let j = 1; j < canvasHeight; j++) {
// 获取像素数据
let bgsPx = bgsCtx.getImageData(i, j, 1, 1).data;
let fullbgsPx = fullbgsCtx.getImageData(i, j, 1, 1).data;
// 计算像素差 并判断是否超过阀值
let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
firstX = i;
// 记录Y坐标
k = j;
}
}
} else {
// 顺着X轴查找最后一个超过阀值的X坐标
// K是第一个超过阀值的Y坐标
// (会多一点循环时间 但是不用手动测量阴影块宽度)
let bgsPx = bgsCtx.getImageData(i, k, 1, 1).data;
let fullbgsPx = fullbgsCtx.getImageData(i, k, 1, 1).data;
let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
lastX = i;
}
}
}
// 滑动到阴影块中心的距离
return firstX + (lastX - firstX) / 2;
});
return moveDistance;
}
复制代码
- 滑动验证
async function slider() {
...
// 滑动验证
await page.hover('.geetest_slider_button');
// 简单模拟人工滑动速度和状态
await page.mouse.down();
await page.mouse.move(canvasX + moveDistance / 3, mouseY, { steps: 15 });
await page.waitForTimeout(1 * 30);
await page.mouse.move(canvasX + moveDistance / 2, mouseY, { steps: 20 });
await page.waitForTimeout(2 * 50);
await page.mouse.move(canvasX + moveDistance + 10, mouseY, { steps: 18 });
await page.waitForTimeout(3 * 80);
await page.mouse.move(canvasX + moveDistance / 1, mouseY, { steps: 60 });
await page.waitForTimeout(4 * 30);
await page.mouse.up();
await page.waitForSelector('.geetest_success_radar_tip_content');
// 是否验证成功
let state = await page.evaluate(() => {
return document.querySelector('.geetest_success_radar_tip_content').innerText;
});
if (state !== '验证成功') {
return slider();
}
}
复制代码
撒花完结
看下最终效果图
完整代码
const puppeteer = require('puppeteer-core');
(async () => {
const browser = await puppeteer.launch({
headless: false,
executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
defaultViewport: {
width: 1920,
height: 1080,
},
timeout: 3000,
slowMo: 10,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-features=site-per-process', '--window-size=1400,900'],
});
// 新开标签页
const page = await browser.newPage();
// 打开指定网址
await page.goto('https://www.geetest.com/demo/slide-popup.html');
// 等待极验出现
await page.waitForSelector(".geetest_holder")
// 点击显示极验
await page.click(".geetest_btn")
await page.waitForTimeout(800);
// 开始滑动
await slider();
async function slider() {
// 等待canvas完成 并完成0.5s的移动动画 (验证出错也可为等待时间)
await page.waitForSelector('.geetest_ready', {
timeout: 0,
});
await page.waitForTimeout(500);
// 获取canvas的左上角X坐标作为滑动的基坐标
await page.waitForSelector('.geetest_canvas_bg');
let canvasCoordinate = await page.$('.geetest_canvas_bg');
let canvasBox = await canvasCoordinate.boundingBox();
let canvasX = canvasBox.x;
// 等待滑动按钮出现获取Y坐标
await page.waitForSelector('.geetest_slider_button');
let button = await page.$('.geetest_slider_button');
let box = await button.boundingBox();
let mouseY = Math.floor(box.y + box.height / 2);
// 计算位移
let moveDistance = await compare();
// 滑动验证
await page.hover('.geetest_slider_button');
await page.mouse.down();
await page.mouse.move(canvasX + moveDistance / 3, mouseY, { steps: 15 });
await page.waitForTimeout(1 * 30);
await page.mouse.move(canvasX + moveDistance / 2, mouseY, { steps: 20 });
await page.waitForTimeout(2 * 50);
await page.mouse.move(canvasX + moveDistance + 10, mouseY, { steps: 18 });
await page.waitForTimeout(3 * 80);
await page.mouse.move(canvasX + moveDistance / 1, mouseY, { steps: 60 });
await page.waitForTimeout(4 * 30);
await page.mouse.up();
await page.waitForSelector('.geetest_success_radar_tip_content');
// 是否验证成功
let state = await page.evaluate(() => {
return document.querySelector('.geetest_success_radar_tip_content').innerText;
});
if (state !== '验证成功') {
return slider();
}
}
// 计算位移
async function compare() {
// 获取canvas
let moveDistance = await page.evaluate(() => {
let fullbgs = document.querySelector('.geetest_canvas_fullbg');
let bgs = document.querySelector('.geetest_canvas_bg');
let bgsCtx = bgs.getContext('2d');
let fullbgsCtx = fullbgs.getContext('2d');
let canvasWidth = bgsCtx.canvas.width;
let canvasHeight = bgsCtx.canvas.height;
// 最大像素差(阀值)
// let pixelsDifference = 100;
let pixelsDifference = 70;
// 第一个超过阀值的x坐标 最后一个超过阀值的x坐标
let firstX, lastX;
// 对比像素
for (let i = 1, k = 1; i < canvasWidth; i++) {
if (!firstX) {
// 找到第一个超过阀值的X坐标后 Y轴停止循环
for (let j = 1; j < canvasHeight; j++) {
// 获取像素数据
let bgsPx = bgsCtx.getImageData(i, j, 1, 1).data;
let fullbgsPx = fullbgsCtx.getImageData(i, j, 1, 1).data;
// 计算像素差 并判断是否超过阀值
let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
firstX = i;
// 记录Y坐标
k = j;
}
}
} else {
// 顺着X轴查找最后一个超过阀值的X坐标
// K是第一个超过阀值的Y坐标
// (会多一点循环时间 但是不用手动测量阴影块宽度)
let bgsPx = bgsCtx.getImageData(i, k, 1, 1).data;
let fullbgsPx = fullbgsCtx.getImageData(i, k, 1, 1).data;
let res1 = Math.abs(bgsPx[0] - fullbgsPx[0]);
let res2 = Math.abs(bgsPx[1] - fullbgsPx[1]);
let res3 = Math.abs(bgsPx[2] - fullbgsPx[2]);
if (res1 > pixelsDifference || res2 > pixelsDifference || res3 > pixelsDifference) {
lastX = i;
}
}
}
// 滑动到阴影块中心的距离
return firstX + (lastX - firstX) / 2;
});
return moveDistance;
}
// await browser.close()
})();
复制代码
说明
本地测试用例使用地址为极验官方文档中提供的demo地址, 如有使用问题可联系本人删除…
后果
测试找到的 python
调用 javascript
方式行不通, 没办法, 再给他整理一个 phthon
版本的吧, 自己装得比, 哭着也要装完… 虽然N年没用过 python
了 但是看看文档应该也能写 下次更新 python
版本
作者:yusf
链接:https://juejin.cn/post/6956800708389634079
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。