用pytest实现POM模型

用pytest实现POM模型,第1张

底层实现原理(简易版):

代码示例:

1.先封装浏览器,创建单例模式(无论怎样,类里只会创建一个实例对象)

from selenium import webdriver

class Browser(object):
    """单例浏览器的类"""
    __instance = None  # 实例对象  #原始不存在   #单例模式:确保某一个类只有一个实例存在。即,无论创建多少个实例对象,都是同一个对象。
    driver = None  # 浏览器driver

    def __new__(cls, browser_name, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)  #创建私有实例对象
            # 打开浏览器
            if browser_name in ("chrome", "Chrome", "gc", "谷歌"):
                cls.driver = webdriver.Chrome()  # 驱动在默认的python目录下,也可以实现指定项目的驱动文件
            elif browser_name in ("firefox", "ff", "火狐"):
                cls.driver = webdriver.Firefox()
            else:
                raise TypeError(f"你传入的浏览器类型 [ browser_name ] 不受支持")
        return cls.__instance

    # 关闭浏览器-->并且要结束单例模式
    @classmethod
    def close_all_browsers(cls):
        if cls.driver is not None:  # 不是空的时候才关闭,防止多次调用。
            cls.driver.quit()
        cls.driver = None
        cls.__instance = None  # 解除单例

2.创建basepage,指定浏览器,封装元素 *** 作和去网页

import time

import allure

from rtest.browser.browser import Browser  #导入browser里面的类。
from selenium.webdriver.support.select import Select

class BasePage(object):
    """提供所有页面 *** 作的公共方法-->类似SeleniumLibrary"""
    def __init__(self):
        """拿到driver"""
        self.instance_browser=Browser("chrome")   #创建浏览器实例
        self.dr = self.instance_browser.driver  #实现driver

    def input_text(self, locator, text, clear=True):  #两个参数,一个布尔做判断。
        """
        定位元素,输入文本
        :param locator: 定位器,例如("xpath","//input")、("id","username")
        :param text: 输入的文本
        :param clear: 默认清空
        :return: None
        """
        ele = self.dr.find_element(*locator)  #因为元素通常是('id','name')这种格式的,localtor是个元组,要解包
        if clear is True:
            ele.clear()
        ele.send_keys(text)

    @allure.step("点击按钮")
    def click_button(self, locator):
        """点击按钮"""
        self.dr.find_element(*locator).click()

    @allure.step("断言")
    def element_text_should_be(self, locator, expected):
        ele = self.dr.find_element(*locator)
        assert expected in ele.text

    @allure.step("去往一个url地址 {url}")
    def goto_location(self,url):
        """去往一个url地址"""
        self.dr.get(url)
    @allure.step("清空输入框的文本",locotor)
    def clear_text(self)
        self.dr.find_element(*locotor).clear()

    @allure.step("设置隐式等待 {implicitly_wait}")
    def set_driver(self,implicitly_wait):
        self.dr.maximize_window()
        self.dr.implicitly_wait(implicitly_wait)

    @allure.step("关闭浏览器")
    def close_all_browsers(self):
        filename=fr"F:\tester\image\screenshot\{int(time.time())}.png"
        self.dr.get_screenshot_as_file(filename)
        allure.attach.file(filename,"关闭前截图",attachment_type=allure.attachment_type.PNG)
#allure添加附件(allure.attach) ,语法:allure.attach(body, name, attachment_type, extension),对测试用例、测试步骤以及fixture的结果加以补充。
        self.instance_browser.close_all_browsers()#结束单例模式

    @allure.step("点击文本链接")
    def click_link_text(self,text):
        self.dr.find_element("link text",text).click()
    @allure.step("按下拉列表的文本选择")
    def select_from_list_by_label(self,locator,lable):
        """
        根据参入的lable文本进行下拉列表的选择
        :param locator: select 的定位器
        :param lable: 根据文本选择
        :return: None
        """
        Select(self.dr.find_element(*locator)).select_by_visible_text(lable)
    
    @def step("js查找元素并传入值")
    def execute_javascript_date(self,locator,date):
        """执行js脚本"""
