Csdn's most detailed and powerful Ali's latest app automation test---Building an automated test framework

content

1. Install dependencies and the base directory of the project

2.pom parsing

3. Excel Data Driven

4. Test report

 5. Optimize conftest.py

 6. Multi-device connection, parallel execution of test code


  ​ Click me to get a full set of software testing (automated testing) video materials for free (note "csdn000")

1. Install dependencies and the base directory of the project

# Installation dependencies
pip install pytest      
pip install appium-python-client  
pip install openpyxl # excel file processing
pip install pytest-html # test report

2.pom parsing

The core idea of ​​pom design is to maintain different pages separately. In the process of automation, if the front-end page is changed, the original automation code may no longer be applicable, because after the front-end page is changed, the element positioning is no longer suitable. , the automation use case execution failed. It is more troublesome to change the code again.

pom encapsulates the page and the test case separately, each operation on the page is encapsulated separately, and the test case only needs to call the encapsulated method. If the page has changed. Just change the operations encapsulated in the page.

The following takes the login scenario as an example to write automation:

conftest.py

from appium import webdriver
import pytest
import os
chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),'drivers/chrome/75.0.3770.140/chromedriver.exe')


@pytest.fixture(scope='session')
def driver():
    desired_caps = {
        'platformName': 'Android',                    # 测试Android系统
        'platformVersion': '7.1.2',                   # Android版本 可以在手机的设置中关于手机查看
        'deviceName': '127.0.0.1:62001',              # adb devices 命令查看  设置为自己的设备
        'automationName': 'UiAutomator2',             # 自动化引擎
        'noReset': False,                             # 不要重置app的状态
        'fullReset': False,                           # 不要清理app的缓存数据
        'chromedriverExecutable': chromedriver,       # chromedriver 对应的绝对路径
        'appPackage': "org.cnodejs.android.md",       # 应用的包名
        'appActivity': ".ui.activity.LaunchActivity"  # 应用的活动页名称(appium会启动app的加载页)
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_capabilities=desired_caps)
    driver.implicitly_wait(5)  # 全局的隐式等待时间

    yield driver  # 将driver 传递出来

    driver.quit()
pom/basePage.py

"""
  所有的页面都继承这个类,获得driver
"""
import time
from appium.webdriver.webdriver import WebDriver
from selenium.common.exceptions import NoSuchElementException


class BasePage:
    def __init__(self,driver:WebDriver):
        self.driver = driver

    # 获取toast的文本值
    @property
    def result_text(self):
        try:
            toast = self.driver.find_element_by_xpath('//android.widget.Toast')
            return  toast.text
        except NoSuchElementException:
            return "找不到这个元素,请检查自己的自动化代码"
pom/loginPage.py

"""
  登陆页面
"""
import time

from appium.webdriver.webdriver import WebDriver

from pom.basePage import BasePage


class LoginPage(BasePage):

    # 初始化类的时候,打开登陆页面
    def __init__(self,driver:WebDriver):
        super(LoginPage,self).__init__(driver)
        # 判断是否是登陆页面
        current_activity = self.driver.current_activity
        if ".ui.activity.LoginActivity" in current_activity:
            pass
        else:
            # 不是登陆页面,则调用方法,打开登陆页面
            self.__go_login_page()


    # 导航到loginPage(登陆页面),定义一个私有的方法
    def __go_login_page(self):
        # 清空app的登陆状态(如果已经登陆,则去掉登陆状态)
        self.driver.reset()
        # 打开首页
        self.driver.start_activity(app_package='org.cnodejs.android.md',app_activity='.ui.activity.MainActivity')

        toggle_but = self.driver.find_element_by_android_uiautomator('resourceId("org.cnodejs.android.md:id/toolbar").childSelector(new UiSelector().className("android.widget.ImageButton"))')
        toggle_but.click()
        time.sleep(1)

        # 点击头像,去登陆页面
        avatar = self.driver.find_element_by_android_uiautomator('text("点击头像登录").resourceId("org.cnodejs.android.md:id/tv_login_name")')
        avatar.click()


    # 使用token的方式进行登录
    def with_token_login(self,token):
        self.driver.find_element_by_id('org.cnodejs.android.md:id/edt_access_token').send_keys(token)
        loginbtn = self.driver.find_element_by_android_uiautomator('text("登录").resourceId("org.cnodejs.android.md:id/btn_login")')
        # 点击登陆
        loginbtn.click()

    # 登陆失败的断言
    @property
    def with_token_failed_text(self):
        # 1. 截图
        ele = self.driver.find_element_by_id('org.cnodejs.android.md:id/edt_access_token')
        png = ele.screenshot_as_base64
        # 2. TODO 调用 ocr 图片识别 将图片中文字识别出来

        return ""

    # 扫码登陆
    def with_code_login(self):
        pass


    # 使用github登陆
    def with_github_login(self):
        pass
