Selenium WebUI 自动化测试框架

框架结构

框架基于 PO 模型进行设计,将页面元素与操作进行拆分,减少页面改动时的维护成本;同时使用 xsd 自定义 xml 标签,通过解析 xml 来驱动 selenium 进行执行,减少了一定的语言学习成本。
主要功能

  1. 基于 selenium-grid 做并发执行
  2. 可基于 xml 编写脚本
  3. 执行步骤可插入自定义函数
  4. 基础操作 (点击、hover、输入、值比较、切换 iframe、切换页面、关闭页面、模板匹配等)
  5. 测试报告收集

设计思路

  1. webUI 自动化大部分都基于 selenium 来进行
  2. PO 模型是 webUI 自动化中应用最广的设计模式
  3. selenium 执行时,可以通过 DataProvider 提供测试数据
  4. 常见的测试数据管理方式无外乎存在 db / 存在本地文件,并且都是结构化数据
  5. Java 有 DOM4J 库,能够很方便的解析 DOM 文档,同时 DOM 中的 XML 可以灵活定义,描述性也比较强,因此选用 XML 作为测试数据管理这样就有了一个框架大致的运行流程
    1. 初始化流程数据以及基础配置
    2. 初始化 webDriver
    3. 遍历解析后的流程数据进行执行
    4. 输出测试报告
    5. 关闭 webDriver

设计初期的明显问题
Q1:如果只能调用内置方法的话,灵活性不够
A:因此引入了 Spring 作为实例管理,这样就只需要在流程数据中使用指定标签 +beanName 就可以实现自定义方法的执行了
Q2:webUI 自动化通常执行时间过长,如何缩短?
A:引入 selenium-grid 做分布式执行,并且可以隔离数据
Q3:元素定位有没有更为准确和快捷的方式?
A:引入特征识别、模板匹配、OCR 等图像算法

基本功能

XML 描述

使用具备自描述性的 XML 进行脚本编写,降低门槛
如果想要新增标签/属性,在 XSD 中定义和对应的 entity 增加字段后,在 AutoTestSuiteParser 类中增加赋值即可

元素复用