self.dr.execute_script('arguments[0].value=arguments[1]',self.dr.find_element(*locator),date)
        #js *** 作,改值

     
    def get_table_cell_text(self,locator:tuple,row,column):
        """给定第几行第几列都能查的出来
        locator='xpath','//table[@]/tbody/tr[{row}]/td[{column}]'
        row=1
        column=2
        expected=15888888888
        """
        #第一步:修改定位器中的行的列
        locator=list(locator)#变为列表即可修改
        locator[1]=locator[1].replace('{row}',row)#替换行
        locator[1] = locator[1].replace('{column}', column)  # 替换列
        cell_text=self.dr.find_element(*locator).text #获取指定行和列的值
        return cell_text

3.用例page,以loginpage为例。传入元素。

import allure

from test.pages.basepage import BasePage


class LoginPage(BasePage):
    """很多页面的 *** 作,依赖basepage库---》继承"""
    login_url="http://192.163.171.1:8080/abcbank"
    username_locator = ("id", "username")
    password_locator = ("id", "password")
    verifycode_locator = ("id", "verifycode")
    login_button_locator = ("xpath", '//button[@onclick="doLogin(\'null\')"]')
    @allure.step("去往登录页面")
    def goto_login_page(self):
        """去往登录页面"""
        self.set_driver(5)  # 设置浏览器
        self.goto_location(self.login_url)

    @allure.step("输入用户名 {text}")
    def input_username(self, text):
        """输入用户名"""
        self.input_text(self.username_locator, text)  # 调用basepage库中的输入动作

    @allure.step("输入密码 {text}")
    def input_password(self, text):
        """输入密码"""
        self.input_text(self.password_locator, text)  # 调用basepage库中的输入动作

    @allure.step("输入验证码 {text}")
    def input_verifycode(self, text):
        """输入验证码"""
        self.input_text(self.verifycode_locator, text)  # 调用basepage库中的输入动作

    @allure.step("点击登录按钮")
    def click_login_button(self):
        """点击登录按钮"""
        self.click_button(self.login_button_locator)  # 调用basepage库中的输入动作

 断言界面:

import allure

from rtester.pages.basepage import BasePage


class SellPage(BasePage):
    logout_locator = "xpath", '//a[@href="/abcbank/user/logout"]'
    customer_link_text="会员管理"

    @allure.step("判断注销文本存在")
    def assert_logout_text_should_expect(self):
        """判断注销文本存在"""
        self.element_text_should_be(self.logout_locator,"注销")

    @allure.step("去往会员管理页面")
    def goto_customer(self):
        """去往会员管理页面"""
        self.click_link_text(self.customer_link_text)

4.用例(case)界面,传入文本等参数。

import pytest
from rtester.pages.login.loginpage import LoginPage
from rtester.pages.home.sellpage import SellPage



class TestLogin(object):
    """拿登录页面,sell 页面  --- pytest框架"""

    # def __init__(self):  #不行的。---> pytest 里面不能有构造方法的。
    #     pass
    def setup(self):
        """拿去页面"""
        self.loginpage = LoginPage()
        self.sellpage = SellPage()

    def teardown(self):
        self.sellpage.close_all_browsers()  # 关闭浏览器

  
    def test_login_success(self):
        self.loginpage.goto_login_page()  # 打开浏览器访问主页
        self.loginpage.input_username("admin")  # 调用页面的功能
        self.loginpage.input_password("admin123")
        self.loginpage.input_verifycode("0000")
        self.loginpage.click_login_button()
        self.sellpage.assert_logout_text_should_expect()  # 调用页面的功能

进阶版:

举例:browser层,basepage层和上面简易模式一模一样。但因为有更多调用层(prettyallure装饰,包括步骤,标题,用例等级等的动态修饰;用例data的封装等等),使他们更加灵活和完美。

其中封装完的browser层,basepage层,common层,util层可以直接套用。其他层次也可以依葫芦画瓢。