testcases/test_user.py

from pom.loginPage import LoginPage

# 登陆的测试用例
# 使用conftest.py 中定义的 driver
def test_login(driver):
    # 打开登录页面
    loginpage = LoginPage(driver)
    # 使用token进行登录
    loginpage.with_token_login('d1563473-1f0d-4307-9774-6c2ff49c93ab')

    # 登陆成功,验证totas文本值
    result = loginpage.result_text
    assert result == "登录成功"

Start appium, execute    pytest testcases\test_user.py -s -v   , check the running result:

(Successful landing)

 (failed to login)

The following takes the post login scenario as an example to write automation:

pom/homePage.py

"""
  首页
"""
from pom.basePage import BasePage
from pom.createTopicPage import CreateTopicPage


class HomePage(BasePage):

    def __init__(self,driver):
        super(HomePage,self).__init__(driver)
        # 判断一下,是否是首页
        if '.ui.activity.MainActivity' in self.driver.current_activity:
            pass
        else:
            self.__go_home_page()


    # 打开首页
    def __go_home_page(self):
        self.driver.start_activity(app_activity='.ui.activity.LaunchActivity')


    # 去发帖页面
    def go_create_topic(self):
        # 判断是否已经到达创建话题页面
        while not '.ui.activity.CreateTopicActivity' in self.driver.current_activity:
            # 再重新点击一下
            create_btn = self.driver.find_element_by_android_uiautomator(
                '.resourceId("org.cnodejs.android.md:id/fab_create_topic")')
            create_btn.click()

        return CreateTopicPage(self.driver)
pom/createTopicPage.py

"""
  发帖页面
"""
from pom.basePage import BasePage


class CreateTopicPage(BasePage):

    # 发布话题
    def create_new_topic(self,tab,title,content):
        # 选择类型
        spinner = self.driver.find_element_by_android_uiautomator('.resourceId("org.cnodejs.android.md:id/spn_tab")')
        spinner.click()
        tab_selcotor = f'.resourceId("android:id/text1").text("{tab}")'
        self.driver.find_element_by_android_uiautomator(tab_selcotor).click()

        # 输入标题
        title_content = self.driver.find_element_by_android_uiautomator(
            'resourceId("org.cnodejs.android.md:id/edt_title")')
        title_content.send_keys(title)

        # 输入内容
        content_area = self.driver.find_element_by_android_uiautomator(
            'resourceId("org.cnodejs.android.md:id/edt_content")')
        content_area.send_keys(content)

        # 点击发送
        send_btn = self.driver.find_element_by_android_uiautomator(
            'resourceId("org.cnodejs.android.md:id/action_send")')
        send_btn.click()
testcases/test_topics.py

from pom.homePage import HomePage
from pom.loginPage import LoginPage


# 发帖的测试用例
def test_create_topic(driver):
    loginpage = LoginPage(driver)
    # 用户登录成功
    loginpage.with_token_login('d1563473-1f0d-4307-9774-6c2ff49c93ab')

    # 首页打开
    hp = HomePage(driver)
    # 进入创建话题页面
    create_page = hp.go_create_topic()

    create_page.create_new_topic(tab='分享',title='123',content='哈哈哈哈哈哈')
    result = create_page.result_text
    # 根据发帖结果做断言
    assert result == "标题要求10字以上"
 
 

  ​ Click me to get a full set of software testing (automated testing) video materials for free (note "csdn000")

Start appium and execute    pytest testcases\test_topics.py -s -v   to view the running results:

You can also execute pytest, view login, and post the execution results of 2 test cases:

3.  Excel Data Driven

testdata/data.xlsx

utils/file_handler.py

"""
  登陆测试用例的数据驱动化测试
"""
import pytest

from pom.loginPage import LoginPage
from utils.file_handler import FileHandler

fl = FileHandler()
# 从Excel文件中获取数据
data = fl.get_data_by_sheet('用户登录')

class TestDdtLogin:

    @pytest.mark.parametrize('token,status,expect_val',data)
    def test_login(self,driver,token,status,expect_val):
        # 打开登录页面
        loginpage = LoginPage(driver)
        # 使用token进行登录
        loginpage.with_token_login(token)
        if status == '成功':
            # 登录成功, 验证toast的文本值为登录成功
            result = loginpage.result_text
            assert result == expect_val
        if status == "失败":
            result = loginpage.with_token_failed_text
            assert result == expect_val
testcases/test_ddt/test_ddt_login.py

"""
  登陆测试用例的数据驱动化测试
"""
import pytest

from pom.loginPage import LoginPage
from utils.file_handler import FileHandler

