iOS App automated testing: from environment construction to actual application (Python+pytest+appium+allure framework)

iOS App automated testing: Python+pytest+appium+allure

1. Environment preparation:

1.1 Appium environment construction

  1. Windows side:Windows side appium environment construction
  2. Mac side:Mac side appium environment construction

Summary: appium-doctor is used to check whether the environment configuration is complete. After the installation is complete, reopen a new command line window and enter the command appium-doctor. Follow the prompts to install missing content.

1.2 Automated testing dependency tools: Install WebDriverAgent on Mac

Reference article:[iOS] WebDriverAgent configuration-Build-Test whole process
(Developer account can be purchased by yourself, or you can find the company iOS Borrowed by the development team)

1.3 iOS App element identification tool: Appium Inspector

  1. Download from the official website:appium inspector official website, and the official website has instructions for use
  2. How to use appium inspector

1. Start Appium Server, set the Host address to 127.0.0.1, and the default Port is 4723 (can be changed);
2. Start the WebDriverAgent and stop running the same application. Python test script;
3. Connect the real machine/simulator and obtain the corresponding mobile phone parameters and test APP parameters. (To obtain the udid of the real iOS device and the bundle id of the application, you can use the toolpy-ios-device
4. Open the Appium inspector app, enter the parameters, and then Click Start Session.
note: Fill in Appium Server with the parameters we used to start the server before.
Fill in the phone parameters (and App parameters) in Desired Capabilities< /span>
appium inspector example
Get element

iOS app uses appium inspactor to locate the mobile parameters of elements
{
  "platformName": "ios",
  "appium:platformVersion": "16.0",
  "appium:bundleId": "com.zego.avatar",
  "appium:automationName": "XCUITest",
  "appium:xcodeOrgId": "xxxx",   # 填自己的开发者账号
  "appium:xcodeSigningId": "iPhone Developer",
  "appium:udid": "401730095f754dd313bbd91c0e000345ab8f638a",
  "appium:deviceName": "iPhone X"
}
Android app uses appium inspector to locate elements
{
  "appium:appPackage": "com.zego.goavatar",
  "appium:appActivity": "com.zego.goavatar.view.AvatarMainActivity",
  "platformName": "Android",
  "appium:deviceName": "YSE0221A12000677" 
}

2. Test cases

1. pytest testing framework organizes use cases

Please refer to:The difference between Python automated testing framework unittest and pytest

1.1 pytest use case writing rules:
  1. The test file name must start with "test_" or end with "_test" (such as test_avatarAPI_iOS.py)
  2. The test method must start with "test_"
  3. The test class name must start with "Test_"
  4. pytest inherits the unittest framework and is compatible with unittest use cases
1.2 Pre- and post-conditions
1.2.1 setup and teardown: pytest provides module-level, function-level, class-level, and method-level setup/teardown

For details, please refer to:pytest test case setup and teardown

  1. Module level (setup_mode/teardown_mode) starts at the beginning and end of the module and affects the whole world
  2. Function level (setup_function/teardown_function) only takes effect for function use cases (not in classes)
  3. Class level (setup_class/teardown_class) only runs before/after all use cases in the class (in the class)
  4. Method level (setup_method/teardown_method) only runs before/after each use case in the class (in the class)
  5. (setup/teardown) in the class runs before and after calling the method
1.2.2 Commonly used decorators in pytest
Parameterized decorator: @pytest.mark.parametrize()

For details, please refer to[email protected]()
Single parameter or multiple parameters (Cartesian product)< /span>

	# 定义
	@pytest.mark.parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None))
	# 单参数示例
	gender = [male,female]
    @allure.story("test_creatAvatar_male:创建默认的男性/女性形象")
    @pytest.mark.flaky(reruns=0, reruns_delay=3)  # 用例执行失败后,再重试2次,间隔3s
    @pytest.mark.parametrize('gender', gender)  # 性别:gender=[male,female]
    def test_createAvatar(self, gender):