执行顺序:主线:browser,封装浏览器----pages【basepage】,指定浏览器,定义元素 *** 作方法----pages【casepage】,用例界面,传入元素----{case调用caspage里的方法和data里的数据(data的数据调用路线:data【casedata.yaml】中放yaml数据----common【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件------case里面将getcasedata(路径)的数据作为pytest.mark.prameterize里的case的参数值。case调用prettyallure,取得模块中feature(模块名称),story(测试点),title(用例标题)的装饰。要传的测试的值放在step里面。再通过case.get(step).get('参数名')获取数据传参。}。

如果有需要常用的作为依赖的模块。(如传好参数的登录),可以封在business层中。作用类似于cookie

如果涉及到数据库,则要考虑config里面common里面设置config.yaml,设置数据库连接数字-----util层中,封装数据库连接和使用方法-----外层的config.py文件,引入doyaml,获得数据库中的数据。(类似于getcasedata,这里是获取的数据库的数据)

如果要更细致的话,可以将驱动器封装。

详解:

browser:封装浏览器

import time
#selenium 的版本是 4.1.0? pip show selenium
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from config import Config

class Browser(object):
    """单例浏览器的类"""
    __instance = None  # 实例对象
    driver = None  # 浏览器driver

    def __new__(cls, browser_name, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
            # 打开浏览器
            if browser_name in ("chrome", "Chrome", "gc", "谷歌"):
                cls.driver = webdriver.Chrome(service=ChromeService(Config.chrome_driver))  # 驱动在默认的python目录下,也可以实现指定项目的驱动文件
            elif browser_name in ("firefox", "ff", "火狐"):
                cls.driver = webdriver.Firefox(service=FirefoxService(Config.firefox_driver))
            else:
                raise TypeError(f"你传入的浏览器类型 { browser_name } 不受支持")
        return cls.__instance

    # 关闭浏览器-->并且要结束单例模式
    @classmethod
    def close_all_browsers(cls):
        if cls.driver is not None:  # 不是空的时候才关闭,防止多次调用。
            cls.driver.quit()
        cls.driver = None
        cls.__instance = None  # 解除单例


if __name__ == '__main__':
    b1 = Browser("chrome")  # 打开
    time.sleep(2)
    b1.close_all_browsers()  # 实例调用类方法---> 可以的。
    # 假设b1关闭,那么要新打开一个浏览器
    b2 = Browser("ff")  # 打开
    # b3 = Browser("chrome")
    # print(b1 is b2 is b3)
    # b3.close_all_browsers()  # 关闭
    time.sleep(5)
    b2.close_all_browsers()

pages【basepage】,指定浏览器,定义元素 *** 作方法

import time,os

import allure

from browser.browser import Browser
from selenium.webdriver.support.select import Select
from config import Config

class BasePage(object):
    """提供所有页面 *** 作的公共方法-->类似SeleniumLibrary"""

    def __init__(self):
        """拿到driver"""
        self.instance_browser = Browser("chrome")
        self.dr = self.instance_browser.driver  # 后面可以实现配置化

    def input_text(self, locator, text, clear=True):
        """
        定位元素,输入文本
        :param locator: 定位器,例如("xpath","//input")、("id","username")
        :param text: 输入的文本
        :param clear: 默认清空
        :return: None
        """
        ele = self.dr.find_element(*locator)
        if clear is True:
            ele.clear()
        ele.send_keys(text)

    def click_button(self, locator):
        """点击按钮"""
        self.dr.find_element(*locator).click()

    def element_text_should_be(self, locator, expected):
        ele = self.dr.find_element(*locator)
        assert expected in ele.text

    @allure.step("去往一个url地址 {url}")
    def goto_location(self, url):
        """去往一个url地址"""
        self.dr.get(url)

    @allure.step("设置隐式等待 {implicitly_wait}")
    def set_driver(self, implicitly_wait):
        self.dr.maximize_window()
        self.dr.implicitly_wait(implicitly_wait)

    @allure.step("关闭浏览器")
    def close_all_browsers(self):
        filename = fr'{Config.root_path}\image\screenshot\{int(time.time())}.png"'
        self.dr.get_screenshot_as_file(filename)
        allure.attach.file(filename, "关闭前截图", attachment_type=allure.attachment_type.PNG)

        self.instance_browser.close_all_browsers()  # 结束单例模式

    @allure.step("清空内容")
    def clear_all_content(self,locotor):
        self.dr.find_element(*locotor).clear()

    def click_link_text(self, text):
        self.dr.find_element("link text", text).click()

    def select_from_list_by_label(self, locator, lable):
        """
        根据参入的lable文本进行下拉列表的选择
        :param locator: select 的定位器
        :param lable: 根据文本选择
        :return: None
        """
        Select(self.dr.find_element(*locator)).select_by_visible_text(lable)

    def execute_javascript_date(self, locator, date):
        """执行js脚本"""

        self.dr.execute_script('arguments[0].value=arguments[1]', self.dr.find_element(*locator), date)

    def get_table_cell_text(self, locator: tuple, row, column):
        """给定第几行第几列都能查的出来
        locator='xpath','//table[@]/tbody/tr[{row}]/td[{column}]'
        row=1
        column=2
        expected=15888888888
        """
        # 第一步:修改定位器中的行的列
        locator = list(locator)  # 变为列表即可修改
        locator[1] = locator[1].replace('{row}', row)  # 替换行
        locator[1] = locator[1].replace('{column}', column)  # 替换列
        cell_text = self.dr.find_element(*locator).text  # 获取指定行和列的值
        return cell_text

pages【casepage】,用例界面,传入元素

import allure

from pages.basepage import BasePage


class LoginPage(BasePage):
    """很多页面的 *** 作,依赖basepage库---》继承"""
    login_url = "https://dsmall.csdeshang.com/home/Sellerlogin/login.html"
    username_locator = ("name", "seller_name")
    password_locator = ("name", "member_password")
    verifycode_locator = ("id", "verifycode")
    login_button_locator = ("xpath", '//input[@value="商家登录"]')

    @allure.step("去往登录页面")
    def goto_login_page(self):
        """去往登录页面"""
        self.set_driver(5)  # 设置浏览器
        self.goto_location(self.login_url)

    @allure.step("输入用户名 {text}")
    def input_username(self, text):
        """输入用户名"""
        self.input_text(self.username_locator, text)  # 调用basepage库中的输入动作

    @allure.step("输入密码 {text}")
    def input_password(self, text):
        """输入密码"""
        self.input_text(self.password_locator, text)  # 调用basepage库中的输入动作

    @allure.step("输入验证码 {text}")
    def input_verifycode(self, text):
        """输入验证码"""
        self.input_text(self.verifycode_locator, text)  # 调用basepage库中的输入动作

    @allure.step("点击登录按钮")
    def click_login_button(self):
        """点击登录按钮"""
        self.click_button(self.login_button_locator)  # 调用basepage库中的输入动作

data--用例的yaml  前面一个-表示列表。 参数名后面记得跟空格,再填参数值。    下图中的content表示是step的下一级。

- skip: false
  id: 1
  feature: 客服管理
  story: 客服设置
  title: 新增客服在前台显示
  serverity: Blocker
  step:
    content: 工作时间:AM10:00-PM 18:00
  expect: 保存成功

utils.doyaml,定义读写数据的方法:

from yaml import load, dump

try:
    from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
    from yaml import Loader, Dumper


class DoYaml(object):
    def __init__(self, filename):
        self.filename = filename

    def read(self):
        with open(self.filename, "r", encoding="utf8") as f:
            data = load(stream=f, Loader=Loader)
            return data

    def write(self, data):
        with open(self.filename, "w", encoding="utf8") as f:
            # data:写入的数据
            # stream:准备写入的文件
            # Dumper:写入器
            # allow_unicode=True:允许中文
            # sort_keys=False : 不排序
            output = dump(data=data, stream=f, Dumper=Dumper, allow_unicode=True, sort_keys=False)


if __name__ == '__main__':
    casedata = DoYaml("../data/usermanange/login_case.yaml")
    print(casedata.read())

common【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件

from config import Config
import os
from utils.doyaml import DoYaml


class GetCaseData(object):
    @classmethod
    def get_case_data(cls, case_file):
        root_path = Config.root_path  # 拿到了
        case_data_file = os.path.join(root_path, case_file)  # 拼接文件路径
        case_data = DoYaml(case_data_file).read()  # 读取用例的数据
        return case_data


if __name__ == '__main__':
    print(GetCaseData.get_case_data("data/customer/customer_add_case.yaml"))

ommon【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件

from config import Config
import os
from utils.doyaml import DoYaml


class GetCaseData(object):
    @classmethod
    def get_case_data(cls, case_file):
        root_path = Config.root_path  # 拿到了
        case_data_file = os.path.join(root_path, case_file)  # 拼接文件路径
        case_data = DoYaml(case_data_file).read()  # 读取用例的数据
        return case_data


if __name__ == '__main__':
    print(GetCaseData.get_case_data("data/customer/customer_add_case.yaml"))

common.prettyallure,存allure的修饰语句:

import pytest, allure


class PrettyAllure(object):
    @classmethod
    def prettyallure(cls, case):
        if case.get("skip") is True:
            pytest.skip("用例数据指定跳过执行~~")  # 跳过不执行
        allure.dynamic.feature(case.get("feature"))  # 用例模块  #dynamic表示动态修饰,能从case中拿到feature的数据后,放在@allure.feature里。
        allure.dynamic.story(case.get("story"))  # 测试点
        allure.dynamic.severity(case.get("serverity"))#严重等级
        allure.dynamic.title(f'编号_{case.get("id")}_{case.get("title")}')  # 测试标题

在business层中放入快速登陆:

import time

from pages.login.bussinessloginpage import LoginPage
class FastLogin(object):

    def __init__(self):
        self.loginpage=LoginPage()

    def user_fast_login(self):
        self.loginpage.goto_login_page()
        self.loginpage.input_username("buyer")
        self.loginpage.input_password("123456")
        self.loginpage.click_login_button()

if __name__ == '__main__':
    FastLogin().user_fast_login()

case的最终呈现示例

# 需要登录
import time
import pytest
from business.fast_user_login import FastLogin
from pages.home.sellpage import SellPage
from pages.customer.customerpage import CustomerPage
from common.prettyallure import PrettyAllure
from common.getcasedata import GetCaseData

case_data = GetCaseData.get_case_data("data/customer/customer_add_case.yaml")  # 目录是从根目录开始写。


class TestCustomerSearch(object):
    def setup(self):
        # 1.登录
        FastLogin().admin_fast_login()  # 使用admin快速登录
        # 2.进入customer 页面
        sellpage = SellPage()
        sellpage.goto_customer()
        # 3.获取customer页面
        self.customerpage = CustomerPage()

    def teardown(self):
        self.customerpage.close_all_browsers()

    @pytest.mark.parametrize("case", case_data)
    def test_customer_search(self, case):
        PrettyAllure.prettyallure(case)  # 美化报告
        search_phone = case.get("step").get("phone")
        self.customerpage.input_phone(search_phone)
        self.customerpage.input_customername("张三")
        self.customerpage.choies_sex("女")
        self.customerpage.input_childdate("1999-09-08")
        self.customerpage.input_creditkids("80")
        self.customerpage.input_creditcloth("70")
        self.customerpage.click_search_button()
        time.sleep(5)
        self.customerpage.table_cell_row1_column2_should_be_customerphone(search_phone)  # 断言


if __name__ == '__main__':
    pytest.main(["-v", '-s', __file__])

runner

import pytest
import subprocess

allure_data_path = "report/allure_data"
allure_report_path = "report/allure_report"
pytest.main(["-v", "-s", f"--alluredir={allure_data_path}", "--clean-alluredir","cases"])
subprocess.run(f"allure generate {allure_data_path} --clean -o {allure_report_path}", shell=True,
               universal_newlines=True)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/918755.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存