Selenuim 解决最简单滑动验证码 java版

新年快乐!

先记录下写这篇随笔的初衷: 同一个项目组的测试组在推广写自动化用例,组长说用selenium写,底下的测试们只能照做喽。一群测试小伙伴们都没学过java,于是一个玩得来的测试同事经常来找我,问我java语法、selenium定位元素等等怎么写。 我也没学过selenium,不过懂点java语法。 他们碰到一个难题,自动化用例都阻塞在登陆页面了,开发、测试环境页面上有用到极验验证码,还好只是最简单的滑动验证码。这下自动化用例登陆都登不进去,全阻塞在那儿了。 用selenium的java版本写出来了,也算有点收获,毕竟靠自己亲手解决了一个难题。

坎坷经历:最开始看到这样一个滑动验证码,思路是验证会发送ajax请求,请求参数携带了一个加密的字符串w, 一开始还想着从geetest.js文件入手,我没有啥JavaScript专业的学习,研究了一天半天就放弃了。毕竟JS编码加混淆对我来说太难了。 后来换了个思路,把这两张图给他另存为到本地,然后通过比较像素点,找到缺口的横坐标。 想法是好的, 但是验证码用canvas画的,我没有用过canvas,但是看到验证码可以右键另存为到本地,于是我又去搜索selenium如何右键另存为图片(要使用autoit,好像有点困难,于是我放弃了),这下又折腾了一两天。 后来旁边的前端小伙伴告诉我canvas可以转为base64编码,于是我就试了一试,所幸我们的环境上是可行的。 回家后又在本地随意找了个网站试了试,可行的所以记录下来。如果没有硬性要求要用selenium,也没有严格的内网环境,完全可以使用python,github上有解决方案,或者使用别人开发好的接口,好像是收费的。我只是兴趣之余给我的测试小伙伴减少工作难题,毕竟人家老是来找我探讨。

0 演示效果图

效果图大约2M,文件有点大,页面加载可能有点慢。

1 滑块验证码思路

1.1 像素点

将有缺口和没有缺口的验证码图片逐点比对,像素点差60以上,我们就认为这个点是缺块内的;找到第一个缺块点的X坐标,滑块移动到这就个位置了。

1.2 移动轨迹

测试的时候发现滑块对上了,但是提示"怪物吃掉了",看接口响应是 forbidden,应该是运动轨迹被识别为自动的,所以禁止登陆。这个时候你完全可以写个自己的运动轨迹。先加速后匀速,或者匀速过去,超一点再倒回来。 方式可以有很多,实现目的就行。

2 JAVA代码

2.1 java实现代码

测试类代码如下:

