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
- fromseleniumimport 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 开头的一系列变量,这些变量会有很组,每组变量的命名格式如下:
其中是我们 source container 的别名;
是在 Dockerfile 中使用 EXPOSE 导出的端口,还有 docker run 的时候使用 - p 导出的端口;
- <port>
则是这些端口对应的协议。
- <protocol>
现在明白上文中为什么 --link selenium/hub 冒号后面为什么要跟 hub 或者 HUB 了吧,只有写成这样才会找到 HUB_PORT_4444_TCP_PORT 与 $HUB_PORT_4444_TCP_ADDR 这 2 个变量,免得容器是启动不起来的!!
到这里结束吧,本来还想多写点关于 docker 的用法知识。但是打字打的想吐了,可能本文中一些东西说的不清不楚,但是我相信您通过查阅资料一定能得到相应的解决!!
来源: http://www.cnblogs.com/hhudaqiang/p/6728890.html