UI自动化
一、概述
1、什么是UI自动化
UI即用户界面(user interface)的缩写,客户通过UI实现查看、操作等功能。UI自动化测试就是把人为驱动的测试转化为机器执行的一种过程,其重点在于持续集成。
2、UI自动化测试技术
1)Qtp类似于脚本执行软件、通过对操作者鼠标、键盘的记录,生成测试脚本;之后执行录制好的测试脚本进行断言、生成测试报告。Qtp更加适用于C/S架构的软件。
链接:Qtp自动测试工具(案例学习) - 思维焦点 - 博客园 (cnblogs.com)
2)Robot Framework是一个基于关键字驱动的自动化测试框架。通过该框架,测试人员可使用python封装关键字,并在非代码环境下使用关键字构建可被执行的测试用例,适合B/S架构的web应用。
链接:Robot Framework官方教程(一)入门 - 简书 (jianshu.com)
3)Watir Watir( web Application Testing in Ruby) 是一个优秀的开源工具,用于开发基于Web 应用的自动化测试程序。它使用Ruby脚本语言,提供了轻量级的自动化测试程序框架和丰富的开发库。
链接:开源Web自动化测试框架——Watir试用手记_Watir_领测软件测试网 (ltesting.net)
4)Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一。它最初由杰森·哈金斯(Jason Huggins)于2004年开发。Selenium支持跨不同浏览器,平台和编程语言的自动化。Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ruby。目前,Selenium Web驱动程序最受Python和C#欢迎。
5)Appium主要应用于移动端测试,测试代码通过appium,实现对于移动端的操作,从而带到测试的目的。
6)Win UI通过控制windows电脑,实现测试
二、Selenium
1、Selenium实现流程
1)元素定位→2)通过python编写测试脚本→3)python调用webDriver实现脚本执行→4)webDriver返回测试结果→5)python进行断言→6)python输出测试报告
笔者主要从三个部分来对Selenium自动化测试进行描述
2、元素定位
1)Xpath定位
1、路径 绝对路径 /html/body/div[2]/div[1]/div/div[4]/div/a[1]/img 相对路径 //*[@id="titleUserIcon"] 2、元素筛选 "*", 通配符,表示任意的标签,任意的字符 "[]", 下标法是从1开始,当标签在同一个父级的情况才能使用 "@", 表示后边跟的是标签属性 "and/or", 连接多条筛选语句 "contains()", 包含 //div[contains(@title,"搜索")] 3、xpath轴 //div[@id="userinfo"]/a[1]/following-sibling::a 找弟弟 //div[@id="userinfo"]/a[1]/preceding-sibling::a 找哥哥
2)CSS 选择器
参考链接:CSS 选择器 - CSS(层叠样式表) | MDN (mozilla.org)
1、基本符号代表的意思
".", 表示属性class
">", 表示id
"+", 表示找弟弟,相邻的元素,不能找哥哥
" ", 表示任意子元素
"*", 通配符
"^", 表示什么开头
2、伪类
div[class="van-slide ggc"]>div:last-child
div[class="van-slide ggc"]>div:nth-child(3)
3、webDriver原理

