app自动化09 TPshop登录实战

TPshop

需求

对tpshop的登录功能进行自动化测试

测试用例的编写

以后在写测试用例时,可以大体上按着这个流程来写。

项目准备

准备之前用到的base和配置文件等

进入登录界面

1. 创建两个文件在对应的文件夹下,login_page、test_login
2. 在test_login下,setup中,连接手机(导入模块,使用init_driver函数)
3. 在login_page下,写类,继承BaseAction
4. 在test_login下,创建page对象
5. 分析步骤,发现部分步骤是必须要有的。考虑写在page的构造函数__init__()中
6. 因为在login中,发现一定是有需要前置的步骤,在page的类里,重写init的函数,
   并且在调用父类构造函数__init__()之后,执行“必要的步骤”com.tpshop.malls:id/tab_txtv
项目结构截图
base文件夹:
base_action.py
class BaseAction:
    def __init__(self,driver):
        self.driver=driver
    def click(self,loc):
        self.find_element(loc).click()

    def input_text(self,loc,text):
        self.find_element(loc).send_keys(text)
    def find_element(self,loc):
        return self.driver.find_element(loc[0],loc[1])
base_driver.py
import os,sys
sys.path.append(os.getcwd())
from appium import webdriver
def init_driver():
    # server 启动参数
    desired_caps = {}
    # 设备信息
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '5.1'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    # app的信息
    desired_caps['appPackage'] = 'com.tpshop.malls'
    desired_caps['appActivity'] = '.SPMainActivity'
    # 解决输入中文
    desired_caps['unicodeKeyboard'] = True
    desired_caps['resetKeyboard'] = True

    # 声明我们的driver对象
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver
base_yml.py
import yaml
def yml_data_with_file(file_name):
    with open("./data/"+file_name+".yml",'r',encoding='utf-8') as f:
        return yaml.load(f)
page文件夹:
login_page.py
import os, sys

from selenium.webdriver.common.by import By

sys.path.append(os.getcwd())

from base.base_action import BaseAction


class LoginPage(BaseAction):

    mine_button = By.XPATH, "//*[contains(@text,'我的')]"
    login_signup_button = By.XPATH, "//*[contains(@text,'登录/注册')]"
    username_text_view = By.XPATH, "//*[contains(@text,'请输入手机号码')]"
    password_text_view = By.ID, "com.tpshop.malls:id/edit_password"
    login_button = By.ID, "com.tpshop.malls:id/btn_login"

    def __init__(self, driver):
        BaseAction.__init__(self, driver)
        # 点击我的
        # 点击登录/注册
        self.jump_2_login_page()

    def jump_2_login_page(self):
        self.click(self.mine_button)
        self.click(self.login_signup_button)

    def input_username(self, text):
        self.input_text(self.username_text_view, text)

    def input_password(self, text):
        self.input_text(self.password_text_view, text)

    def click_login(self):
        self.click(self.login_button)
data文件夹
login_data.yml
test_login:
  -
    - "18503080305"
    - "123000"
    - "成功"
  -
    - "185030803051"
    - "123000"
    - "不存在"
scripts文件夹
test_login.py
import os, sys

import pytest

sys.path.append(os.getcwd())

from base.base_driver import init_driver
from page.login_page import LoginPage
from base.base_yml import yml_data_with_file


class TestLogin:

    def setup(self):
        self.driver = init_driver()
        self.login_page = LoginPage(self.driver)

    @pytest.mark.parametrize(("username", "password","toast"),yml_data_with_file("login_data")['test_login'])
    def test_login(self, username, password, toast):
        # 输入手机号
        self.login_page.input_username(username)
        # 输入密码
        self.login_page.input_password(password)
        # 点击登录
        self.login_page.click_login()