通过 ref 标签引用 element 文件[

即可使用,重复引用可实现复用

提取与比较

通过指定 html 标签的属性获取对应值,和 variableName 以 kv 的形式存储在 threadLocal 的 Map 中,从而在同一 testFlow 的后续流程中以 $variableName 进行提取,与期望值比较

模板匹配点击

使用 JavaCV 进行模板匹配并点击的操作
eg:

使用属性 templateImgName 即可
模板图片存放于 src/test/template_img 下,源图片则是当前浏览器窗口截图
tips:使用模板匹配时,不建议在本地执行,会大概率失败,因为本地操作可能会干扰鼠标原始定位

并发与分布式执行

通过反射读取 engine.properties 文件中的配置,可动态配置并发执行用例数

并发执行时将会 send task 到 selenium-hub,并进行分发,加快执行速度

执行自定义方法

使用 custom 标签时,指定 customFunction 指定类的的 bean 名称,并且该类应该实现 ICustomAction 的 execute 方法

方法 bean 名称为_@_Service() 中的 beanName

常用操作封装

封装了常用的操作,同时可以进行扩展

环境部署

selenium-grid

m1 (arm64)

version: "3"
services:
  selenium-hub:
    image: seleniarm/hub:4.0.0-beta-1-20210215
    container_name: selenium-hub-arm
    ports:
      - "4444:4444"
    environment:
      - GRID_MAX_SESSION=50
      - GRID_TIMEOUT=200
      - START_XVFB=false
      - GRID_CLEAN_UP_CYCLE=200
  chrome:
    image: seleniarm/node-chromium:4.0.0-beta-1-20210215
    volumes:
      - /dev/shm:/dev/shm
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_PUBLISH_PORT=4444
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - NODE_MAX_INSTANCES=5
      - NODE_MAX_SESSION=5
      - GRID_CLEAN_UP_CYCLE=200

intel(x86)

version: "3"
services:
  selenium-hub:
    image: selenium/hub
    container_name: selenium-hub
    ports:
      - "4444:4444"
    environment:
      - GRID_MAX_SESSION=50
      - GRID_TIMEOUT=900
      - START_XVFB=false
  chrome:
    image: selenium/node-chrome
    volumes:
      - /dev/shm:/dev/shm
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_PUBLISH_PORT=4444
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - NODE_MAX_INSTANCES=5
      - NODE_MAX_SESSION=5
      - GRID_CLEAN_UP_CYCLE=200

start.sh

#! /bin/sh

docker-compose up -d --scale chrome=2

chrome=2 表示 selenium-grid 集群的节点数量,chrome 为 docker-compose 中的 service name
启动 shell 脚本,docker ps 查看是否启动成功

访问 localhost:4444 / 虚拟机 ip:4444

Extentx

创建 Dockerfile 文件

FROM node:alpine
LABEL maintainer="lain"

RUN mkdir -p ./app
WORKDIR ./app

RUN apk add --no-cache git
RUN git clone https://gist.github.com/3c4f35a41c58a55a0ffd00c3e64142c8.git tmpChange
RUN git clone https://github.com/anshooarora/extentx.git

RUN mv tmpChange/connections.js extentx/config/connections.js
RUN rm -rf tmpChange

WORKDIR ./extentx

EXPOSE 1337

RUN npm set registry https://registry.npm.taobao.org
RUN npm config set registry https://registry.npm.taobao.org
RUN npm config set disturl https://npm.taobao.org/dist
RUN npm install

CMD ["./node_modules/.bin/sails", "lift"]

构建镜像
docker build -t extentx .
创建 docker-compose.yml 文件

version: '3.6'
services:
 extentx:
  build: .
  environment:
    - MONGODB_PORT_27017_TCP_ADDR=mongo
  links:
    - mongo:mongo
  ports:
    - 1337:1337

 mongo:
  image: mongo:3.4
  ports:
    - 27010:27017

docker-compose up -d启动
访问 localhost:1337 即可

打包到本地仓库

下载附在项目中的 extentsReport-1.0-SNAPSHOT.jar ,提取出 jar 包中的 pom 文件,和 jar 放在同一级目录下,执行命令
mvn install:install-file -Dfile=extentsReport-1.0-SNAPSHOT.jar -DartifactId=extentsReport -DgroupId=com.antigenmhc -Dversion=1.0-SNAPSHOT -Dpackaging=jar -DpomFile=pom.xml
即可

JavaCV (以 arm64 为例)

mac m1 (arm64)

前置环境为已下载 arm64 版本的 JDK

  1. 安装并设置环境变量
# 升级 brew
brew update
# 安装 cmake
brew install cmake
# 安装 ant
brew install ant

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_331.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
export JAVA_AWT_INCLUDE_PATH=$JAVA_HOME
export JAVA_AWT_LIBRARY=$JAVA_HOME
export JAVA_INCLUDE_PATH=$JAVA_HOME/indclude
export JAVA_INCLUDE_PATH2=$JAVA_HOME/include/darwin
export JAVA_JVM_LIBRARY=$JAVA_HOME

  1. 打开 Java build

brew edit opencv
找到
-DBUILD_opencv_java=OFF 修改为-DBUILD_opencv_java=ON
在上面的参数之后添加参数:-DOPENCV_JAVA_TARGET_VERSION=1.8,防止使用高版本 JDK 进行编译

  1. 编译

brew install --build-from-source opencv

  1. 生成 jar 和 dylib:成功后在 /opt/homebrew/Cellar/opencv/4.7.0/share/java/opencv4 这里会生成 libopencv_java470.dylib 和 opencv-470.jar
  1. 将 jar 和 dylib 加入项目
设置项目 JDK 为刚才用来编辑 (即 JAVA\_HOME ) 的 JDK
  1. 引入依赖
<dependency>
  <groupId>org.bytedeco</groupId>
  <artifactId>javacv-platform</artifactId>
  <version>1.5.7</version>
</dependency>
<dependency>
  <groupId>org.bytedeco</groupId>
  <artifactId>javacpp</artifactId>
  <version>1.5.7</version>
</dependency>
  1. 运行 demo
//待匹配图片
Mat src = imread("filePath",Imgcodecs.IMREAD_GRAYSCALE);
Mat mInput=src.clone();
// 获取匹配模板
Mat mTemplate = imread("filePath",Imgcodecs.IMREAD_GRAYSCALE);
/**
* TM_SQDIFF = 0, 平方差匹配法,最好的匹配为0,值越大匹配越差
* TM_SQDIFF_NORMED = 1,归一化平方差匹配法
* TM_CCORR = 2,相关匹配法,采用乘法操作,数值越大表明匹配越好
* TM_CCORR_NORMED = 3,归一化相关匹配法
* TM_CCOEFF = 4,相关系数匹配法,最好的匹配为1,-1表示最差的匹配
* TM_CCOEFF_NORMED = 5;归一化相关系数匹配法
*/
int resultRows = mInput.rows() - mTemplate.rows() + 1;
int resultCols = mInput.cols() - mTemplate.cols() + 1;
Mat gResult = new Mat(resultRows, resultCols, CvType.CV_32FC1);

Imgproc.matchTemplate(mInput, mTemplate, gResult, Imgproc.TM_CCORR_NORMED);

Core.normalize(gResult, gResult, 0, 1, Core.NORM_MINMAX, -1, new Mat());
Core.MinMaxLocResult mmlr = Core.minMaxLoc(gResult);
Point matchLocation = mmlr.maxLoc;

double x = matchLocation.x + (mTemplate.cols() / 2);
double y = matchLocation.y + (mTemplate.rows() / 2);
System.out.println(new Point(x, y));

控制台输出坐标即成功

运行项目

项目结构及说明

项目的大致结构如下

qa-ui-test-demo                                                   
├─ screen:截图                                                            
├─ src                                          
│  ├─ main                                           
│  │  └─ resources                              
│  │     ├─ css                                                      
│  │     ├─ driver:浏览器驱动                              
│  │     │  ├─ chromedriver                     
│  │     │  └─ geckodriver                                             
│  │     ├─ cc.properties:邮件发送方邮箱
│  │     ├─ extentx.properties:连接 extentx 平台配置                       
│  │     ├─ engine.properties:测试报告以及核心配置                   
│  │     ├─ mail.properties:邮件配置                     
│  │     └─ sendto.properties:邮件接收方邮箱                   
│  └─ test                                      
│     ├─ java                                   
│     │  └─ com                                 
│     │     └─ qalain                           
│     │        └─ ui                            
│     │           └─ action:java 脚本                     
│     └─ resources                              
│        ├─ page:page xml,用来转为 page pojo 的                                
│        │  └─ demo-baidu.xml                   
│        ├─ suiteflow:自动化测试流程文件                          
│        │  └─ demo-flow.xml                    
│        └─ suite-testng.xml                                    
├─ pom.xml                                                               
└─ README.md                                                     

因为经过 xml 二次封装了,所以需要了解常见标签

  1. action 为 openPage 时,需要设置的属性为 url;
  2. action 为 click 时,需要设置的属性为 refId;
  3. action 为 fillvalue 时,需要设置的属性为 elementId 和 value;
  4. action 为 compareValue 时,需要设置的属性为 refId 和 expectValue;
  5. action 为 keyBoardEnter 时,无需设置其他属性;
  6. action 为 swithWindow 时,无需设置其他属性;
  7. action 为 closeCurrentWindow 时,无需设置其他属性;
  8. action 为 closeDrive 时,无需设置其他属性;
  9. action 为 jsInvoker 时,需要设置的属性为 jsCode;
  10. action 为 custom 时,需要设置的属性为 customFunction;
  11. action 为 hover 时,无需设置其他属性

推荐相关学习视频

【打卡WebUI自动化测试】从Selenium原理→元素定位→设计模式→关键字驱动→Excel数据驱动的实现效果,……

记录UI自动化测试框架从搭建到测试报告生成的全过程:设计模式→关键字驱动设计→Excel数据驱动设计→生成……

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容