整个的工作过程简要来说就是:IDE通过webDriver实例化一个Driver,通过实例化的Driver向Browser发送指令,并接收Browser返回的结果,之后将返回的结果再传输到IDE。
其实早版本的selenium并不是通过webDriver来控制浏览器的,在selenium1.x的时候,selenium通过JS注入来控制浏览器。过程为Selenium RC启动一个Server,将操作Web元素的API调用转化为一段段Javascript,在Selenium内核启动浏览器之后注入这段Javascript。Javascript可以获取并调用DOM的任何元素,自如的进行操作,由此才实现了Selenium的目的:自动化Web操作。这种Javascript注入技术的缺点是速度不理想,而且稳定性大大依赖于Selenium内核对API翻译成的Javascript质量高低。
于是开发了selenium2.x,即通过webDriver实现自动化Web操作。这个版本的selenium是利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API。直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)。由于使用的是浏览器原生的API,速度大大提高,而且调用的稳定性交给了浏览器厂商本身,显然是更加科学。然而带来的一些副作用就是,不同的浏览器厂商,对Web元素的操作和呈现多少会有一些差异,这就直接导致了Selenium WebDriver要分浏览器厂商不同,而提供不同的实现。
4、WebDriver和Selenium配置
1)WebDriver配置
因为不同浏览器支持的WebDriver是不同的,因此需要根据测试的需求下载对应的WebDriver。然后将下载好的Driver放到环境变量配置到的文件夹,此处笔者使用Python完成UI自动化,因此将WebDriver放到了Python的环境变量中。
下载链接:Selenium 与 浏览器驱动 - 介绍 (liushilive.github.io)
2)Selenium安装
pip install selenium -i https://mirrors.aliyun.com/pypi/simple/
5、Selenium常用方法
1、常用的包名/类名 from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.support.select import Select from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By 2、webdriver webdriver主要用于实例化一个driver对象,不同的浏览器实例化的方式不同。 Chrome_driver = webdriver.Chrome() Edge_driver = webdriver.Edge() Firefox_driver = webdriver.Firefox()
3、expected_conditions
alert_is_present() # 判断alert是不是存在
element_selection_state_to_be(element, is_selected) # 判断某元素选中状态(True/False)是否与预期相同
element_located_to_be_selected(locator) # locator为一个元组(By.ID, "checkbox")
element_to_be_selected(element) # element为定位到的元素
element_to_be_clickable(locator) # 判断某元素是否是可访问,可启动
frame_to_be_available_and_switch_to_it(locator) # 判断frame是否可以切换过去
invisibility_of_element_located(locator) # 判断某个元素是不是不可访问或者不存在,True表示不可见
new_window_is_opened(current_handles) # 通过当前窗口句柄判断是否新增了窗口
number_of_windows_to_be(num_windows) # 判断当前窗口是不是指定数量
presence_of_all_elements_located(locator) # 判断定位元素的位置至少有一个元素存在页面中
presence_of_element_located(locator) # 判断一个元素存在页面中
staleness_of(element) # 判断某个元素不再附加于DOM树中
text_to_be_present_in_element(locator, text_) # 判断给定文本是否出现在特定元素中
text_to_be_present_in_element_value(locator, text_) # 判断给定文本是否出现在特定元素的value中
title_contains(title) # title是否包含特定文本
title_is(title) # title是否是指定的title
url_changes(url) # url判断网页是否更改了
url_contains(url) # 判断网页是否包含给定字段
url_matches(pattern) # 判断网址是否匹配给定格式
url_to_be(url) # 判断网页是否为特定网页
visibility_of_element_located(locator) # 判断特定元素是否可见
visibility_of(element) # 判断特定元素是否可见
visibility_of_all_elements_located(locator) # 判断locator定位的所有元素都存在
visibility_of_any_elements_located(locator) # 判断locator定位的任意元素存在
4、实例化后的driver常见用法
driver = webdriver.Chrome() # 实例化一个driver driver.get(url) # 打开指定网址 driver.refresh() # 刷新页面 driver.title # 获取标题 driver.page_source # 获取当前页面渲染之后的源代码 driver.current_url # 获取当前的url driver.window_handles # 获取当前窗口句柄,以列表形式返回 driver.current_window_handle # 获取当前所在窗口句柄 driver.close() # 关闭浏览器当前窗口 driver.quit() # 关闭浏览器所有窗口并且退出
5、Select
def select_method(self):
self.driver.get("https://www.woniuxy.com/live")
# 找元素,使用变量接收元素
select_element = self.driver.find_element_by_id("searchlivebymajor")
my_select = Select(select_element) # 传入参数,是web元素,得是select元素,select标签
my_select.select_by_visible_text("Java测试开发") # 通过可以看的见文本,选择一项
6、切换window/frame
def switch_window(self): # 切换window
self.driver.get("http://www.woniuxy.com/") # 打开一个url
element = self.driver.find_element_by_link_text("企业内训")
element.click()
currents = self.driver.window_handles # 是个列表
self.driver.switch_to.window(currents[1]) # 使用下标法选择窗口的句柄,切换到新的句柄,新的位置
shizi_element = self.driver.find_element_by_link_text("师资介绍")
print(shizi_element) 7、切换ifram
def switch_iframe(self): self.driver.get("https://www.woniuxy.com/train/teacher.html") time.sleep(15) # 找第一个iframe iframe1 = self.driver.find_element_by_id("iframe_company_mini") # 切换到iframe1 self.driver.switch_to.frame(iframe1) iframe2 = self.driver.find_element_by_class_name("ke-edit-iframe") # 切换到iframe2 self.driver.switch_to.frame(iframe2) # 找到聊天的输入框 input_box = self.driver.find_element_by_xpath("/html/body") # 输入发送的内容 input_box.send_keys("this is a testing") # 逐层向上返回 self.driver.switch_to.parent_frame()# 返回父级iframe # 直接返回到最外层,也就是刚开始的地方 self.driver.switch_to.default_content() # 返回最初的地方,重新来过
6、ActionChaines(动作链)
1、动作链常用动作
click(on_element=None) 单击鼠标左键 click_and_hold(on_element=None) 点击鼠标左键,不松开 context_click(on_element=None) 点击鼠标右键 double_click(on_element=None) 双击鼠标左键 drag_and_drop(source, target) 拖拽到某个元素然后松开 drag_and_drop_by_offset(source, xoffset, yoffset) 拖拽到某个坐标然后松开 key_down(value, element=None) 按下某个键盘上的键 key_up(value, element=None) 松开某个键 move_by_offset(xoffset, yoffset) 鼠标从当前位置移动到某个坐标 move_to_element(to_element) 鼠标移动到某个元素 move_to_element_with_offset(to_element, xoffset, yoffset) 移动到距某个元素(左上角坐标)多少距离的位置 perform() 执行链中的所有动作 release(on_element=None) 在某个元素位置松开鼠标左键 send_keys(*keys_to_send) 发送某个键到当前焦点的元素 send_keys_to_element(element, *keys_to_send) 发送某个键到指定元素
2、动作链基本操作
driver = webdriver.Chrome()
actions = ActionChains(driver) # 实例化一个动作链
# 执行动作链动作点击→鼠标移动到指定元素→释放鼠标→执行
action.click(on_element=None).move_to_element(to_element).release(on_element=None).perform()
# 分开执行
action.click(on_element=None)
action.move_to_element(to_element)
action.release(on_element=None)
action.perform()
3、例:通过ActionChains实现drag_and_drop()动作
def drag_drop(self): self.driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable") iframe = self.driver.find_element_by_id("iframeResult") self.driver.switch_to.frame(iframe) # 源元素 - 开始 source_element = self.driver.find_element_by_id("draggable") # 目标元素 - 结束 target_element = self.driver.find_element_by_id("droppable") # 定义动作链 actions = ActionChains(self.driver) # 实例化一个动作链,参数是driver,意味着driver要做动作链中动作 # 定义动作链中的动作 actions.drag_and_drop(source_element,target_element) # 执行动作 actions.perform() self.driver.switch_to.alert.accept() # 切换到alert,点击确定
7、元素等待
1、 强制等待(让整个执行程序等待50s)
time.sleep(50)
2、隐式等待(让driver等待10s)
driver.implicitly_wait(10)
3、显示等待(刷新频率为1次/s,超过10s没有发现就为超时)
my_wait = WebDriverWait(driver, timeout=10, poll_frequency=1) # 实例化一个显示等待
my_wait.until(EC.visibility_of_element_located(locator)) # 知道指定locator出现,停止等待
8、Cookie/Session
# 采用driver.get_cookie(),获取当前生成的sessionid,采用driver.get_cookies()获取浏览器存储的所有sessionid,
# 前者是字典存储着有关sessionid的多个键值对,后者是列表嵌套字典,包含多个cookie的信息。
def get_session():
driver = webdriver.Chrome() driver.get(url) driver.find_element_by_name("userName").send_keys("username") driver.find_element_by_name("userPass").send_keys("password") driver.find_element_by_class_name("btn.btn-primary.btn-block.btn-save").click() s_id = driver.get_cookie("JSESSIONID") # 将获取到的sessionid写入文件中
with open("cookie",mode="w") as wf: contents = json.dumps(s_id) wf.write(contents)
def driver_add_session(): # 注意,添加之前需要先打开浏览器和url driver = webdriver.Chrome()
driver.get(url) driver.delete_all_cookies() # 删除所有的原来cookies with open("cookie") as rf: session_dict = json.load(rf) # 字符串转字典 self.driver.add_cookie(session_dict) # 注意session必须是一个字典格式 self.driver.get(url/login) # 直接登录成功
9、通过JS解决问题
# 在Selenium中有一个专门执行JS代码的API,execute_script(js),通过return 可以返回执行结果的返回值 1、滚动条回到底部 js="document.getElementById('id').scrollTop=0" driver.execute_script(js) js1 = "document.documentElement.scrollTop=0" driver.execute_script(js) 2、滚动条划到底部 js="document.getElementById('id').scrollTop=10000" driver.execute_script(js) js1 = "document.documentElement.scrollTop=10000" driver.execute_script(js) 3、滚动条拉到指定位置 target = driver.find_element_by_id("xxx") driver.excute("arguments[0].scrollIntoView();", target) 4、scrollTo函数 # --scrollHeight 获取对象的滚动高度。 # --scrollLeft 设置或获取位于对象左边界和窗口中目前可见内容的最左 端之间的距离。 # --scrollTop 设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离。 # --scrollWidth 获取对象的滚动宽度。 #滚动到底部 js = "window.scrollTo(0,document.body.scrollHeight)" driver.execute_script(js) # 滚动到顶部 js = "window.scrollTo(0,0)" driver.execute_script(js)
三、Appium
1、概述
Appium 是一个开源工具,用于自动化 iOS 手机、 Android 手机和 Windows 桌面平台上的原生、移动 Web 和混合应用。「原生应用」指那些用 iOS、Android 或者 Windows SDKs 编写的应用。「移动 Web 应用」是用移动端浏览器访问的应用( Appium 支持 iOS 上的 Safari 、Chrome 和 Android 上的内置浏览器)。「混合应用」带有一个「webview」的包装器——用来和 Web 内容交互的原生控件。类似于 Apache Cordova 项目,创建一个混合应用使得用 Web 技术开发然后打包进原生包装器创建一个混合应用变得容易了。重要的是,Appium 是跨平台的:它允许你用同样的API 对多平台(iOS、Android、Windows)写测试。做到在 iOS、Android 和 Windows 测试套件之间复用代码。
2、原理
1) Client端:
一般来说就是运行代码的机器,即我们是用Java语言编写的代码,也可以用其他Selenium支持Python,ruby,C#等语言来编写,Appium提供的Appium-client API是Appium通过扩展Selenium的Webdriver协议而来的,我们编写代码的时只要实现Webdriver标准协议即可。
2)Appium Server:
Appium Server功能是监听接口,接收client端发送的command,然后将command转为移动端能够识别的command,然后发送给移动设备进行操作,再等待移动设备返回来的操作结果,将操作结果发送给client端。 Appium server是可以放在client端,也可以放在云端。 Appium server 默认的端口号是4723,用于Appium server监听client端的发送来的请求。
3) Android设备
Android端,Appium基于Webdriver协议,利用Bootstrap.jar,最后通过调用UIautomatior命令,实现APP的自动化测试(Android4.2以前的版本是用Instrumentation框架,通过绑定另外一个独立的selendroid项目来实现),Android4.2以后的版本是用UIautomator)。UIAutomator测试框架是Android SDK自带的APP UI自动化测试Java库,另外UIAutomator对H5支持有限,Appium引入了Chromedriver以及Safaridriver来实现H5的自动化。
参考链接:Appium工作原理 - 简书 (jianshu.com)
3、Appium的安装及相关环境的配置
# 1、Appium安装之前需要安装JDK环境,因为笔者已经安装,所以不赘述了。 # 2、Appium安装可以在官网下载,详见链接 # 3、python安装Appium库 pip install Appium-Python-Client -i https://mirrors.aliyun.com/pypi/simple/
4、Appium基本操作
Appium结合python完成移动端UI自动化过程为:(1)启动被测的设备和相关的app应用(2)连接设备到client(通过USB数据线或者无线网络连接,即使是使用无线连接也需要首先通过数据线连接),查看设备的连接信息和要测app的包名及主进程名。(3)启动Appium输入相关参数进行连接,连接之后可以进行相关自动化测试操作。
1)Appium界面