Decorator: @pytest.mark.flaky(), automatically retry after the use case execution fails
# reruns:重试次数,reruns_delay:重试之间的最小间隔时间
@pytest.mark.flaky(reruns=2, reruns_delay=3)  # 用例执行失败后,再重试2次,间隔3s

2. Test cases

2.1 Initialization parameter description
# 导入webdriver
from appium import webdriver
# 初始化参数
desired_caps = {
    
    
    'platformName': 'Android',  # 被测手机平台:Android/iOS
    'platformVersion': '10',  # 手机安卓版本
    'deviceName': 'xxx',  # 设备名,安卓手机可以随意填写
    'appPackage': 'tv.danmaku.bili',  # 启动APP Package名称
    'appActivity': '.ui.splash.SplashActivity',  # 启动Activity名称
    'unicodeKeyboard': True,  # 使用自带输入法,输入中文时填True
    'resetKeyboard': True,  # 执行完程序恢复原来输入法
    'noReset': True,  # 不要重置App,如果为False的话,执行完脚本后,app的数据会清空,比如你原本登录了,执行完脚本后就退出登录了
    'newCommandTimeout': 6000,  # 超时时间可以设置久一些,太短,用例比较多会报错
    'automationName': 'UiAutomator2'  #  自动化测试引擎 ,默认Appium
}
# 连接Appium Server,初始化自动化环境
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
# 退出程序
driver.quit()
2.2 Example of initialization parameters
# 启动参数
iOS_caps = {
    
    
    'automationName': 'XCUITest',
    'bundleId': 'im.xxx.xxx',
    'platformName': 'iOS',
    'platformVersion': '16.0',
    'xcodeOrgId': 'xxxx',
    'xcodeSigningId': 'iPhone Developer',
    'newCommandTimeout': "1200000",   
    'udid': '401730095f754dd313bbd91c0e000345ab8f638a',
    'deviceName': 'iPhoneX'
}
2.3 appium operation elements

Reference article:Summary of appium operation elements
Appium’s iOS positioning elements mentioned: < /span> a> and ios_predicate is the most reliable to find elements. ios_predicate >> accessibility_id >> class_name >>xpath
According to the order speed of finding elements, the order from fastest to slow is as follows:

Appium's way of locating elements:
find_element_by_* has been deprecated, use find_element instead
(see WebDriver positioning element (official website), Appium Python Client (github)

from selenium.webdriver.common.by import By

text = self.device.find_element(AppiumBy.XPATH, value=self.output_xpath).text

class AppiumBy(By):  # 继承的By类
    IOS_PREDICATE = '-ios predicate string'
    IOS_UIAUTOMATION = '-ios uiautomation'
    IOS_CLASS_CHAIN = '-ios class chain'
    ANDROID_UIAUTOMATOR = '-android uiautomator'
    ANDROID_VIEWTAG = '-android viewtag'
    ANDROID_DATA_MATCHER = '-android datamatcher'
    ANDROID_VIEW_MATCHER = '-android viewmatcher'
    # Deprecated
    WINDOWS_UI_AUTOMATION = '-windows uiautomation'
    ACCESSIBILITY_ID = 'accessibility id'
    IMAGE = '-image'
    CUSTOM = '-custom'
定位方式:
class By:
    """
    Set of supported locator strategies.
    """
    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"
2.4 Operating elements

There are more click() and drag_and_drop(origin_el=eleA, destination_el=eleB) methods used in UI automated testing.
More methods are as follows:

# 元素,以IOS_PREDICATE为定位方式举例
element = self.device.find_element(AppiumBy.IOS_PREDICATE,value=self.RE.element('action_predicate')

# 1.点击元素
element.click()

# 2.清理元素,一般是清空输入框内容
element.clear()

# 3.向元素发送文本,一般是向输入框内输入内容
element.send_keys('hello')

# 4.提交
element.submit()

# 5.拖拽元素从A位置到B的位置
eleA = self.device.find_element(AppiumBy.XPATH,value=self.RE.element('shoes_xpath')
eleB = self.device.find_element(AppiumBy.XPATH,value=self.RE.element('cloth_xpath')
self.device.drag_and_drop(origin_el=eleA, destination_el=eleB)

# 6.通过坐标模拟手点击元素
self.device.tap([(100, 20), (100, 60), (100, 100)], 500)
# tap可参考:[appium点击坐标tap方法及封装](https://www.cnblogs.com/syw20170419/p/8192629.html)

# 7.截图1:get_screenshot_as_file(filename)参数filename为截图文件保存的绝对路径,图片为png格式,区域全屏幕,如:
self.device.get_screenshot_as_file('../picture/screenshot.png')

# 8.截图2:save_screenshot(filename)该方法与get_screenshot_as_file()不同的是,参数为文件名称,保存当前屏幕截图到当前脚本所在的文件,区域全屏幕,如:
self.device.save_screenshot('test_02.png')
2.5 Get element attributes
# 1.获取元素的text
element.text

# 2. 获取元素坐标,返回具有元素大小和位置的字典
element.rect

# 3.获取元素坐标(返回字典,x,y轴)
element.location

# 4.获取元素的size(返回字典)
element.size

# 5.获取元素的标签名称
element.tag_name

# 6.返回元素是否被启用
 element.is_enabled() 

# 7.返回元素是否可选择
 element.is_selected()

# 8.元素是否显示
element.is_displayed()

# 9.获取当前会话的活动元素
element = self.device.switch_to.active_element

# 10.获取元素属性
element.get_attribute(value)
# 说明:
# value='name' 返回content-desc或者text属性值,二者只能其一
# value='text'返回text的属性值
# value='className'返回class属性值,要API=>18(Android4.2.1以上即可)
# value='resourceId'返回resource-id的属性值,要API=>18(Android4.2.1以上即可)
2.6 Affirmations

In UI automated testing, you can assert whether a button exists, or take a screenshot and use the image similarity to assert whether the image meets expectations
(1) Assert whether a button exists:< /span>

# 如元素element
element = self.device.find_element(AppiumBy.IOS_PREDICATE,value=self.RE.element('action_predicate')
assert element

(2) Assert image similarity
Run the code first, obtain the screenshot as the negative, and use the screenshot during testing as the tested image, compare the negative and the tested image, and obtain the similarity n (The value range is 0~1), and then assert n.

Screenshot
Appium's screenshot method get_screenshot_as_file(filename) captures the entire screen. If you want to accurately capture a specified area, you can use the crop (box) method of PIL.Image, box Bit coordinate values ​​(x1, y1, x2, y2)

	from PIL import Image
	def screenshot(self, partName, pictureName):
        # 共用方法:根据坐标区域截图
        box = (0, 0, 0, 0)
        if partName == 'default':
            box = (0, 210, 1110, 1430)  # 区域1
        elif partName == 'hair':
            box = (110, 270, 950, 1145)  # 区域2
        self.device.get_screenshot_as_file(
            '../picture/screenshot.png')  # 路径和名称,图片是.png格式
        image = Image.open('../picture/screenshot.png')
        new_image = image.crop(box)
        new_image.save(pictureName)  # 截取精确区域后的图片

Image similarity
Please refer to:python OpenCV 5 image similarity algorithms, After testing, the "three histogram algorithm" is more suitable for my test Products
Python five image similarity comparison methods_Sakura's blog-CSDN blog_python image similarity

#! /usr/bin/env python3
# _*_ coding:utf-8 _*_
"""
@Auth : Dora
@Date : 2022/6/13 14:14
"""

import cv2
import numpy as np

# 三直方图算法
# 通过得到RGB每个通道的直方图来计算相似度
def classify_hist_with_split(image1, image2, size=(256, 256)):
    # 将图像resize后,分离为RGB三个通道,再计算每个通道的相似值
    image1 = cv2.resize(image1, size)
    image2 = cv2.resize(image2, size)
    sub_image1 = cv2.split(image1)
    sub_image2 = cv2.split(image2)
    sub_data = 0
    for im1, im2 in zip(sub_image1, sub_image2):
        sub_data += calculate(im1, im2)
    sub_data = sub_data / 3
    return sub_data

# 计算单通道的直方图的相似值
def calculate(image1, image2):
    hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0])
    hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0])
    # 计算直方图的重合度
    degree = 0
    for i in range(len(hist1)):
        if hist1[i] != hist2[i]:
            degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i]))
        else:
            degree = degree + 1
    degree = degree / len(hist1)
    return degree