import org.openqa.selenium.By;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class AutoTests {

    public static void main(String[] args) throws Exception {
        try {
            System.setProperty("webdriver.chrome.driver", "D:\\new_soft\\chromedriver.exe");
            WebDriver driver = new ChromeDriver();
            driver.get("https://www.geetest.com/Register");
            driver.manage().window().maximize();
            driver.findElement(By.xpath("//input[@placeholder=\"手机号码\"]")).sendKeys("11111111111");
            driver.findElement(By.xpath("//div[text()=\"获取验证码\"]")).click();
            //处理滑动验证码
            SlideVerifyBlock.common(driver);
            Thread.sleep(2000);
            driver.quit();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

滑块验证码代码如下:
滑块验证码算出来的像素点距离减去8,是因为滑块距离图片最左边一般都是紧贴着的,我量了一下大约8,所以才减去这么多。moveWay1moveWay2于2021/2/12测试时候是均可通过的。

import org.openqa.selenium.By;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SlideVerifyBlock {

    // 减少计算50
    private static final int OFFSET = 50;

    // 滑块和左边轴都有一定的距离, 误差范围内都可以接受
    private static final int LEFT_GAP = 8;

    //加速度
    private static int a_speed = 150;

    public static void common(WebDriver driver) throws Exception {
        String bgClassName = "geetest_canvas_bg geetest_absolute",
                bgFullClassName = "geetest_canvas_fullbg geetest_fade geetest_absolute";
        String getImgBase64 = "return document.getElementsByClassName(\'%s\')[0].toDataURL('image/png');";
        //等待一下验证码加载出来
        Thread.sleep(1000);
        String bgImg = ((JavascriptExecutor) driver).executeScript(String.format(getImgBase64, bgClassName)).toString();
        bgImg = bgImg.startsWith("data:image/png;base64,") ? bgImg.substring(22) : bgImg;
        String bgFullImg = ((JavascriptExecutor) driver).executeScript(String.format(getImgBase64, bgFullClassName)).toString();
        bgFullImg = bgFullImg.startsWith("data:image/png;base64,") ? bgFullImg.substring(22) : bgFullImg;
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] bgBytes = decoder.decodeBuffer(bgImg);
        byte[] bgFullBytes = decoder.decodeBuffer(bgFullImg);
        generateFile(bgBytes, "C:\\Users\\Administrator\\Desktop\\笔记", "bg.png");
        generateFile(bgFullBytes, "C:\\Users\\Administrator\\Desktop\\笔记", "bgfull.png");
        InputStream inputStream = new ByteArrayInputStream(bgBytes);
        InputStream inputStream2 = new ByteArrayInputStream(bgFullBytes);
        BufferedImage bg = ImageIO.read(inputStream), fullBg = ImageIO.read(inputStream2);
        ;
        int width = bg.getWidth(), height = bg.getHeight();
        int[] tmp1 = new int[3], tmp2 = new int[3];
        int gap = 0;
        outer:
        for (int i = OFFSET; i < width; i++) {
            for (int j = OFFSET; j < height; j++) {
                int rgb1 = bg.getRGB(i, j), rgb2 = fullBg.getRGB(i, j);
                tmp1[0] = (rgb1 >> 16) & 0xff;
                tmp1[1] = (rgb1 >> 8) & 0xff;
                tmp1[2] = rgb1 & 0xff;
                tmp2[0] = (rgb2 >> 16) & 0xff;
                tmp2[1] = (rgb2 >> 8) & 0xff;
                tmp2[2] = rgb2 & 0xff;
                if (Math.abs(tmp1[0] - tmp2[0]) < 60 && Math.abs(tmp1[1] - tmp2[1]) < 60 && Math.abs(tmp1[2] - tmp2[2]) < 60) {
                    continue;
                } else {
                    gap = i;
                    break outer;
                }
            }
        }
        inputStream.close();
        inputStream2.close();
        System.out.println("缺口位置:" + gap);
        //得到缺块坐标移动
        WebElement slider = driver.findElement(By.className("geetest_slider_button"));
        moveWay1(driver, slider, gap);
    }

    public static void generateFile(byte[] data, String fileDirector, String filename) throws Exception {
        FileOutputStream stream = new FileOutputStream(fileDirector + File.separator + filename);
        stream.write(data);
        stream.flush();
        stream.close();
    }

    public static void moveWay1(WebDriver driver, WebElement slider, int gap) {
        Actions actions = new Actions(driver);
        actions.clickAndHold(slider);
        actions.perform();
        List<Double> doubles = moveManualiy(driver, gap - LEFT_GAP);
        Double[] array = doubles.toArray(new Double[doubles.size()]);
        double res = 0;
        for (int i = 0; i < array.length; i++) {
            // 由于经常会出现 forbidden
            int intValue = new Double(array[i]).intValue();
            res += (array[i] - intValue);
            actions.moveByOffset(intValue, (i % 2) * 1);
            actions.perform();
        }
        actions.moveByOffset(new Double(res).intValue(), 0);
        actions.pause(200 + new Random().nextInt(300)).release(slider);
        actions.perform();
    }

    // 先过去,再倒回来
    public static void moveWay2(WebDriver driver, WebElement slider, int gap) {
        Actions actions = new Actions(driver);
        actions.clickAndHold(slider);
        actions.moveByOffset(gap - LEFT_GAP, 0);
        actions.perform();
        actions.moveByOffset(10, 0);
        actions.perform();
        actions.moveByOffset(-10, 0);
        actions.perform();
        actions.pause(200 + new Random().nextInt(300)).release(slider);
        actions.perform();
    }


    public static List<Double> moveManualiy(WebDriver driver, double distance) {
        DecimalFormat df = new DecimalFormat("######0.00");
        // 先加速移动 然后匀速移动  1/2*a*t0^2  + at0 * t1 =  distance
        double current = 0;
        ArrayList<Double> list = new ArrayList<>();
        int a = 0, cnt = 0;
        while (current < distance) {
            if (cnt < 10) {
                a = a_speed;
            } else {
                a = 0;
            }
            if (a == a_speed) {
                list.add(Double.valueOf(df.format(0.5 * a * Math.pow((cnt + 1) * (0.1), 2) - current)));
                current = 0.5 * a * Math.pow((cnt + 1) * (0.1), 2);
            } else {
                if (distance - current < a_speed * 0.1) {
                    break;
                } else {
                    current = current + a_speed * 0.1;
                    list.add(a_speed * 0.1);
                }
            }
            cnt++;
        }
        list.add(distance - current);
        return list;
    }
}

3 收获

和测试小伙伴一起学习selenium使用过程中,也学到了一些知识。学习一些东西,有一些收获就要记录下来。虽然以后可能不怎么会用到这门技术。

3.1 xpath

以前我都不知道什么是xpath,我理解的xpath是定位扩展标记语言的方式,比如通过selenium定位元素:F12控制台还能这么用,右键直接可以复制元素的xpath,直接就能用了。
优点:简单快捷
缺点:/html/body/div[3]/div[2]/div[6]/div/div[1]/div[1]/div/a/div[1]/div/canvas[2] 形如这种页面稍微变化下,就不能用了; 不利于阅读。

xpath几种定位方式:

  • //元素名[@属性名="属性值"]
    比如//div[@id="udesk_container"]

  • //元素名[text()="文本内容"]
    比如//p[text()="武汉极意网络科技有限公司"]

  • //元素名/parent::父元素类型
    比如//p[text()='如果需要了解产品信息或售前服务欢迎联系我们']/parent::div,用来定位到查找元素的父亲元素

  • //元素名/子元素名[下标从1开始]
    比如//div[@class="geetest_panel_success geetest_success_animate"]/div[2],用来查找指定元素的子元素

以上就是我从xpath中学到的以及在日常中能常使用的。

3.2 base64图片内容转为字节数组

转为字节数组之后就可以生成文件到本地。

BASE64Decoder decoder = new BASE64Decoder();
byte[] bgBytes = decoder.decodeBuffer(bgImg);
FileOutputStream stream = new FileOutputStream(fileDirector + File.separator + filename);
stream.write(data);
stream.flush();
stream.close();
posted @ 2021-02-12 13:35  喜欢日向雏田一样的女子啊  阅读(1664)  评论(1编辑  收藏  举报