美文网首页
前端 - 自动化极验解锁 - puppeteer篇

前端 - 自动化极验解锁 - puppeteer篇

作者: 木木木_HYY | 来源:发表于2021-04-30 12:18 被阅读0次

    前因

    某天, 公司一个测试找到我说: '大佬, 极验那东西你这配置过吗?';

    我: '?';

    我一个前端, 你竟然问我配置过极验没有?!

    我: '没有, 没配过, 咋了';

    测试: '我做自动化测试时, 卡到极验了, 你看前端这能不能把他给关掉, 要不然我过不去登录这一步';

    我内心: 'WDFK';

    你这...

    想起N年前在 puppeteer 刚出的时候, 自己使用 puppeteer 写了一个小测试文件, 里面就有自动化解锁极验的功能, 现在找出来, 修修补补应该还能用

    我: '一会儿有时间我给你搞个脚本';

    测试: '行, 那我先去找下 python 怎么调用js文件';

    翻出N年前的 puppeteer 代码, 装包运行... 报错

    没办法, 改吧

    开整

    1. 下载插件
    puppeteer-core
    
    1. 本机需要有 chrome 并能找到安装地址

    2. 创建 xxx.js 文件, 在文件中编写代码

    3. 运行方式

    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();
    
    image.gif

    开始滑动

    思路: 获取滑块按钮, 滑动到图片的阴影缺口处

    1. 获取滑块
    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);
    }
    
    1. 计算滑块需要滑动的距离
    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;
    }
    
    1. 滑动验证
    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();
        }
    }
    

    撒花完结

    看下最终效果图


    image.gif

    完整代码

    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 版本

    相关文章

      网友评论

          本文标题:前端 - 自动化极验解锁 - puppeteer篇

          本文链接:https://www.haomeiwen.com/subject/nbsnrltx.html