新年快乐!

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

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

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

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

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

测试类代码如下:

  1. import org.openqa.selenium.By;
  2. import org.openqa.selenium.WebDriver;
  3. import org.openqa.selenium.chrome.ChromeDriver;
  4. public class AutoTests {
  5. public static void main(String[] args) throws Exception {
  6. try {
  7. System.setProperty("webdriver.chrome.driver", "D:\\new_soft\\chromedriver.exe");
  8. WebDriver driver = new ChromeDriver();
  9. driver.get("https://www.geetest.com/Register");
  10. driver.manage().window().maximize();
  11. driver.findElement(By.xpath("//input[@placeholder=\"手机号码\"]")).sendKeys("11111111111");
  12. driver.findElement(By.xpath("//div[text()=\"获取验证码\"]")).click();
  13. //处理滑动验证码
  14. SlideVerifyBlock.common(driver);
  15. Thread.sleep(2000);
  16. driver.quit();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }

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

  1. import org.openqa.selenium.By;
  2. import org.openqa.selenium.JavascriptExecutor;
  3. import org.openqa.selenium.WebDriver;
  4. import org.openqa.selenium.WebElement;
  5. import org.openqa.selenium.interactions.Actions;
  6. import sun.misc.BASE64Decoder;
  7. import javax.imageio.ImageIO;
  8. import java.awt.image.BufferedImage;
  9. import java.io.*;
  10. import java.text.DecimalFormat;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import java.util.Random;
  14. public class SlideVerifyBlock {
  15. // 减少计算50
  16. private static final int OFFSET = 50;
  17. // 滑块和左边轴都有一定的距离, 误差范围内都可以接受
  18. private static final int LEFT_GAP = 8;
  19. //加速度
  20. private static int a_speed = 150;
  21. public static void common(WebDriver driver) throws Exception {
  22. String bgClassName = "geetest_canvas_bg geetest_absolute",
  23. bgFullClassName = "geetest_canvas_fullbg geetest_fade geetest_absolute";
  24. String getImgBase64 = "return document.getElementsByClassName(\\'%s\\')[0].toDataURL(\'image/png\');";
  25. //等待一下验证码加载出来
  26. Thread.sleep(1000);
  27. String bgImg = ((JavascriptExecutor) driver).executeScript(String.format(getImgBase64, bgClassName)).toString();
  28. bgImg = bgImg.startsWith("data:image/png;base64,") ? bgImg.substring(22) : bgImg;
  29. String bgFullImg = ((JavascriptExecutor) driver).executeScript(String.format(getImgBase64, bgFullClassName)).toString();
  30. bgFullImg = bgFullImg.startsWith("data:image/png;base64,") ? bgFullImg.substring(22) : bgFullImg;
  31. BASE64Decoder decoder = new BASE64Decoder();
  32. byte[] bgBytes = decoder.decodeBuffer(bgImg);
  33. byte[] bgFullBytes = decoder.decodeBuffer(bgFullImg);
  34. generateFile(bgBytes, "C:\\Users\\Administrator\\Desktop\\笔记", "bg.png");
  35. generateFile(bgFullBytes, "C:\\Users\\Administrator\\Desktop\\笔记", "bgfull.png");
  36. InputStream inputStream = new ByteArrayInputStream(bgBytes);
  37. InputStream inputStream2 = new ByteArrayInputStream(bgFullBytes);
  38. BufferedImage bg = ImageIO.read(inputStream), fullBg = ImageIO.read(inputStream2);
  39. ;
  40. int width = bg.getWidth(), height = bg.getHeight();
  41. int[] tmp1 = new int[3], tmp2 = new int[3];
  42. int gap = 0;
  43. outer:
  44. for (int i = OFFSET; i < width; i++) {
  45. for (int j = OFFSET; j < height; j++) {
  46. int rgb1 = bg.getRGB(i, j), rgb2 = fullBg.getRGB(i, j);
  47. tmp1[0] = (rgb1 >> 16) & 0xff;
  48. tmp1[1] = (rgb1 >> 8) & 0xff;
  49. tmp1[2] = rgb1 & 0xff;
  50. tmp2[0] = (rgb2 >> 16) & 0xff;
  51. tmp2[1] = (rgb2 >> 8) & 0xff;
  52. tmp2[2] = rgb2 & 0xff;
  53. if (Math.abs(tmp1[0] - tmp2[0]) < 60 && Math.abs(tmp1[1] - tmp2[1]) < 60 && Math.abs(tmp1[2] - tmp2[2]) < 60) {
  54. continue;
  55. } else {
  56. gap = i;
  57. break outer;
  58. }
  59. }
  60. }
  61. inputStream.close();
  62. inputStream2.close();
  63. System.out.println("缺口位置:" + gap);
  64. //得到缺块坐标移动
  65. WebElement slider = driver.findElement(By.className("geetest_slider_button"));
  66. moveWay1(driver, slider, gap);
  67. }
  68. public static void generateFile(byte[] data, String fileDirector, String filename) throws Exception {
  69. FileOutputStream stream = new FileOutputStream(fileDirector + File.separator + filename);
  70. stream.write(data);
  71. stream.flush();
  72. stream.close();
  73. }
  74. public static void moveWay1(WebDriver driver, WebElement slider, int gap) {
  75. Actions actions = new Actions(driver);
  76. actions.clickAndHold(slider);
  77. actions.perform();
  78. List<Double> doubles = moveManualiy(driver, gap - LEFT_GAP);
  79. Double[] array = doubles.toArray(new Double[doubles.size()]);
  80. double res = 0;
  81. for (int i = 0; i < array.length; i++) {
  82. // 由于经常会出现 forbidden
  83. int intValue = new Double(array[i]).intValue();
  84. res += (array[i] - intValue);
  85. actions.moveByOffset(intValue, (i % 2) * 1);
  86. actions.perform();
  87. }
  88. actions.moveByOffset(new Double(res).intValue(), 0);
  89. actions.pause(200 + new Random().nextInt(300)).release(slider);
  90. actions.perform();
  91. }
  92. // 先过去,再倒回来
  93. public static void moveWay2(WebDriver driver, WebElement slider, int gap) {
  94. Actions actions = new Actions(driver);
  95. actions.clickAndHold(slider);
  96. actions.moveByOffset(gap - LEFT_GAP, 0);
  97. actions.perform();
  98. actions.moveByOffset(10, 0);
  99. actions.perform();
  100. actions.moveByOffset(-10, 0);
  101. actions.perform();
  102. actions.pause(200 + new Random().nextInt(300)).release(slider);
  103. actions.perform();
  104. }
  105. public static List<Double> moveManualiy(WebDriver driver, double distance) {
  106. DecimalFormat df = new DecimalFormat("######0.00");
  107. // 先加速移动 然后匀速移动 1/2*a*t0^2 + at0 * t1 = distance
  108. double current = 0;
  109. ArrayList<Double> list = new ArrayList<>();
  110. int a = 0, cnt = 0;
  111. while (current < distance) {
  112. if (cnt < 10) {
  113. a = a_speed;
  114. } else {
  115. a = 0;
  116. }
  117. if (a == a_speed) {
  118. list.add(Double.valueOf(df.format(0.5 * a * Math.pow((cnt + 1) * (0.1), 2) - current)));
  119. current = 0.5 * a * Math.pow((cnt + 1) * (0.1), 2);
  120. } else {
  121. if (distance - current < a_speed * 0.1) {
  122. break;
  123. } else {
  124. current = current + a_speed * 0.1;
  125. list.add(a_speed * 0.1);
  126. }
  127. }
  128. cnt++;
  129. }
  130. list.add(distance - current);
  131. return list;
  132. }
  133. }

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

以前我都不知道什么是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中学到的以及在日常中能常使用的。

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

  1. BASE64Decoder decoder = new BASE64Decoder();
  2. byte[] bgBytes = decoder.decodeBuffer(bgImg);
  3. FileOutputStream stream = new FileOutputStream(fileDirector + File.separator + filename);
  4. stream.write(data);
  5. stream.flush();
  6. stream.close();

版权声明:本文为lvbinbin2yujie原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lvbinbin2yujie/p/14398744.html