pytest.ini
[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*
python_classes = Test*
python_functions = test_*

Toast

toast:显示一段文本(或者图片),然后再缓缓消失。就是一个弹窗。

获取toast

下载 appium-uiautomator2-driver
 cnpm install appium-uiautomator2-driver
前置代码添加
desired_caps['automationName'] = 'Uiautomator2'
查找弹窗消息
from appium import webdriver
import time

# server 启动参数
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

desired_caps = dict()
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.164.101:5555'
# app信息
desired_caps['appPackage'] = 'com.cyanogenmod.filemanager'
desired_caps['appActivity'] = '.activities.NavigationActivity'
# 中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True

# toast
desired_caps['automationName'] = 'Uiautomator2'

# 声明对象
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def find_toast(message):
    ele = driver.find_element(By.XPATH,"//*[contains(@text,'"+message+"')]")
    return ele.text

time.sleep(5)
# driver.keyevent(4) Uiautomator1框架
driver.press_keycode(4)  # Uiautomator2框架返回,出现退出弹窗
print(find_toast('退出'))

"""
    再次点击即可退出
"""

将登录成功或失败设为toast

1. 在base中,写了一个find_toast的函数,返回,根据部分内容,找到的全部内容。
2. 在base中,写了一个is_toast_exist的函数,如果找到,则返回true,如果找不到,则报错并且返回false
3. desired_caps['automationName'] = 'Uiautomator2'!!!!!!
base_action.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
    def __init__(self,driver):
        self.driver=driver

    def click(self,loc):
        self.find_element(loc).click()

    def input_text(self,loc,text):
        self.find_element(loc).send_keys(text)

    def find_element(self,loc):
        return self.driver.find_element(loc[0],loc[1])

    def find_toast(self, message, timeout, poll):
        """
        # message: 预期要获取的toast的部分消息
        """
        message = "//*[contains(@text,'" + message + "')]"  # 使用包含的方式定位
        element = WebDriverWait(self.driver, timeout,poll).until(lambda x: x.find_element(By.XPATH, message))
        return element.text

    def is_toast_exist(self, message, timeout=3, poll=0.1):
        try:
            self.find_toast(message, timeout, poll)
            return True
        except Exception:
            return False
login_page.py
import os, sys

from selenium.webdriver.common.by import By

sys.path.append(os.getcwd())

from base.base_action import BaseAction


class LoginPage(BaseAction):

    mine_button = By.XPATH, ["text,我的", "resource-id,com.tpshop.malls:id/tab_txtv,1"]
    login_signup_button = By.XPATH, "text,登录/注册"
    username_text_view = By.XPATH, "text,请输入手机号码"
    password_text_view = By.ID, "com.tpshop.malls:id/edit_password"
    login_button = By.ID, "com.tpshop.malls:id/btn_login"

    def __init__(self, driver):
        BaseAction.__init__(self, driver)
        # 点击我的
        # 点击登录/注册
        self.jump_2_login_page()

    def jump_2_login_page(self):
        self.click(self.mine_button)
        self.click(self.login_signup_button)

    def input_username(self, text):
        self.input_text(self.username_text_view, text)

    def input_password(self, text):
        self.input_text(self.password_text_view, text)

    def click_login(self):
        self.click(self.login_button)
test_login.py
import os, sys

import pytest

sys.path.append(os.getcwd())

from base.base_driver import init_driver
from page.login_page import LoginPage
from base.base_yml import yml_data_with_file


class TestLogin:

    def setup(self):
        self.driver = init_driver()
        self.login_page = LoginPage(self.driver)

    @pytest.mark.parametrize(("username", "password","toast"), yml_data_with_file("login_data")['test_login'])
    def test_login(self, username, password, toast):
        # 输入手机号
        self.login_page.input_username(username)
        # 输入密码
        self.login_page.input_password(password)
        # 点击登录
        self.login_page.click_login()
        assert self.login_page.is_toast_exist(toast)

实际项目中碰到的数据解析问题

有问题版本

上面代码虽然大体上完成了某个功能,但还有最重要的一点:那就是可维护性
之前的数据
test_login:
  -
    - "18503080305"
    - "123000"
    - "成功"
  -
    - "185030803051"
    - "123000"
    - "不存在"
解析
base_yml.py
import yaml
def yml_data_with_file(file_name):
    with open("./data/"+file_name+".yml",'r',encoding='utf-8') as f:
        return yaml.load(f)
    
"""
    {'test_login': [['18503080305', '123000', '成功'], ['185030803051', '123000', '不存在']]}
    
"""
login_data.yml
test_login:
  -
    - "18503080305"
    - "123000"
    - "成功"
  -
    - "185030803051"
    - "123000"
    - "不存在"
参数化
import os, sys
import pytest
sys.path.append(os.getcwd())

from base.base_yml import yml_data_with_file

class Test_Login:
    """
         [['18503080305', '123000', '成功'], ['185030803051', '123000', '不存在']]
         因为列表里的每一个元素都是列表,所以可用元组或列表取出来,
         故可写成下列形式取出
    """
    @pytest.mark.parametrize(("username", "password", "toast"),yml_data_with_file("login_data")['test_login'])
    def test_login(self, username, password, toast):
         # 输入手机号
        self.login_page.input_username(username)
        # 输入密码
        self.login_page.input_password(password)
        # 点击登录
        self.login_page.click_login()
        assert self.login_page.is_toast_exist(toast)
但是这样有一个缺点
过一段时间,你想要改第二条用例数据,但是写成上面的login_data.yml文件,你不知道哪一条是第二条用例数据?
怎么办?

改进版本

解决思路
把测试用例的编号,写到yml文件里面,写成下面这种形式:
login_data.yml
test_login:
  test_login_001:
    username: "18503080305"
    password: "123000"
    toast: "成功"
  test_login_002:
    username: "185030803051"
    password: "123000"
    toast: "不存在"
解析
base_yml.py
import yaml
def yml_data_with_file(file_name):
    with open("../data/"+file_name+".yml",'r',encoding='utf-8') as f:
        return yaml.load(f)
    
if __name__ == '__main__':
    print(yml_data_with_file("login_data"))

"""
{'test_login': {
    'test_login_001': {'username': '18503080305', 'password': '123000', 'toast': '成功'}, 
    'test_login_002': {'username': '185030803051', 'password': '123000', 'toast': '不存在'}
                }
}
"""
若想将其转变为列表形式(这是因为参数的值最终要转为列表形式存储,才好获取,元素可以为列表或字典等)
新版:base_yml.py
import yaml
def yml_data_with_file(file_name,key):
    with open("../data/"+file_name+".yml",'r',encoding='utf-8') as f:
       data_list=[]
       for data in yaml.load(f)[key].values():
           print(data)
           data_list.append(data)
           print(data_list)
           print('**************')

       return data_list

if __name__ == '__main__':
    yml_data_with_file("login_data",'test_login')
结果
{'username': '18503080305', 'password': '123000', 'toast': '成功'}
[{'username': '18503080305', 'password': '123000', 'toast': '成功'}]
**************
{'username': '185030803051', 'password': '123000', 'toast': '不存在'}
[{'username': '18503080305', 'password': '123000', 'toast': '成功'}, {'username': '185030803051', 'password': '123000', 'toast': '不存在'}]
**************
参数化
import os, sys
import pytest
sys.path.append(os.getcwd())

from base.base_yml import yml_data_with_file

class Test_Login:
"""
数据格式:
[{'username': '18503080305', 'password': '123000', 'toast': '成功'}, {'username': '185030803051', 'password': '123000', 'toast': '不存在'}]
由于参数化列表里面的元素是列表形式,所以用列表接收即可,这里我用了args作为参数化接收
"""
    @pytest.mark.parametrize("args",yml_data_with_file("login_data",'test_login'))
    def test_login(self,args):
        print(args["username"],args["password"],args["toast"])
        # 输入手机号
        self.login_page.input_username(args["username"])
        # 输入密码
        self.login_page.input_password(args["password"])
        # 点击登录
        self.login_page.click_login()

        assert self.login_page.is_toast_exist(args["toast"])

MeizuNote9测试小米商城的登录

base_driver.py
import os,sys
sys.path.append(os.getcwd())
from appium import webdriver
def init_driver():
    # server 启动参数
    desired_caps = {}
    # 设备信息
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '10'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    # app的信息
    desired_caps['appPackage'] = 'com.xiaomi.shop'
    desired_caps['appActivity'] = 'com.xiaomi.shop2.activity.MainActivity'
    # 解决输入中文
    desired_caps['unicodeKeyboard'] = True
    desired_caps['resetKeyboard'] = True
    desired_caps['noReset'] = True  # 保留软件数据
    # 声明我们的driver对象
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver
test_login.py
import os, sys

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

sys.path.append(os.getcwd())
from base.base_driver import init_driver
class TestLogin:

    def setup(self):
        self.driver=init_driver()

    def test_login(self):
       #1进入小米商城后,点击我的
       mine = WebDriverWait(self.driver, 3.0, 1.0)\
           .until(lambda x: x.find_element(By.XPATH, "//*[contains(@text,'我的')]"))
       mine.click()
       #2点击左上角登录注册按钮
       login= WebDriverWait(self.driver, 3.0, 1.0)\
           .until(lambda x: x.find_element(By.XPATH, "//*[contains(@text,'登录/注册')]"))
       login.click()
       #3 点击用账号、密码登录
       accout_pwd=WebDriverWait(self.driver, 3.0, 1.0)\
           .until(lambda x: x.find_element(By.ID, "com.xiaomi.shop:id/entry_to_password_login"))
       accout_pwd.click()
注意:
x.find_element(loc[0],loc[1])的效率更高些,如果页面报找不到元素可以考虑用这个方法
如果By.XPATH找不到元素,可以考虑用By.ID找元素。多试几次,这都是泪呀,孙腾

后续:对登录测试做进一步的完善

小米商城自动化登录测试完善

项目架构如下

base文件夹

base_driver.py
import os,sys
sys.path.append(os.getcwd())
from appium import webdriver
def init_driver():
    # server 启动参数
    desired_caps = {}
    # 设备信息
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '10'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    # app的信息
    desired_caps['appPackage'] = 'com.xiaomi.shop'
    desired_caps['appActivity'] = 'com.xiaomi.shop2.activity.MainActivity'
    # 解决输入中文
    desired_caps['unicodeKeyboard'] = True
    desired_caps['resetKeyboard'] = True
    # 保留软件数据,否则每次一开始都要有一些权限弹窗问题,会报错的
    desired_caps['noReset'] = True  
    # 声明我们的driver对象
    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    return driver
base_yml.py
import yaml
def yml_data_with_file(file_name,key):
    with open("./data/"+file_name+".yml",'r',encoding='utf-8') as f:
       data_list=[]
       for data in yaml.load(f)[key].values():
           data_list.append(data)
       return data_list

if __name__ == '__main__':
    data_list=yml_data_with_file("login_data",'test_login')
    print(data_list)
base_action.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
    def __init__(self,driver):
        self.driver=driver

    def click(self,loc):
        ele=WebDriverWait(self.driver, 5.0, 1.0).until(lambda x: x.find_element(loc[0],loc[1]))
        ele.click()

    def input_text(self,loc,text):
        ele=WebDriverWait(self.driver, 5.0, 1.0).until(lambda x: x.find_element(loc[0],loc[1]))
        ele.send_keys(text)

data文件夹

login_data.yml
test_login:
  test_login_001:
    username: "18355700120"
    password: "st998929"

page文件夹

login_page.py
import os, sys
from selenium.webdriver.common.by import By
sys.path.append(os.getcwd())
import time
from base.base_action import BaseAction
class LoginPage(BaseAction):
    # 我的
    mine_button = By.XPATH, "//*[contains(@text,'我的')]"
    # 登录/注册
    loginRegist_button = By.XPATH, "//*[contains(@text,'登录/注册')]"
    # 用账号密码登录
    accoun_button = By.ID, "com.xiaomi.shop:id/entry_to_password_login"
    # 用户名
    username_btn = By.ID,'com.xiaomi.shop:id/et_account_name'
    # 密码
    pwd_btn = By.ID,'com.xiaomi.shop:id/et_account_password'
    # 登录
    login_btn = By.ID,"com.xiaomi.shop:id/btn_login"
    # 错误提示文字 用户名密码不匹配
    error_btn = By.ID,'com.xiaomi.shop:id/error_password_tips'
    # 设置按钮
    set_btn = By.ID,'com.xiaomi.shop.plugin.homepage:id/mine_root_icon_setting'
    # 退出按钮
    back_btn =By.ID,'com.xiaomi.shop2.plugin.setting:id/common_button_rootview'


    def __init__(self, driver):
        BaseAction.__init__(self,driver)
        # 前置代码,调到账号、密码登录界面
        self.jump_to_login()

     #4 输入用户名
    def input_username(self,text):
        self.input_text(self.username_btn,text)

    #5 输入密码
    def input_password(self,text):
        self.input_text(self.pwd_btn,text)

    #6 点击登录
    def click_login(self):
        self.click(self.login_btn)

    #7 判断是否登录
    def is_errorTip(self):
        try:
            self.driver.find_element(self.error_btn[0],self.error_btn[1])
            # print(ele.text)
            return False
        except Exception:
            return True

    # 登录成功需要退出
    # 8 点击登录成功后的设置页面
    def click_set(self):
        self.click(self.set_btn)
    # 9 模拟手势的下滑操作,找到退出按钮并点击
    def click_back(self):
        self.driver.swipe(50,1583,50,463,3000)
        # 10 点击退出账号
        self.click(self.back_btn)
        # 11 用绝对坐标点击退出,由于焦点问题,只能用坐标定位
        time.sleep(3)  # 如果遇到超时异常,等待3秒再去定位,可以解决这个问题
        self.driver.tap([(780, 2010)])
    def jump_to_login(self):
        # 1 点击我的
        self.click(self.mine_button)
        # 2 点击登录注册
        self.click(self.loginRegist_button)
        # 3 点击用账号密码登录
        self.click(self.accoun_button)

scripts文件夹

test_login.py
import os, sys
import time
sys.path.append(os.getcwd())
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from page.login_page import LoginPage
from base.base_driver import init_driver
from base.base_yml import yml_data_with_file
class TestLogin:

    def setup(self):
        self.driver=init_driver()
        self.login_page = LoginPage(self.driver)

    @pytest.mark.parametrize("args", yml_data_with_file("login_data",'test_login'))
    def test_login(self, args):
        self.login_page.input_username(args['username'])
        self.login_page.input_password(args['password'])
        self.login_page.click_login()
        time.sleep(5)
        # 成功就向下执行
        if self.login_page.is_errorTip():
            # 登录成功后返回我的主页
            self.driver.keyevent(4)
            # 点击设置页面,再退出
            self.login_page.click_set()
            # 找到退出按钮并点击
            self.login_page.click_back()
        assert self.login_page.is_errorTip()

    def teardown(self):
        print("测试结束hahaha")

pytest.ini

[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*
python_classes = Test*
python_functions = test_*

posted @ 2020-10-12 23:37  幸福来之不易  阅读(837)  评论(0编辑  收藏  举报