2)手机端设置
在进行appium连接的时候需要查看一下手机端的USB调试设置,打开允许通过USB进行点击操作、允许通过USB安装应用、USB默认设置中勾选MIDI模式。
3)Appium库安装,及通过python操作appium
Appium可以使用selenium中的内容来实现元素定位,同时还有自己的定位方式:driver.find_element_by_android_uiautomator()
# 1、python中安装Appium库
pip install Appium-Python-Client -i https://mirrors.aliyun.com/pypi/simple/
# 2、通过python连接移动端设备 from appium import webdriverclass AppDriver: def __init__(self): self.driver = self.get_driver def get_driver(self): desired_capabilities = { "platformName": "Android", "platformVersion": "10", "deviceName": "redmi", "udid": "b431bcd7" } driver = webdriver.Remote("http://172.16.5.54:4723/wd/hub", desired_capabilities) return driver # 打开手机中的博客园 def open_blog(self): self.get_driver().activate_app("com.cnblogs.xamarinandroid") # 3、driver.find_element_by_android_uiautomator()定位, 下面的内容为()中的参数 #匹配全部text文字: 'new UiSelector().text("手机号")'#包含text文字 : 'new UiSelector().textContains("机")'#以text什么开始: 'new UiSelector().textStartsWith("手")'#正则匹配:text 'new UiSelector().textMatches("^手.*")'#className: 'new UiSelector().className("android.widget.TextView")'# classNameMatches: 'new UiSelector().classNameMatches("^android.widget.*")'# resource-id、resourceIdMatches: 'new UiSelector().resourceId("com.syqy.wecash:id/et_content")'# description: 'new UiSelector().description("S 日历")'
# 匹配以什么开始
'new UiSelector().descriptionStartsWith("日历")' # 通过正则进行匹配 'new UiSelector().descriptionMatches(".*历$")'
# 4、uiaumator滑动 # 找到第一个可滚动元素,然后找到带有文本“ Tabs”的TextView。“标签”元素将滚动到视图中 self.driver.find_element_by_android_uiautomator('new UiScrollable(new UiSelector().scrollable(true).instance(0)).getChildByText(new UiSelector().className("android.widget.TextView"), "Tabs")') # scrollIntoView返回滚动到视图中的元素,scrollIntoView允许滚动到任何UiSelector,元素以text中的为准 self.driver.find_element_by_android_uiautomator('new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("WebView").instance(0));') # 5、多点触控 import time from appium import webdriver from appium.webdriver.common.touch_action import TouchAction from appium.webdriver.common.multi_action import MultiAction desired_caps = {} desired_caps['platformName'] = 'Android' #平台名称 desired_caps['deviceName'] = "aa" #设备名称 desired_caps['platformVersion'] = "9" #平台版本 desired_caps['appPackage'] = "com.google.android.apps.messaging" #包名 desired_caps['appActivity'] = "com.google.android.apps.messaging.ui.ConversationListActivity" #包入口 desired_caps['noReset'] = 'true' driver = webdriver .Remote('http://localhost:4723/wd/hub', desired_caps) #启动服务/通过4723端口来建立一个会话 def scr(): action1 = TouchAction(driver) #第一个手势 action2 = TouchAction(driver) #第二个手势 action3 = TouchAction(driver) #第三个手势 zoom_action = MultiAction(driver) action1.press(x = 300, y = 600).move_to(x = 300, y = 750).wait(200).release() action2.press(x = 400, y = 600).move_to(x = 400, y = 750).wait(200).release() action3.press(x = 500, y = 600).move_to(x = 500, y = 750).wait(200).release() zoom_action.add(action1,action2,action3) #加载 time.sleep(2) print("第%s次截图"%(i)) zoom_action.perform() #执行
if __name__ == '__main__': for i in range(100): #执行100次 scr()
四、win ui
使用win ui 要用到相关的插件应用:
使用uiautomation模块,相关的应用为:dotnet,pywin32,pywinauto
使用一个栗子展示相关的操作(非重点)
import uiautomation import os import subprocess # 通过subprocess创建一个线程 class TestNotepad: def __init__(self): subprocess.Popen("notepad.exe")
def notepad(self): # 找到Notepade - Class Name notepad_window = uiautomation.WindowControl(ClassName="Notepad") # 找editbox,输入内容 edit = notepad_window.EditControl() edit.SendKeys("祝大家国庆快乐") edit.SendKeys("\n") notepad_window.MenuItemControl(Name="文件(F)").Click() notepad_window.MenuItemControl(SubName="另存为(A)...").Click() save_window = notepad_window.WindowControl(Name="另存为") tree_item = save_window.TreeItemControl(Name="桌面") # 找到了第一个属性是树状的桌面 sub_tree_item = tree_item.TreeItemControl(Name="桌面") sub_tree_item.Click()
if __name__ == '__main__': mytest = TestNotepad() mytest.notepad()
expected_conditions

浙公网安备 33010602011771号