selenium是目前web和app自动化测试的主要框架。对于web自动化测试而言,由于selenium2.0以后socker服务器由本地浏览器自己启动且直接通过浏览器原生API操作页面,故越来越多的人不再使用selenium RC了。大家使用的大多数是selenium-client,python版本的selenium-client最新版本是3.3.3(2017-04-04发布),却忽略了selenium server!事实上在大型的Grid分布式布局中必须要使用selenium server,我也会对这个布局的使用做一些必要的解释。在开始这个话题之前我说明两点内容:
第一、写selenium结合docker的使用只是个缩影,其实我想让大家了解使用docker,你可以不精通但是你得会用!!!
第二、我不太想写一些关于selenium的一些基础操作,如:browser.get(×××),browser.find_element_by_id(×××)....这些大家都会,不会可以查一些文档都会有相应的解释,对我自己而言我想记录的是一些关于比较高级点的话题,或者在自动化过程中遇到的难题(比如上篇操作flash),又或者selenium中大家忽略的一些方法的使用。
废话说了不少,开始我们的话题selenium结合docker构建分布式测试环境。
1.了解Selenium Standalone Server的使用
Selenium Standalone Server目前最新的版本是3.3.1(dcoker中selenium/hub这个镜像下载的也是这个最新的版本),因为我们的分布式测试环境是基于这个包的使用,我们花一点时间来说说如何使用它。下面是一张Grid分布式布局的基本结构:
上图中的selenium hub类似于一个中央处理器,selenium node是远程需要执行测试的节点,selenium Test case是运行在hub上的测试用例。
如何启动一个selenium hub?
首先需要java的运行环境,在终端中输入java -jar selenium-server-standalone-x.xx.x.jar -role hub -参数。
关于这个启动的参数其实是需要我们来关注的。我们输入java -jar selenium-server-standalone-2.52.0.jar -role hub -help下面列出来很多启动参数,我相信你应该能看得懂,在下面的介绍中我也会提到一些参数。
如何向selenium hub中注册信息?
在终端中输入java -jar selenium-server-standalone-x.xx.x.jar -role node -参数。
同理,我们输入java -jar selenium-server-standalone-2.52.0.jar -role node -help获取一些启动node的参数说明。
2.了解docker
说完了Grid的一些原理,我们来看看docker关于它的说明自行百度。我对它的简单理解如下:
docker就像个货船,货船是干嘛的?装集装箱的! 那么这些集装箱是什么?集装箱是一个个小的容器! 容器是什么? 容器就是一个个搭建有简易linux系统的特定应用,比如这个容器只搭建了java环境或者只搭建了apache环境! 容器如何启动它? 通过镜像来启动! 镜像怎么来的? 通过从镜像源pull或者自己构建镜像!
自言自语一会,不知道你是否明白我说什么了,不明白也没关系,我们结合搭建分布式环境这个应用,你应该会明白!
说完了docker是什么,我们着手使用!
1.如何安装docker?
如何安装docker百度上有很多它的说明,由于我的是windows系统,关于它的安装使用推荐文章如下:
http://www.jianshu.com/p/4052926bc12c这篇文章讲述了用docker搭建spalsh服务器,里面有详细的关于docker的安装使用
2.如何获取镜像?
我们用xshell或者CRT连接docker后,执行如下命令:
docker pull selenium/hub,它会自动从镜像源中下载最新的selenium/hub镜像
docker pull selenium/node-firefox,它会自动从镜像源中下载最新selenium/node-firefox镜像
当然可以用docker pull selenium/node-chrome下载selenium/node-chrome的镜像,目前是没有基于IE或者Safari浏览器的镜像,当然我们完全有能力去自己构建这些镜像!这一篇我主要讲基于firefox浏览器的,其他的类似...
顺便提一句,可以利用docker search+镜像名称能搜索镜像源所存在的镜像名称。当我们下载完这2个镜像后,我们在Xshell中输入docker images应该有如下信息:
3.如何启动这2个镜像?
上文提到了我们先启动一个hub,docker启动命令如下:
Sikilu$ docker run -p 5555:4444 -d --name ‘selenium_hub‘ selenium/hub做一些简单的说明:
run:通过镜像启动一个容器
-p:端口映射,5555是容器宿主机的端口就是我们docker这个轮船的端口,4444是我们容器的端口就是我们集装箱的端口。这说明了我们把容器的4444端口开放给docker主机的5555端口,那么我们就可以通过docker主机的5555端口来访问容器了,有点啰嗦~~~
-d:docker后台运行这个容器,我们知道运行server-standalone-2.52.0.jar这个包实际上是启动一个socket程序的,是在一个while循环中的。如果不启用后台运行的话,在xshell当前窗口是不能进行其他的操作的,当然你要再开一个窗口连接docker也可以。
--name:指定容器运行的别名,如果不指定会随机生成一个。
selenium/hub:就是我们要运行的镜像文件。
启动完hub后,我们启动一个node,启动node命令如下:
docker run -P -d --link selenium_hub:hub selenium/node-firefox做一些简单的说明:
run:和上文相同
-P:随机生成映射端口号,上文中的-p是指定特定的端口号,这里面是node我们并不需要知道容器内部的端口号,当然你要指定也可以,端口号不要冲突即可。
-d:后台运行与上文相同。
--link:说明我们这个容器是依赖上文中我们生成的容器selenium_hub,后面我们会提到link的使用。
selenium_hub:hub:前面的selenium_hub是我们上文中通过selenium/hub镜像启动容器的别名;后面的hub一定要写成hub或者HUB,写成其他启动失败,为什么这样我们后面会和--link一起说明。
selenium/node-firefox:node的镜像。
启动了selenium/hub与selenium/node后,我们运行docker ps有如下信息:
我们看出来相应的一些启动信息。
CONTAINER ID:容器的id号,随机生成,我们可以对他进行docker stop +id号、docker start +id号,或者docker rm+id号(删除该容器)
IMAGE:我们利用到的镜像文件。
CMMAND:容器启动时执行的命令,有Dockerfile文件指定,后面提到!
PORTS:容器的一些端口信息。
NAMES:注意到,在启动node时我们没有指定--name属性,所以他随机命名了sleepy_bhabha,而启动hub我们指定了--name属性,hub容器的name为我们指定的selenium-hub
由于我们是在后台启动这2个镜像文件的,我们想要看容器启动的log怎么办,以selenium_hub为例子,我们输入docker logs selenium_hub有如下信息:
这些信息与我们在本地PC机上使用selenium-server-standalone-×××.jar无异。同理我们可以用docker logs sleepy_bhabha查看node的启动信息。
做完这一切我们在浏览器中输入http://192.168.99.100:5555/grid/console,有如下信息:
可以看出来,我们的hub中有个新注册的装有firefox浏览器且版本号为50.0的node节点,我们本地写个简单的脚本来测试下这个简单的grid布局代码如下:
#coding=utf-8 from selenium import webdriver firefox_capabilities ={ "browserName": "firefox", "version": "50.0",#注意版本号一定要写对 "platform": "ANY", "javascriptEnabled": True, "marionette": True, } browser=webdriver.Remote("http://192.168.99.100:5555/wd/hub",desired_capabilities=firefox_capabilities)#注意端口号5555是我们上文中映射的宿主机端口号 browser.get("http://www.baidu.com") browser.get_screenshot_as_file("D:/baidu.png") browser.close()
代码比较简单,就是打开node端的firefox浏览器,输入百度后截图,一切看起来都是那么顺利,但是细心的同事可能发现至少有以前2个问题:
第一、我们对百度的截图中文是乱码的,这个不能忍啊!!!
第二、我们再次在IDE运行下这个脚本,发现一直处于阻塞状态也不报错,得不到hub远程的执行信息~~
对于问题一,因为我们要涉及到关于docker的镜像制作等操作我们放在后面介绍,先来看看第二个问题,为什么我们再次运行这个脚本无法正常执行?
首先,这是和docker无关的,你在真实的PC机上也是同样的结果!既然和docker无关那就是selenium-server-standalone-3.3.1.jar这个包的bug了?答案是:也不是!!那到底是什么我们来看看是什么原因。我们打开hub的log(docker logs selenium_hub)。截图如下:
看出来,第一次我们得到了一个包含有Capabilties请求的request后成功的建立了一个session,这个对应于我们第一次在IDE运行上文的脚本;我们第二次再次运行这个脚本,虽然hub得到了这个请求,但是没有成功的建立了一个新的session。那么问题可能出在node端它不容许建立新的session,所以我们一直处于阻塞状态(阻塞的时间由node的启动参数newSessionWaitTimeout决定),除非我们的第一个session被人为的或者由于超时被自动切断后,才能重新建立!
问题知道了,我们如何改进呢?对的就是改变node的启动参数。我们seleniu/node镜像默认的关键参数如下:
"capabilities": [ { "maxInstances": $NODE_MAX_INSTANCES,#最大的浏览器实例 ...... } "maxSession\": $NODE_MAX_SESSION #最大的Session数目
由于默认的maxSession为5而maxInstances为1,所以取最小的一个,node端只运行有一个session的存在,所以我们第二次运行处于阻塞状态!!这些信息我们会在后面对其镜像的构建文件Dockerfile讲解时提到。作者为什么要将maxInstances设置为1?我的理解是:一般用到Grid布局的都是一些比较大型的测试体系,一般环境搭建好了测试脚本写好了保证一天有一个测试结果就好。我们默认的timeout时间是30000S,这个从上面我们hub的启动日志可以看出来,也就是说如果hub在30000s内没有执行任何操作就会自动切断!这个timeout的时间是相当的长,关于这个timeout的时间长短我觉得与你的testcase有关,比如你在脚本中有个比较耗时的操作例如Sleep(100),睡眠100S再进行后续操作,如果你的timeout设置为30s,那么在30S的时候hub会主动切断这个连接,也就是说你后面的用例是不会再执行了....这个就是个仁者见仁,智者见智的事情了,但是我觉得把timeout搞长点好点。想要多次调试脚本,大不了我们把maxInstances与maxSession设置数目大点就了。实在不行你也可以先docker stop+node 容器的ID,再docker start +node 容器ID就好了!
说了第二个问题的一些看法,我们重点来看看如何解决第一个问题,在解决第一个问题的同时,我们修改一些配置,一并把第二个问题给解决了!!
我们先来看看selenium/hub与seleniu/node-firefox的源Dockerfile文件,源码的地址为:https://github.com/SeleniumHQ/docker-selenium,你可以下载或clone下来。我们来看看这个项目的结构:
Base是selenium镜像的基础,里面的Dockerfile除了在Ubuntu镜像的基础上安装了一些工具包,主要工作如下:
1.还有搭建了一个java运行环境,用来运行后来的selenium-server-standalone-3.3.1.jar
2.下载了selenium-server-standalone-3.3.1.jar放在容器的/opt/selenium文件夹下
Hub文件夹里面的Dockerfile就是用来build selenium/hub镜像的,是基于上面的Base,主要工作如下:
1.定义了node启动的一些参数,发现它把里面的timeout参数改成30s了。我并没有pull最新的selenium/hub,你们现在pull的可能timeout参数就是30s,我的是30000s。
2.定义了容器启动的脚本entry_point.sh,并把他复制到容器的/opt/bin下
NodeBase是一切node镜像的基础,我们查看里面的Dockerfile发现它是基于我们上面提到的Base的,主要工作如下:
1.定义了node容器的启动脚本entry_point.sh并复制到容器的/opt/bin/下。
NodeFirefox是用来build我们pull下来的selenium/node-firefox它是基于我们上面提到的NodeBase。我们查看它的Dokcerfile文件,发现它的主要工作如下:
1.下载了版本号为52.0.2的firefox浏览器,并做了一些配置。
2.下载了GeckoDriver,我们知道高版本的浏览器必须要通过GeckoDriver来启动一个socket,这也是selenium3.0诞生的主要原因。2.0是无法启动高版本的firefox的
3.设置fire-node的一些启动参数,我们清楚的看到NODE_MAX_INSTANCES 1与ENV NODE_MAX_SESSION 1的配置,所以如何要改变我们问题二的情况,在重新构建镜像的时候,就要重新定义这些环境变量了。
对上面的解释做个说明:
1.Dockerfile是用来build镜像的。指令为docker build -t 镜像名称 Dockerfile路径,例如:docker build -t selenium/my-firefxo-node . 用当前路径下的Dockerfile文件来创建一个 selenium/my-firefxo-node镜像文件。
2.Dockerfile一些基础知识可以百度了解,无非就是一些shell指令集的大集合。
说完了这些,我们就来解决我们遇到的第一个和第二个问题,思路很简单,对的,就是利用的selenium/firefox-node重新构建镜像。
截图中文乱码的问题我的考虑如下:
1.docker的Ubuntu版本是没有中文版的,所以我们要下载中文包,并把默认语言配置成中文
2.仅仅做了1的操作后,我们重新运行build的镜像,发现还是乱码,查阅资料明白可能是firefox必须支持相应的中文字体才可以避免乱码的情况,所以我们最终的Dockerfile内容如下:
FROM selenium/node-firefox #改变node的启动参数 ENV NODE_MAX_INSTANCES 10 ENV NODE_MAX_SESSION 10 # 配置中文 RUN sudo locale-gen zh_CN.UTF-8 && sudo DEBIAN_FRONTEND=noninteractive dpkg-reconfigure locales RUN sudo locale-gen zh_CN.UTF-8 ENV LANG zh_CN.UTF-8 ENV LANGUAGE zh_CN:zh ENV LC_ALL zh_CN.UTF-8 #更新软件包索引 RUN sudo apt-get update -qqy #安装基本字体 RUN sudo apt-get -qqy --no-install-recommends install fonts-ipafont-gothic xfonts-100dpi xfonts-75dpi xfonts-cyrillic xfonts-scalable #安装文泉驿微米黑字体 RUN sudo apt-get -qqy install ttf-wqy-microhei && sudo ln /etc/fonts/conf.d/65-wqy-microhei.conf /etc/fonts/conf.d/69-language-selector-zh-cn.conf
说到这遇到一个现实的问题是,我们建立了上面的Dokcerfile文件,但是是在windows平台上的,例如我是放在D盘的Sikilu这个文件夹下的,我们docker主机如何访问这个Sikilu文件夹呢?
首先,我们要明白个道理,docker是不能运行在windows平台,我们能在windows平台使用是因为它运行在Oracle VM VirtualBox里面的!!所以在windows平台搭建docker时给你安装了个VirtualBox虚拟机。这就好办了,我们打开这个虚拟机把D盘的Sikilu设置为共享文件夹就好了。具体截图如下:
设置完后,我们重启虚拟机中名称为default这个docker主机。
我们在Xshell中切换到根目录cd /发现了共享的文件夹:
我们把上面的Dockerfile放到我们共享的这个文件夹下并切换进去。执行docker build -t selenium/my_node-firefox .
build的过程可能要点时间最终build成功!我们运行docker images
可以看到我们build的镜像。我们先停掉上面用selenium/node-firefox启动的容器:
我们运行如下指令:docker run -P -d --link selenium_hub:hub selenium/my_node-firefox
由于selenium/my_node-firefox是基于selenium/node-firefox的,所以容器内部也是个版本为50.0的firefox浏览器。但是我们为该容器下载了中文包且下载了相应的字体,改变了firefox浏览器中文乱码的问题,同时改变了firefox node的启动参数,使它能够同时建立10个session!具体数字多少自己更改,当然也可以改其他的启动参数!
我们运行上面的测试脚本,发现问题能得到有效的解决,当然运行超过10次就又阻塞了,原因上面说的很清楚了我们的最大session设置了10!
还有一个问题:我们如何建立其他版本号的node-firefox?上面说了我们pull下来的是50.0的,比如我要构建集群测试每台机器上不同版本的firefox,那么我们就要创建不同版本的镜像!
这边有2中思路,第一种思路想法也简单
1.下载我们特定版本的浏览器到我们共享的Sikilu文件夹中。
2.运行我们上面build的selenium/my_node-firefox这个镜像把这个共享的文件夹挂载进去。(-v参数)
docker run -P -v /Sikilu/:/Sikilu --link selenium_hub:hub selenium/my_node-firefox
3.我们删除该容器/opt下的firefox-50.0这个浏览器,同时解压挂载的宿主机Sikilu中的特定版本的firefox到/opt目录
4.运行rm -rf /usr/bin/firefox
5.ln -fs /opt/firefox-$FIREFOX_VERSION/firefox /usr/bin/firefox
6.exit这个容器运行docker commit -m "firefox-47.0.2" 当前容器的ID 镜像名称,例如docker commit -m "firefox-47.0.2" 2d96630b4f07(我们进入的容器id号) selenium/node-firefox47.0.2(新的镜像名称)
上面的一系列操作,我们得到了一个特定版本的镜像,下次启动我们run这个镜像就可以了。
第二种思路,在我们新build的selenium/my_node-firefox这个基础新建Dockerfile,重新构建镜像(建议这种方式毕竟高端点...)内容如下:
FROM selenium/my_node-firefox #指定新构建firefox版本 ARG FIREFOX_VERSION=47.0.2 #删除已存在firefox浏览器 RUN a=$(find /opt/ -name "firefox" |cut -d/ -f 3)&&sudo rm -rf /opt/$a&&sudo rm -rf /usr/bin/firefox #下载指定版本的浏览器 RUN sudo apt-get update -qqy && sudo rm -rf /var/lib/apt/lists/* /var/cache/apt/* && sudo wget --no-verbose -O /tmp/firefox.tar.bz2 https://download-installer.cdn.mozilla.net/pub/firefox/releases/$FIREFOX_VERSION/linux-x86_64/en-US/firefox-$FIREFOX_VERSION.tar.bz2 && sudo tar -C /opt -xjf /tmp/firefox.tar.bz2 && sudo rm /tmp/firefox.tar.bz2 && sudo mv /opt/firefox /opt/firefox-$FIREFOX_VERSION && sudo ln -fs /opt/firefox-$FIREFOX_VERSION/firefox /usr/bin/firefox
同样,我们切换到共享的Sikilu文件夹中运行:docker build -t selenium/node-firefox47.0.2 .
如果网络较慢是特别费时间的,这时候去听音乐去吧,一段时间后建立一个firefox版本为47.0.2的镜像,当我们run这个镜像来启动容器时,注册进hub的就是这个版本的浏览器!!
最后一个问题收尾,就是我们最开始提到的run一个镜像时的--link的参数。
docker通过link将source container中定义的环境变量全部导入到received container中。我们先来看看上面提到NodeBase文件下的entry_point.sh这个脚本,我们截取最关键的一部分:就是执行selenium-server-standalone.jar这个jar包的一段:
xvfb-run -n $SERVERNUM --server-args="-screen 0 $GEOMETRY -ac +extension RANDR" java ${JAVA_OPTS} -jar /opt/selenium/selenium-server-standalone.jar -role node -hub http://$HUB_PORT_4444_TCP_ADDR:$HUB_PORT_4444_TCP_PORT/grid/register -nodeConfig /opt/selenium/config.json ${SE_OPTS} & NODE_PID=$!
上面的$HUB_PORT_4444_TCP_PORT表示我们hub的端口号,$HUB_PORT_4444_TCP_ADDR这个代表hub的IP地址,这些是怎么来的?其实这个是因为我们使用link参数,docker自动生成这些参数。我们进入到由镜像selenium/my_node-firefox启动的容器(sudo docker exec -it 24c5fb9a649a /bin/bash),输入env
发现了这2个字段,其实这个容器的环境变量被分成5个部分,一个部分就是ALIASDB_PORT开头的一系列变量,这些变量会有很组,每组变量的命名格式如下:
其中<alias>是我们source container的别名;<port>
是在Dockerfile中使用EXPOSE导出的端口,还有docker run 的时候使用-p导出的端口;<protocol>
则是这些端口对应的协议。
现在明白上文中为什么--link selenium/hub冒号后面为什么要跟hub或者HUB了吧,只有写成这样才会找到HUB_PORT_4444_TCP_PORT与$HUB_PORT_4444_TCP_ADDR这2个变量,免得容器是启动不起来的!!
到这里结束吧,本来还想多写点关于docker的用法知识。但是打字打的想吐了,可能本文中一些东西说的不清不楚,但是我相信您通过查阅资料一定能得到相应的解决!!