fl = FileHandler()
# 从Excel文件中获取数据
data = fl.get_data_by_sheet('用户登录')

class TestDdtLogin:

    @pytest.mark.parametrize('token,status,expect_val',data)
    def test_login(self,driver,token,status,expect_val):
        # 打开登录页面
        loginpage = LoginPage(driver)
        # 使用token进行登录
        loginpage.with_token_login(token)
        if status == '成功':
            # 登录成功, 验证toast的文本值为登录成功
            result = loginpage.result_text
            assert result == expect_val
        if status == "失败":
            result = loginpage.with_token_failed_text
            assert result == expect_val
 
 

Start appium, execute    pytest testcases\test_ddt\test_ddt_login.py -s -v   , check the running result:

4. Test report

pytest-html pytest-html · PyPI

pytest-allure  allure-pytest PyPI

Test report one: pytest-html

main.py

"""
  项目运行文件,并添加测试报告
"""
import pytest
import os,time

if __name__ == '__main__':
    report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),'reports')
    if not os.path.exists(report_dir):
        os.mkdir(report_dir)

    report = time.strftime('%Y_%m_%d_%H_%M_%S')

    reportfile = os.path.join(report_dir,report+'.html')

    pytest.main(['testcases','-s','-v',f'--html={reportfile}'])
 
 

Run the main.py file, run main.py to execute, it will execute all the test cases in testcases

After the execution is over, the generated test report, we can open it in the browser

 5. Optimize conftest.py

   After each test case is executed, if the execution fails to take a screenshot, the name of the screenshot is the test case name + time format related processing

from appium import webdriver
import pytest
import os, time
chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),'drivers/chrome/75.0.3770.140/chromedriver.exe')

# scope='session' 标记的方法执行域为---->所有测试用例运行之前/之后 运行的方法
@pytest.fixture(scope='session',autouse=True)
def driver():
    desired_caps = {
        'platformName': 'Android',                    # 测试Android系统
        'platformVersion': '7.1.2',                   # Android版本 可以在手机的设置中关于手机查看
        'deviceName': '127.0.0.1:62001',              # adb devices 命令查看  设置为自己的设备
        'automationName': 'UiAutomator2',             # 自动化引擎
        'noReset': False,                             # 不要重置app的状态
        'fullReset': False,                           # 不要清理app的缓存数据
        'chromedriverExecutable': chromedriver,       # chromedriver 对应的绝对路径
        'appPackage': "org.cnodejs.android.md",       # 应用的包名
        'appActivity': ".ui.activity.LaunchActivity"  # 应用的活动页名称(appium会启动app的加载页)
    }
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_capabilities=desired_caps)
    driver.implicitly_wait(5)  # 全局的隐式等待时间
    yield driver  # 将driver 传递出来
    driver.quit()


# 该方法是用来获取测试用例执行的结果(passed / FAILED)
@pytest.hookimpl(tryfirst=True,hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()               # 获取用例的执行结果
    print('用例的执行结果rep---->',rep)
    setattr(item, "rep_" + rep.when, rep)    # 将执行结果保存到 item 属性中 , req.when 执行时


# scope='function' 标记的方法执行域为---->每个测试用例运行之前/之后 运行的方法
@pytest.fixture(scope='function',autouse=True)
def case_run(driver:webdriver,request):   # request 为 pytest_runtest_makereport 方法获取到的执行结果(固定参数和用法)
    yield
    # 每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式
    if request.node.rep_call.failed:
        screenshots = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'screenshots')
        if not os.path.exists(screenshots):
            os.mkdir(screenshots)
        casename: str = request.node.nodeid
        # print("执行测试用例的名字:", casename)
        # 测试用例的名字很长 testcases/test_ddt/test_ddt_login.py::TestDdtLogin::test_login[....]
        # 对字符串进行截取,截取之后显示为  test_ddt_login-TestDdtLogin
        casename = casename[casename.rfind('/')+1:casename.rfind('::')].replace('.py::','-')
        filename = casename + '-' + time.strftime('%Y_%m_%d-%H_%M_%S')  +".png"
        screenshot_file = os.path.join(screenshots, filename)
        # 保存截图
        driver.save_screenshot(screenshot_file)

 6. Multi-device connection, parallel execution of test code

   Automatically obtain multiple connected devices through code, get the serial number of the device, start multiple appiums, and execute automated test cases on multiple devices in parallel through multi-process methods

     Click to view the optimized code

Finally, execute pip freeze > requirements.txt to export the package names and versions of the third-party packages used in the project to a file

                   pip install -r requirements.txt # Install the library and the corresponding version in the requirements.txt folder

  ​ Click me to get a full set of software testing (automated testing) video materials for free (note "csdn000")

Guess you like

Origin blog.csdn.net/csdnchengxi/article/details/123643121