def runHistHash(image1, image2):
    img1 = cv2.imread(image1)
    img2 = cv2.imread(image2)
    n = classify_hist_with_split(img1, img2)
    print('三直方图算法相似度:', n)
    return n

if __name__ == "__main__":
    image1 = '../picture/p11.png'
    image2 = '../picture/p22.png'
    runHistHash(image1, image2)

The assertion plug-in pytest-assume optimizes assertions. If the previous assertion fails, subsequent assertions can continue to be executed.

#!/usr/bin/env python3
#!coding:utf-8
import pytest
 
@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
    assert x == y  #如果这个断言失败,则后续都不会执行
    assert True
    assert False
 
@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_pytest_assume(x, y):
    pytest.assume(x == y) #即使这个断言失败,后续仍旧执行
    pytest.assume(True)
    pytest.assume(False)

3.Test report

You can refer to:Python automation-allure generates test reports,
Detailed explanation of the use of allure automated test reports< /span>

3.1 allure in organizational use cases

The format is as follows, the description content in brackets will be displayed in the test report


@allure.feature("Avartar API测试")  # 模块名称
class TestAvatarAPIiOS:
    @allure.story('创建默认的男性/女性形象')  # 用例名称,对用例的描述
    @allure.title('test_createAvatar')  # 用例标题
    def test_createAvatar(self):
        with allure.step('步骤1:打开应用'):  # 测试用例操作步骤
            print('应用已打开')
        with allure.step('步骤2:创建Avatar形象'):
            print('已生成Avatar形象')
        print('创建默认形象:创建成功')
3.2 Execute use cases, generate test reports and take screenshots
import os
import random
from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
import requests
import base64
import hashlib
from WorkWeixinRobot.work_weixin_robot import WWXRobot

# 企业微信群机器发消息
webhook = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d8243e49-47e3-4420-857e-7ffb01c866ec'  # No八哥 群 机器人url

wx_driver = WWXRobot(key=webhook)

def runCase_captureReport(runcmd, port):
    # 执行测试用例-生成报告-并给测试报告截图
    # 命令执行测试用例
    # runcmd : 执行测试用例的命令
    # port:指定的测试报告端口
    runcmd1 = 'pytest E:/pythonProject/GoAvatarUIAutoTest/testcase/test_GoAvatarUI.py::TestGoAvatar::test_gender_change -s -q --alluredir=../report/result --clean-alluredir'  # 指定单条用例
    # port = random.randint(1000, 9999)     # 生成随机端口
    reportcmd = 'allure serve ../report/result -h 192.168.55.183 -p {}'.format(port)  # 测试结果生成报告
    runcmd2 = 'pytest -v -k change E:/pythonProject/GoAvatarUIAutoTest/testcase/test_GoAvatarUI.py -s -q --alluredir=../report/result --clean-alluredir'  # 指定单条用例
    runcmdall = 'E:/pythonProject/GoAvatarUIAutoTest/pytest testcase/test_GoAvatarUI.py -s -q --alluredir=./report --clean-alluredir'

    rungo = os.popen(runcmd)
    runmsg = rungo.read()
    print(runmsg)
    sleep(3)
    reportrun = os.popen(reportcmd)

    # 生成allure测试报告并截图
    driver = webdriver.Chrome()
    report_url = "http://192.168.55.183:{}/index.html".format(port)  # 192.168.55.183 为电脑ip地址,{}为端口号
    print("测试报告链接:{}".format(report_url))
    driver.get(report_url)
    driver.maximize_window()  # 将浏览器最大化显示
    sleep(0.5)
    driver.get_screenshot_as_file('../report/reportOverview-{}.png'.format(port))  # 测试报告首页截图
    sleep(1)
    driver.find_element(By.XPATH, '//*[@id="content"]/div/div[1]/div/ul/li[6]/a/div').click()  # 测试报告详情页
    sleep(0.5)
    driver.find_element(By.XPATH,
                        '//*[@id="content"]/div/div[2]/div/div[1]/div/div[3]/div/div/div/div[1]/div[1]').click()  # 展开测试报告详情
    sleep(1)
    driver.get_screenshot_as_file('../report/reportPackages-{}.png'.format(port))  # 测试报告详情截图

