• 用户端APP自动化测试_L2


    目录:

    1. appium server 环境安装
    2. capability 进阶用法
    3. 元素定位工具
    4. 高级定位技巧-xpath 定位
    5. 高级定位技巧-css 定位与原生定位
    6. 特殊控件 toast 识别
    7. 显式等待高级使用
    8. 高级控件交互方法
    9. 设备交互api
    10. 模拟器控制
    11. 雪球财经app股票详情功能点自动化测试实战

    1.appium server 环境安装

    • 安装 Node.js
    • 安装 Appium Server
    • 安装 Appium Doctor

    安装 Node.js

    • 下载
    • 默认配置环境变量
    • 查看源
    • 更换源
    1. // 查看源
    2. npm config get registry
    3. //更换源
    4. npm config set registry https://registry.npm.taobao.org

    安装成功后的截图:

    安装 Appium Server

    • Appium Server 安装
    1. // 安装最新版本的appium server
    2. npm install -g appium
    3. // 注意 -g 参数一定要有,不能省,代表全局
    4. // 安装某个特定的版本
    5. npm install -g appium@1.19.1

     安装环境检测工具

    • 运行环境检测工具 appium-doctor
    1. # 安装检测工具 appium-doctor
    2. npm install -g appium-doctor
    • 执行检测命令 appium-doctor

    运行 Appium 服务

    1. // 帮助文档
    2. appium --help
    3. // 运行 appium 服务
    4. appium

    2.capability 进阶用法

    deviceName

    • 只是设备的名字,别名
    • 随便起
    • 不能锁定唯一一个设备

    uid

    • 多设备选择的时候,要指定 uid

    • 默认读取设备列表的第一个设备

    • 设备列表获取

     adb devices

    newCommandTimeout

    • appium 程序应等待来自客户端的新命令多长时间
    • 超时后会话删除
    • 默认 60 秒
    • 设置为 0 禁用

     autoGrantPermissions

    • 授予启动的应用程序某些权限
    • 默认为 false
    • 发生任何错误,强制服务器将实际的 XML 页面源转储到日志中.

     测试策略

    • noReset
    • fullReset
    • dontStopAppOnReset

    noReset

    • 默认为false
    • 安卓true
      • 不停止应用程序
      • 不清除应用程序数据
      • 不卸载 apk

     fullReset

    • 默认为 falsetrue:新会话之前完全卸载被测应用程序

    • 安卓

      • 在会话开始之前(appium 启动 app)和测试之后停止应用程序
      • 清除应用程序数据并卸载 apk

    dontStopAppOnReset

    • 默认为 false
    • 不希望应用程序在运行时重新启动,设置为 true

     #打开的app退出后重新启动

    adb shell am start -S 包名/activity名

    #打开的app不需要退出,直接使用当前页面

    adb shell am start 包名/activity名

    性能相关

    1. skipServerInstallation:这个选项可以跳过服务器的安装过程,如果在测试过程中不需要进行服务器安装,就可以使用这个选项来提高测试效率。
    2. skipDeviceInitialization:这个选项可以跳过设备的初始化过程,如果在测试过程中设备已经初始化过,就可以使用这个选项来避免重复初始化,提高测试效率。
    3. skipUnlock:这个选项可以跳过设备的解锁过程,如果在测试过程中设备已经解锁,就可以使用这个选项来避免重复解锁,提高测试效率。
    4. skipLogcatCapture:这个选项可以跳过日志的获取过程,如果在测试过程中不需要日志获取,就可以使用这个选项来提高测试效率。
    5. systemPort:这个选项可以获取系统的端口号,在测试过程中需要访问系统端口时使用。
    6. ignoreUnimportantViews:这个选项可以跳过不重要组件的获取过程,如果在测试过程中不需要获取这些组件的信息,就可以使用这个选项来提高测试效率。
    7. relaxed-security:这个选项可以在启动时设置放松的安全性,以允许某些应用程序在安全性检查中运行,从而提高测试效率。但是,这个选项可能会增加安全风险,应该谨慎使用。

    3.元素定位工具

    uiautomatorviewer 工具安装(不推荐)

    • 工具的安装:Android SDK 自带的界面分析工具
    • 打开 tools/bin 目录下的 uiautomatorviewer 程序

    uiautomatorviewer 工具功能介绍

    • 第一个是通过分析给定的文件定位
    • 第二个是将当前界面截图并分析xml结构
    • 第三个与第二个功能类似,但它会对页面内容进行压缩,导致一些控件定位不准确
    • 第四个是保存当前界面的截图以及xml结构

    weditor 安装与运行

    • 要求:python 3.6+ 以上
    • 安装:weditor 是 python 的第三方库
      • pip install weditor 进行安装
      • 运行:安装完成之后,在命令行运行 python -m weditor 即可

    weditor 功能介绍

    • 支持 Android 和 iOS 的界面分析
    • 通过设备的 uuid 连接设备
    • 展示页面结构

     appium inspector 安装与运行

    • 下载:下载链接
    • 运行 appium inspector
      • 运行 appium server
      • 本地的 adb 已连接设备
      • Desired Capabilities 参数填写
    • 点击 Start Session
    • 注意:在运行appium inspect 的时候需要检查一下weditor在手机上安装的ATX服务是否停止,因为ATX服务会占用uiautomator2的

    appium inspector 功能简介

    • 定位的模式相关
      • 原生app模式
      • 混合模式
    • 界面操作相关
      • 选择元素
      • 坐标滑动界面
      • 坐标点击元素
    • 其他功能
      • 返回
      • 刷新页面
      • 录制
      • 搜索元素
      • 复制 xml 结构

     对选中元素操作

    • 点击
    • 输入
    • 清空
    • 复制元素的属性
    • 获取元素的响应时间
    • 元素的属性

    4.高级定位技巧-xpath 定位

    xpath 函数

    包含-contains()

    • Xpath 表达式中的一个函数

    • contains()函数匹配==属性值==中包含的==字符串==

      //*[contains(@属性,"属性值")]

    xpath 轴

    父子-当前节点的父节点

    1. //*[@text="HK"]/..
    2. //*[@text="HK"]/parent::*

    父子-当前节点的子节点 

    //*[@resource-id="com.xueqiu.android:id/stock_layout"]/child::*

    爷孙-当前节点的爷爷

    1. //*[@text="HK"]/../..
    2. //*[@text="HK"]/parent::*/parent::*

    爷孙-当前节点的孙子

    //*[@resource-id="com.xueqiu.android:id/stock_layout"]/child::*/child::*

    祖先-ancestor

    • 返回当前节点的所有祖先

      //*[@text="HK"]/ancestor::android.widget.RelativeLayout
    • 显式指定要返回的祖先

      //*[@text="HK"]/ancestor::android.widget.RelativeLayout[1]

     兄弟姐妹-sibling

    • 节点后的兄弟姐妹节点,following-sibling
    • 节点前的兄弟姐妹节点,preceding-sibling

    following-sibling

    • 选择当前节点之后的所有兄弟节点

    • 节点后有一个兄弟节点

      //*[@text="HK"]/following-sibling::*
    • 节点后有多个兄弟节点

      //*[@resource-id="com.xueqiu.android:id/stock_layout"]/following-sibling::*[@resource-id="com.xueqiu.android:id/price_layout

     preceding-sibling

    • 选择当前节点之前的所有兄弟节点

    • 节点前有一个兄弟节点

      //*[@text="09988"]/preceding-sibling::*
    • 节点前有多个兄弟节点

      //*[@resource-id="com.xueqiu.android:id/add_attention"]/preceding-sibling::*[@resource-id="com.xueqiu.android:id/price_layou

    XPath 运算符

     AND

    • 可以在 XPath 表达式中放置 2 个条件

    • 在 AND 两个条件都应该为真的情况下,才能找到元素

      //*[@resource-id="com.xueqiu.android:id/current_price" and @text="107.8"]

    OR

    • 可以在 XPath 表达式中放置 2 个条件

    • 在 OR 的情况下,两个条件中的任何一个为真,就可找到元素。

    • OR 定位获取的是并集

      //*[@resource-id="com.xueqiu.android:id/tv_stock_add_follow" or @text="加自选"]

    5.高级定位技巧-css 定位与原生定位

    原生定位

    1. # ID 定位
    2. driver.find_element_by_android_uiautomator('new UiSelector().resourceId("")')
    3. # 组合定位
    4. driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/tab_name").text("我的")')

     css selector 定位介绍

    • 官网说明
    • Android: Appium Server 版本 >= 1.19.0
    • iOS:Appium Server>= 1.21.0
    1. 代码:
    2. driver.find_element(AppiumBy.CSS_SELECTOR,"#com\.xueqiu\.android\:id\/tv_search")
    3. 解析前:
    4. {"using":"css selector","value":"#com\\.xueqiu\\.android\\:id\\/tv_search"}
    5. 解析后:
    6. {"strategy":"-android uiautomator","selector":"new UiSelector().resourceId(\"com.xueqiu.android:id/tv_search\")",...}

     css selector 用法

    1. # 通过 id
    2. elementById("someResourceID")`
    3. -> `elementsByCss("#someResourceID")
    4. # 通过 class
    5. elementsByClassName("android.widget.TextView")`
    6. -> `elementsByCss("android.widget.TextView")
    7. # 通过 accessibility id
    8. elementsByAccessibilityId("Some Content Description")`
    9. -> `elementsByCss('*[description="Some Content Description"]')
    10. # 通过 xpath
    11. elementsByXpath("//android.widget.TextView[@description='Accessibility']")`
    12. -> `elementsByCss("android.widget.TextView[description='Accessibility']")

    iOS css selector 定位

    • Appium Server 版本>=1.21.0
    • 官网:https://github.com/appium/appium/releases/tag/v1.21.0

    总结

    • Appium Server 版本>=1.21.0
    • css selector 会转化为 Android/iOS 原生定位的定位策略
    • Android 转为 Android Uiautomator 定位方式
    • iOS 转为 class chain 定位方式

    6.特殊控件 toast 识别

    Toast 是什么

    • 一种消息框类型

    • 永远不会获得焦点,无法被点击

    • Toast显示的时间有限,Toast会根据用户设置的显示时间后自动消失

    • 是系统级别的控件,属于系统settings

    • Toast类的思想:就是尽可能不引人注意,同时还向用户显示信息,希望他们看到

    Toast 定位

    • appium 用的是uiautomator底层来抓取toast,
    • 再把toast放到控件树内,但是它本身不属于空间
    • 使用的是uiautomator2

     Toast 定位

    • xpath 可以找到 ``` //*[@class="android.widget.Toast"]

      //*[contains(@text,"xxx")] ``` xxx:toast的文本内容

      1. driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(50));
      2. driver.findElement(AppiumBy.xpath("//*[@class=\"android.widget.Toast\"]"));

    代码示例:

    1. from appium import webdriver
    2. from appium.webdriver.common.mobileby import MobileBy
    3. class TestToast():
    4. def setup(self):
    5. desire = {
    6. 'platformName': 'android',
    7. 'platformVersion': '6.0',
    8. 'deviceName': 'emulator-5554',
    9. 'appPackage': 'io.appium.android.apis',
    10. 'appActivity': 'io.appium.android.apis.view.PopupMenu1',
    11. 'automationName': 'uiautomator2'
    12. }
    13. self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire)
    14. self.driver.implicitly_wait(5)
    15. def teardown(self):
    16. self.driver.quit()
    17. def test_toast(self):
    18. self.driver.find_element(MobileBy.ACCESSIBILITY_ID, "Make a Popup!").click()
    19. self.driver.find_element(MobileBy.XPATH, "//*[@text='Search']").click()
    20. print(self.driver.find_element(MobileBy.XPATH, "//*[@class='android.widget.Toast']").text)
    21. # print(self.driver.find_element(MobileBy.XPATH, "//*[contains(@text, 'Clicked popup')]").text)

    7.显式等待高级使用

    Wait 等待

    • 强制等待:sleep 不推荐
    • 全局隐式等待
      • 在服务端等待
      • driver.implicitly_wait(TIMEOUT)
    • 显式等待
      • 在客户端等待
      • WebDriverWait(self.driver,10,0.5).until(expected_conditions.visibility_of_element_located(LOCATOR))

    显式等待

    • 显式等待
      • 显示等待与隐式等待相对,显示等待必须在每个需要等待的元素前面进行声明。
      • 是针对于某个特定的元素设置的等待时间,在设置时间内,默认每隔一段时间检测一次当前页面某个元素是否存在,
      • 如果在规定的时间内找到了元素,则直接执行,即找到元素就执行相关操作,
      • 如果超过设置时间检测不到则抛出异常。默认检测频率为 0.5s,默认抛出异常为:NoSuchElementException
    • 显示等待用到的两个类:
      • WebDriverWait 和 expected_conditions 两个类

    显式等待

    • 显式等待可以等待动态加载的 ajax 元素,显式等待需要使 ExpectedCondtions 来检查条件
    • 一般页面上元素的呈现
      • title 出现 首先出现 title
      • dom 树出现 presence ,还不完整
      • css 出现 (可见 visibility)
      • js 出现,js 特效执行 ( 可点击 clickable)
    • html 文档是自上而下加载的,
    • js 文件加载会阻塞 Html 内容的加载,有些 JS 异步加载的方式来完成 js 的加载
    • 样式表下载完成之后会跟之前的样式表一起进行解析,会对之前的元素重新渲染

    WebDriverWait 用法

    • WebDriverWait 用法
      • WebDriverWait(driver,timeout,poll_frequency=0.5,ignored_exceptions=None)
      • driver:浏览器驱动
      • timeout:最长超时时间,默认以秒为单位
      • poll_frequency:检测的间隔步长,默认为 0.5s
      • ignored_exceptions:超时后的抛出的异常信息,默认抛出 NoSuchElementExeception 异常。
    • WebDriverWait 的 until()和 until_not()方法:
      • method: 在等待期间,每隔一段时间(init中的 poll_frequency)调用这个传入的方法,直到返回值不是 False , message: 如果超时,抛出 TimeoutException,将 message 传入异常
      • until_not: 与 until 相反,until 是当某元素出现或什么条件成立则继续执行,until_not 是当某元素消失或什么条件不成立则继续执行,参数也相同。

    expected_conditions 类

    • presence_of_element_located 判断元素是否被加到了 DOM 树里,并不代表该元素一定可见
      • 用法:WebDriverWait().until(expected_conditions.presence_of_element_located(元素对象))
    • visibility_of_element_located 判断某个元素是否可见,可见代表元素非隐藏,并且元素的宽和高都不等于 0
      • 用法:
      • WebDriverWait().until(expected_conditions.visibility_of_element_located(元素定位符))

    使用 lambda 表达式

    • WebDriverWait(driver,time).until(lambda x:x.find_element_by_id("someId")
    • 返回一个元素

    总结三种等待方式

    • 隐式等待,尽量默认都加上,时间限定在 3-6s,不要太长, 为了所有的 find_element 方法都有一个很好的缓冲
    • 显式等待,用来处理隐式等待无法解决的一些问题,比如:文件上传(可以设置长一点),文件上传需要设置 20s 以上, 但是如果设置隐式等待, 它会在每个 find 方法都等这么长时间 , 一旦发现没有找到元素, 就会等 20s 以后才抛出异常, 影响 case 的执行效率,这时候就需要用显式等待,显式等待可以设置的长一点
    • 强制等待:一般不推荐,前两种基本能解决绝大部分问题,如果某个控件没有任何特征,只能强制等待,这种情况比较少

    8.高级控件交互方法

    Actions

    • Actions
    • 执行一系列或多个键盘和指针(触摸、鼠标、触控笔)操作链
    • w3c

    W3C 事件流 

    用法

    • 定义 ActionChains 实例
    • 定义输入源
    • 定义动作

     代码

    1. ...
    2. # 定义ActionChains 实例
    3. actions = ActionChains(driver)
    4. # 第一步:定义输入源
    5. # ActionChains里有个属性是ActionBuilder类型的, 使用的就是w3c协议
    6. # 可以定义鼠标指针源,键盘源,滚轮源事件
    7. actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
    8. # 第二步:定义动作
    9. # 移动到起点-> 按下-> 滑动-> 抬起
    10. actions.w3c_actions.pointer_action.move_to_location(115, 183)
    11. actions.w3c_actions.pointer_action.pointer_down()
    12. actions.w3c_actions.pointer_action.move_to_location(362, 179)
    13. actions.w3c_actions.pointer_action.release()
    14. actions.perform()
    15. ...

    练习

    • 安装手势密码锁 app(TouchAction.apk)
    • 打开应用
    • 点击【设置手势】
    • 完成手势操作(如图)

     

    代码示例:

    1. import time
    2. from appium import webdriver
    3. from appium.webdriver.common.appiumby import AppiumBy
    4. from selenium.webdriver import ActionChains
    5. from selenium.webdriver.common.actions import interaction
    6. from selenium.webdriver.common.actions.action_builder import ActionBuilder
    7. from selenium.webdriver.common.actions.pointer_input import PointerInput
    8. class TestAction:
    9. def setup(self):
    10. caps = {}
    11. caps["platformName"] = "Android"
    12. caps["appPackage"] = "cn.kmob.screenfingermovelock"
    13. caps["appActivity"] = "com.samsung.ui.FlashActivity"
    14. caps["deviceName"] = "127.0.0.1:7555"
    15. caps["ensureWebviewsHavePages"] = True
    16. caps["noReset"] = "true"
    17. self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
    18. self.driver.implicitly_wait(15)
    19. def teardown(self):
    20. self.driver.quit()
    21. def test_action(self):
    22. el1 = self.driver.find_element(AppiumBy.ID,"cn.kmob.screenfingermovelock:id/patternTxt")
    23. el1.click()
    24. # 定义ActionChains 实例
    25. actions = ActionChains(self.driver)
    26. # 第一步:定义输入源
    27. # ActionChains里有个属性是ActionBuilder类型的, 使用的就是w3c协议
    28. # 可以定义鼠标指针源,键盘源,滚轮源事件
    29. actions.w3c_actions = ActionBuilder(self.driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
    30. # 第二步:定义动作
    31. # 移动到起点-> 按下-> 滑动-> 抬起
    32. actions.w3c_actions.pointer_action.move_to_location(118, 176)
    33. actions.w3c_actions.pointer_action.pointer_down()
    34. actions.w3c_actions.pointer_action.move_to_location(355, 176)
    35. actions.w3c_actions.pointer_action.pause(0.5)
    36. actions.w3c_actions.pointer_action.move_to_location(751, 221)
    37. actions.w3c_actions.pointer_action.pause(0.5)
    38. actions.w3c_actions.pointer_action.move_to_location(752, 518)
    39. actions.w3c_actions.pointer_action.pause(0.5)
    40. actions.w3c_actions.pointer_action.move_to_location(751, 821)
    41. actions.w3c_actions.pointer_action.release()
    42. actions.perform()
    43. time.sleep(2)

    9.设备交互api

    常用的设备交互命令(建议去appium官网查看文档)

    • 模拟电话、短信
    • 网络模式
    • 横竖屏切换
    • App处理
    • 录屏

    模拟电话、短信

    • appium可以模拟来电话,来短信功能,在app运行过程中收到短信/电话,app如何做处理的,专属的一些场景
    • 只支持原生模拟器,不支持mumu,genimotion等
    1. driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.CALL);
    2. driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.ACCEPT);
    3. driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.CANCEL);
    4. driver.sendSMS("555-123-4567", “Appium Test”);

    网络设置

    1. def set_network_connection(self, connection_type: int) -> int:
    2. """Sets the network connection type. Android only.
    3. Possible values:
    4. +--------------------+------+------+---------------+
    5. | Value (Alias) | Data | Wifi | Airplane Mode |
    6. +====================+======+======+===============+
    7. | 0 (None) | 0 | 0 | 0 |
    8. +--------------------+------+------+---------------+
    9. | 1 (Airplane Mode) | 0 | 0 | 1 |
    10. +--------------------+------+------+---------------+
    11. | 2 (Wifi only) | 0 | 1 | 0 |
    12. +--------------------+------+------+---------------+
    13. | 4 (Data only) | 1 | 0 | 0 |
    14. +--------------------+------+------+---------------+
    15. | 6 (All network on) | 1 | 1 | 0 |
    16. +--------------------+------+------+---------------+
    1. self.driver.set_network_connection(1)
    2. self.driver.set_network_connection(4)

    横竖屏切换

    • 横竖屏切换,官方暂不支持python,以下是java代码。
    • 切换成横屏
      • driver.rotate(Screenorientation.LANDSCAPE)
    • 切换成竖屏
      • driver.rotate(Screenorientation.PORTRAIT)

     获取日志

    1. self.driver.log_types
    2. self.driver.get_log("logcat")

    其它常用操作

    • 锁屏
      • driver.lock()
    • 截图
      • driver.get_screenshot_as_file('./photos/img.png')
    • 录屏:模拟器需要 androidAPI>27,华为不支持,只支持 8.0以上的版本
      • 开始录制:self.driver.start_recording_screen()
      • 结束录制:self.driver.stop_recording_screen()

     代码示例:

    1. import time
    2. from appium import webdriver
    3. from appium.webdriver.extensions.android.gsm import GsmCallActions
    4. class TestDevice:
    5. def setup(self):
    6. caps = {}
    7. caps["platformName"] = "Android"
    8. caps['platformVersion'] = '6.0'
    9. caps["appPackage"] = "cn.kmob.screenfingermovelock"
    10. caps["appActivity"] = "com.samsung.ui.FlashActivity"
    11. caps["deviceName"] = "127.0.0.1:7555"
    12. caps["ensureWebviewsHavePages"] = True
    13. caps["noReset"] = "true"
    14. caps['unicodeKeyBoard'] = 'true'
    15. caps['resetKeyBoard'] = 'true'
    16. self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
    17. self.driver.implicitly_wait(15)
    18. def teardown(self):
    19. self.driver.quit()
    20. def test_mobile(self):
    21. self.driver.make_gsm_call('15033657989', GsmCallActions.CALL)
    22. self.driver.send_sms('15033657956', 'hello appium api')
    23. time.sleep(5)
    24. self.driver.set_network_connection(1)
    25. time.sleep(2)
    26. self.driver.set_network_connection(4)
    27. time.sleep(2)
    28. self.driver.get_screenshot_as_file('./datas/screenshot/img.png')

    10.模拟器控制

    android 模拟器创建

    • Android Studio
    • 在命令行启动模拟器
      • emulator -list-avds 模拟器列表
      • emulator ‘@foo’ or ‘-avd foo’

    配置

    • desirecap里面需要配置
      • avd: ‘模拟器名’
    • 注意自动启动模拟器,只能是sdk的模拟器,第三方模拟器不支持,7.0不支持

    代码示例:

    test_simulator_control.py

    1. import time
    2. from appium import webdriver
    3. class TestControl:
    4. def setup(self):
    5. caps = {}
    6. caps["platformName"] = "Android"
    7. caps['platformVersion'] = '6.0'
    8. caps["appPackage"] = "cn.kmob.screenfingermovelock"
    9. caps["appActivity"] = "com.samsung.ui.FlashActivity"
    10. caps["deviceName"] = "127.0.0.1:7555"
    11. caps["ensureWebviewsHavePages"] = True
    12. caps['unicodeKeyBoard'] = 'true'
    13. caps['resetKeyBoard'] = 'true'
    14. caps["noReset"] = "true"
    15. caps['avd'] = 'Resizable_Experimental_API_34'
    16. self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
    17. self.driver.implicitly_wait(15)
    18. def teardown(self):
    19. self.driver.quit()
    20. def test_control(self):
    21. self.driver.set_network_connection(1)
    22. time.sleep(2)
    23. self.driver.set_network_connection(4)
    24. time.sleep(2)
    25. self.driver.get_screenshot_as_file('./datas/screenshot/img.png')

     11.雪球财经app股票详情功能点自动化测试实战

    产品分析

    • 被测应用:雪球 App
    • 雪球 App 介绍
      • 雪球 app 是一款免费版,非常实用的股票平台,提供热点资讯内容和实时行情,提供了证券交流交易,公募、理财服务
      • 雪球 app 功能包括搜索股票,查看行情,交易,浏览热门文章,发帖,登录,注册,等功能

    Page Object 模式六大原则

    总结

    • 公共方法表示该页提供的服务。
    • 尽量不要暴露页面的内部
    • 一般不要断言
    • 方法返回其他页面对象
    • 不需要表示整个页面
    • 同一行动的不同结果用不同的方法建模

    用例设计

    • 搜索场景
      • 打开【雪球】应用首页
      • 点击搜索框,进入搜索页面
      • 向搜索输入框中输入【alibaba】
      • 点击搜索结果中的【阿里巴巴】
      • 切换到 tab 的【股票】
      • 找到 股票【阿里巴巴】的股票价格 price
      • 判断 price 在 110 上下 10%浮动

    实战练习思路

     

    优化用例-封装样板代码(基类)

    • 问题:
      • 直接调用 Appium API,导致存在大量的样板代码,find、finds 等。
    • 解决方案:
      • 常用的 UI 操作封装在 base_page 中。
      • 日志
      • 截图
      • page_source

     优化用例-页面元素提取

    • 问题:
      • 页面定位写在每个 Page 的方法中,如果此页面定位存在多处复用,那么需要多处修改
    • 解决方案:
      • 将页面定位抽离为私有类变量,符合六大原则中不要暴露页面内部的元素给外部

    优化用例-复用driver

    • 问题
      • 当执行多条测试用例时,每次执行用例都创建一个driver,浪费资源,效率低
      • 当执行多条测试用例时,所有的用例都使用同一个driver ,一旦服务出现问题(比如socket hang up)服务宕了,会导致后面所有的用例执行失败
    • 解决方案
      • 当创建driver 时,做个判断,如果driver 为None,则创建,否则,复用之前的driver

    代码示例:

    base/base_page.py

    1. from appium.webdriver.webdriver import WebDriver
    2. class BasePage:
    3. def __init__(self, driver: WebDriver = None):
    4. self.driver = driver
    5. def find(self, by, locator):
    6. return self.driver.find_element(by, locator)
    7. def find_and_click(self, by, locator):
    8. self.find(by, locator).click()
    9. def find_and_send(self, by, locator, text):
    10. self.find(by, locator).send_keys(text)
    11. def find_and_gettext(self, by, locator):
    12. return self.find(by, locator).text

    base/xueqiu_app.py

    1. from appium import webdriver
    2. from app_automation_testing.test_xueqiu_po.base.base_page import BasePage
    3. class XueQiuApp(BasePage):
    4. def start(self):
    5. if self.driver == None:
    6. print("driver == None")
    7. caps = {}
    8. caps["platformName"] = "Android"
    9. caps['platformVersion'] = '6.0'
    10. caps["appPackage"] = "com.xueqiu.android"
    11. caps["appActivity"] = ".view.WelcomeActivityAlias"
    12. caps["deviceName"] = "127.0.0.1:7555 device"
    13. caps["noReset"] = "true"
    14. self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
    15. self.driver.implicitly_wait(10)
    16. else:
    17. print("复用driver")
    18. self.driver.launch_app()
    19. return self
    20. def restart(self):
    21. pass
    22. def stop(self):
    23. self.driver.quit()
    24. def goto_main(self):
    25. from app_automation_testing.test_xueqiu_po.page.main_page import MainPage
    26. return MainPage(self.driver)

    page/main_page.py

    1. from appium.webdriver.common.appiumby import AppiumBy
    2. from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
    3. class MainPage(XueQiuApp):
    4. _search_box_element = (AppiumBy.ID, "com.xueqiu.android:id/tv_search")
    5. def click_search(self):
    6. self.find_and_click(*self._search_box_element)
    7. from app_automation_testing.test_xueqiu_po.page.search_page import SearchPage
    8. return SearchPage(self.driver)

    page/search_page.py

    1. from appium.webdriver.common.appiumby import AppiumBy
    2. from appium.webdriver.webdriver import WebDriver
    3. from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
    4. class SearchPage(XueQiuApp):
    5. def input_searchcentent(self, searchkey):
    6. self.find_and_send(AppiumBy.ID, "com.xueqiu.android:id/search_input_text", searchkey)
    7. from app_automation_testing.test_xueqiu_po.page.search_result_page import SearchResultPage
    8. return self
    9. def click_searchresult(self, text):
    10. self.find_and_click(AppiumBy.XPATH, f'//*[@text="{text}"]')
    11. from app_automation_testing.test_xueqiu_po.page.search_result_page import SearchResultPage
    12. return SearchResultPage(self.driver)

    page/search_result_page.py

    1. from appium.webdriver.common.appiumby import AppiumBy
    2. from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
    3. class SearchResultPage(XueQiuApp):
    4. def goto_stock_tab(self):
    5. self.find_and_click(AppiumBy.XPATH, '//*[@text="股票"]')
    6. return self
    7. def get_price(self):
    8. current_price = self.find_and_gettext(AppiumBy.XPATH,
    9. "//*[@text='BABA']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']")
    10. return float(current_price)

    case/test_xueqiu.py

    1. from hamcrest import assert_that, close_to
    2. from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
    3. class TestXueQiu:
    4. def setup_class(self):
    5. self.xueqiuapp = XueQiuApp()
    6. def setup(self):
    7. self.main = self.xueqiuapp.start().goto_main()
    8. def teardown_class(self):
    9. self.xueqiuapp.stop()
    10. def test_search(self):
    11. search_key = "alibaba"
    12. search_result = 'BABA'
    13. stock_price = self.main.click_search(). \
    14. input_searchcentent(search_key). \
    15. click_searchresult(search_result). \
    16. goto_stock_tab(). \
    17. get_price()
    18. assert_that(stock_price, close_to(110, 110 * 0.5))
    19. def test_search1(self):
    20. search_key = "alibaba"
    21. search_result = 'BABA'
    22. stock_price = self.main.click_search(). \
    23. input_searchcentent(search_key). \
    24. click_searchresult(search_result). \
    25. goto_stock_tab(). \
    26. get_price()
    27. assert_that(stock_price, close_to(110, 110 * 0.5))

  • 相关阅读:
    04、Java数组
    LightDB-A 兼容oracle支持mod操作符
    vue 使用webpack打包,出现路径404 ,导致白屏&webpack-bundle-analyzer使用
    Ubuntu 22.04 MySQL安装并设置远程访问
    海底捞逃离小县城
    Java学习笔记3.11.1 垃圾回收 - 垃圾回收的作用
    vue2 打印数据 以及使用 (2)
    2019银川icpc K 思维,悬线法dp
    Webpack 打包 commonjs 和 esmodule 动态引入模块的产物对比
    【力扣题解】2139. 得到目标值的最少行动次数
  • 原文地址:https://blog.csdn.net/qq_56444564/article/details/132596688