QA: Problems and solutions encountered in building the appium environment

Windows side:

Q1. Permission issues are prompted during global installation
chown -R current user name /usr/local/lib/node_modules

Q2. The following 5 steps solve the problem that opencv4nodejs cannot be found
Install c-make first, then install opencv4nodejs
Install cmake and configure the environment variable https: //cmake.org/
Install git and configure environment variables
If the installation of opencv4nodejs fails, you can first set the mirror to Taobao, and then execute the install opencv4nodejs command a>
1. Set the mirror source
npm config set registry http://registry.npm.taobao.org
2. Run the command< /span> npm i -g opencv4nodejs 5. Run the command again export OPENCV4NODEJS_DISABLE_AUTOBUILD=1 4. Run the command git config --global http.postBuffer 524288000 3. Increase the git cache npm i -g npm@latest npm i opencv4nodejs --save
npm i -g npm@6







Q3.ffmpeg installation
Download the ffmpeg package, https://ffmpeg.zeranoe.com
After unzipping, go to D:\ffmpeg\bin Add environment variables (mine is D drive, write the full path of "\ffmpeg\bin" according to your own situation)

Q4. Solve the problem that bundletool.jar cannot be found
1. Download bundletool.jar: https://github.com/google/bundletool/releases
(Grant access permission to bundletool.jar)
2. Place it under D:\AndroidSDK\bundle-tools and rename it to bundletool.jar
Change bundletool Add the .jar path to the environment variable path
3. Add the .JAR to PATHEXT

Q5.解决 mjpeg-consumer cannot be found
安装:npm i -g mjpeg-consumer

Q6. Solve the problem that gst-launch-1.0.exe and/or gst-inspect-1.0.exe cannot be found
1. Manual download and installation, download address: https:// gstreamer.freedesktop.org/download/
Note: Both runtime installer and development installer applications must be downloaded and installed.
2. After the installation is complete, find the gstreamer path (the installation path cannot be selected during the installation process, and it will be installed to the disk where the installation package is located by default), and configure the Path system environment variable: such as: E:\gstreamer\ 1.0\mingw_x86_64\bin (bin directory of the installation path)

Common errors on Mac

Q1. Install opencv4nodejs
1. Find a place to create a new folder nodeOpencv, open the terminal cd and enter this folder.
2.⚠️Important⚠️, enter the following command and press Enter.
export OPENCV4NODEJS_DISABLE_AUTOBUILD=1
3. Then enter the following and wait for about 5, 6, 7, or 8 minutes to complete.
cnpm install --save opencv4nodejs

Q2. Solve the problem that idb and idb_companion are not installed
brew install facebook/fb/idb-companion
Some libraries fail to download, so execute them one by one. :brew install xx library

Guess you like

Origin blog.csdn.net/u012089395/article/details/130584116