3.13 Web自动化 --- PO设计思想介绍

一 、 PO 是什么

  • 页面对象模型(PO)是一种设计模式,全称(Page Object),用来管理维护一组web元素的对象库

  • 在PO下,应用程序的每一个页面都有一个对应的page class

  • 每一个page class维护着该web页的元素集和操作这些元素的方法
  • page class中的方法命名最好根据对应的业务场景进行,例如通常登录后我们需要等待几秒钟,
  • 多用于UI自动化测试(例: Web ,APP等)

二、代码结构

 1.     PageObject层页面对象内容同包含: 

       1) 对象属性(全局变量)   = =       页面的元素 

python中,页面元素定位表达式表示,建议如下格式:
   
   ele_loactor = (By.XPATH, "XPath表达式")    
   当然:定位方式也可以是其他,比如By.ID,By.Name等

java中,页面元素定位表达式,建议如下格式:
     public static final By ele_loc  = By.xpath("元素定位表达式")

       2) 对象的行为       ==    页面的元素的操作(如:页面点击、输入等等)

三 、 代码实战

     这里以 http://120.78.128.25:8765/Index/login.html 登录功能测试为例

1.   python实现:

    1) LoginPage封装:

 

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC


class LoginPage:
    current_url = "http://120.78.128.25:8765/Index/login.html"
    # 登录框
    box_loc = (By.XPATH, '//form[@name="login-form"]')
    # 账号输入框
    user_loc = (By.XPATH, '//input[@name="phone"]')
    # 密码输入框
    pwd_loc = (By.XPATH, '//input[@name="password"]')
    # 登录按钮
    but_login_loc = (By.XPATH, "//button[contains(text(),'登录')]")
    # 表单错误提示
    error_loc = (By.XPATH, '//div[@class="form-error-info"]')
    # 页面中间错误提示
    center_error_loc = (By.XPATH, '//div[@class="layui-layer-content"]')

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

    def check_login_box_exist(self):
        try:
            # 先等待登录框可见
            WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(self.box_loc))
        except Exception as e:
            return False
        else:
            return True

    def login(self, user, pwd):
        """ 登录
        :param user:  用户账号
        :param pwd: 用户密码
        :return: None
        """
        # 先等待登录框可见
        if self.check_login_box_exist():
            # 定位账号输入框,并输入账号
            ele = self.driver.find_element(*self.user_loc)
            ele.clear() # 清除输入框内容
            ele.send_keys(user)  # 输入框输入指定文案
            # 定位密码输入框,并输入账号
            ele = self.driver.find_element(*self.pwd_loc)
            ele.clear()
            ele.send_keys(pwd)
            # 定位登录按钮并点击
            self.driver.find_element(*self.but_login_loc).click()

    def get_form_error_info(self):
        """ 获取错误提示内容:账号位数不对、密码未输入 """
        WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(self.error_loc))
        return self.driver.find_element(*self.error_loc).text

    def get_center_error_info(self):
        """ 获取页面中间错误提示内容:密码错误、账号未授权 """
        WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(self.center_error_loc))
        error_text = self.driver.find_element(*self.center_error_loc).text
        return error_text

2. LoginTest测试:

@ddt
class TestLogin(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.maximize_window()
        cls.driver.get("http://120.78.128.25:8765/Index/login.html")

    @classmethod
    def tearDownClass(cls):
        # 会话关闭,关闭浏览器
        cls.driver.close()
        cls.driver.quit()

    # 用例初始化以及清除操作,每次打开浏览器,关闭浏览器会比较耗时,如果所有用例的初始化(即:预置条件)是一样的,那么可以将操作放置类初始化和清除函数中
    # def setUp(self):
    #     self.driver = webdriver.Chrome()
    #     self.driver.get(LoginPage(self.driver).current_url)
    #     self.driver.maximize_window()
    #
    # def tearDown(self):
    #     self.driver.quit()

    # 输入不合法数据
    failture_data_input_invaild = [
        {'title': '密码为空', 'TestDatas': ['18684720553', ''], 'expect': '请输入密码'},
        {'title': '号码位数不足11位', 'TestDatas': ['1868472055', '123456'], 'expect': '请输入正确的手机号'},
        {'title': '号码位数大于11位', 'TestDatas': ['186847205537', '123456'], 'expect': '请输入正确的手机号'}]
    # 密码错误、号码未授权
    failture_data_number_not_allowed = [
        {'title': '帐号或密码错误!', 'TestDatas': ['18684720553', 'python123'], 'expect': '帐号或密码错误!'},
        {'title': '号码未授权', 'TestDatas': ['15934810000', '123456'], 'expect': '此账号没有经过授权,请联系管理员!'}]

    def test_login_success(self):
        """ 登录成功后,检测界面跳转后,”我的账户“元素是否存在"""
        # 调用OP中Login封装类中登录方法
        LoginPage(self.driver).login(RightPhone,RightPwd)
        # 断言
        self.assertTrue(IndexPage(self.driver).check_account_sign_exist())

    @data(*failture_data_input_invaild)
    def test_login_failure_for_invaild_input(self, one_invaild):
        """ 登录失败:无密码、账号位数不对"""
        # 登录类实例对象
        lp = LoginPage(self.driver)
        # 调用OP中Login封装类中登录方法
        lp.login(*one_invaild['TestDatas'])
        # 错误提示预期:
        error_expect = one_invaild['expect']
        # 断言
        self.assertEqual(lp.get_form_error_info(), error_expect)

    @data(*failture_data_number_not_allowed)
    def test_login_center_error(self, one_invaild):
        """ 登录失败:账号未授权、密码错误"""
        # 登录类实例对象
        lp = LoginPage(self.driver)
        # 调用OP中Login封装类中登录方法
        lp.login(*one_invaild['TestDatas'])
        # 错误提示预期:
        error_expect = one_invaild['expect']
        # 断言
        self.assertEqual(lp.get_center_error_info(), error_expect)

2 、 Java实现

     1) LoginPage封装:

package com.WebUI.Pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;


import com.WebUI.Tests.TestLogin;

public class LoginPage {
	/**
	 * 页面元素属性
	 */
  private By account_input_loc=By.xpath("//input[@name='phone']");
  private By password_input_loc=By.xpath("//input[@name='password']");
  private By remember_phone_loc = By.xpath("//input[@type='checkbox']");
  private By forgot_pwd_loc = By.xpath("//a[contains(text(),'忘记密码')]");
  private By login_button_loc = By.xpath("//button[text()='登录']");
  private By free_register_loc = By.xpath("//a[contains(text(),'免费注册')]");
  private By pwdOrAccountNull_error_loc = By.xpath("//div[@class='form-error-info']");
  private By verify_img_loc =By.xpath("//div[@name='notify_url']") ;
//  private By verify_input_loc = 
  // 密码错误或者账号无权限或者账号未注册时,错误提示
  private By accountNoExit_error_loc = By.xpath("//div[@class='layui-layer-content']");
  
  /**
   * 判断当前页面是否是登录界面
   * @return 是否是登录页面
   */
  public boolean currentPageisLoginIdle() {
	  String title = TestLogin.driver.getTitle();
	  if (title.equals("前程贷官网 - 欢迎登录")) {
		  return true;
	  }
	  return false;
  }
  /**
   * 判断元素是否存在
   * @param locator : 元素定位表达式
   * @return 元素存在与否状态
   */
  public boolean  eleIsExist(final By locator ) {
	  WebDriverWait wait =new WebDriverWait(TestLogin.driver,20);
	  try {
			wait.until(ExpectedConditions.presenceOfElementLocated(locator));
        return true;
	  }catch(Exception e) {
		  return false;
	  }
  }
  
  /**
   * 判断元素是否可见
   * @param locator : 元素定位表达式
   * @return 元素可见与否状态
   */
  public boolean  eleIsVisible(final By locator ) {
	  WebDriverWait wait =new WebDriverWait(TestLogin.driver,20);
	  try {
			wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
           return true;
	  }catch(Exception e) {
		  return false;
	  }
  }
  /**
   * 指定input元素输入内容
   * @param locator  : 元素定位表达式 By 
   * @param content :  输入内容
   */
  public void inputContent(final By locator,String content) {
	  if (eleIsVisible(locator)) {
		  WebElement ele = TestLogin.driver.findElement(locator);
		  ele.clear();
		  ele.sendKeys(content);
	  }	  
  }
  /**
   * 仅输入账号
   * @param phone :账号/号码
   */
  public void inputAccountInfo(String phone) {
	  inputContent(account_input_loc,phone);  
  }
  
  /**
   * 输入账号密码
   * @param phone :  账号
   * @param pwd: 密码
   */
  public void inputAccountInfo(String phone,String pwd) {
	  inputContent(account_input_loc,phone);
	  inputContent(password_input_loc,pwd);;  
  }
  /**
   * 点击登录按钮
   */
  public void clickLoginButton() {
	  if (eleIsVisible(login_button_loc)) {
	  TestLogin.driver.findElement(login_button_loc).click(); }

  }
  
  public String  getFormErrorInfo() {
	  if (eleIsVisible(pwdOrAccountNull_error_loc)) {
         return TestLogin.driver.findElement(pwdOrAccountNull_error_loc).getText();
         }
	  return "";
  }
  
   public String  getCenterError_info() {
     // 获取页面中间错误提示内容:密码错误、账号未授权 """
	   if (eleIsVisible(accountNoExit_error_loc)) {
	         return TestLogin.driver.findElement(accountNoExit_error_loc).getText();
	         }
		  return "";
     
 }
}

2. 测试类:

package com.WebUI.Tests;
import com.WebUI.Utils.OpenBrowser;
import com.WebUI.Pages.LoginPage;
import com.WebUI.Utils.Constants;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriver.Navigation;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class TestLogin {
	public static WebDriver driver;
	LoginPage LP = new LoginPage();
	@Parameters({"browserType"})
	@BeforeTest
	public void setUpTest(String browserType) {
		// 浏览器打开,进入登录界面
		System.out.println("您所选择浏览器类型为: " + browserType );
		driver = OpenBrowser.openBrower(browserType);
		driver.get(Constants.LOGIN_URL);
		driver.manage().window().maximize(); 
		driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
	}
	@BeforeMethod
	public void setUpMethod() {
		// 页面刷新
		Navigation navigate = driver.navigate();
		navigate.refresh();
		System.out.println("页面刷新");
	}
	
	@AfterTest
	public void tearDownTest() {
		// 浏览器关闭
//       driver.close();
       driver.quit();
 	}
	
	@Test(enabled=false)
	public void test_is_login_idle() {
		// 地址是否指向登录界面
		boolean is_login = LP.currentPageisLoginIdle();
		Assert.assertEquals(is_login, true);
		
	}

	@Test
	public void test_success() {
		LP.inputAccountInfo("18684720553");
		
	}
	@Test
	public void testNoinputLogin() {
		// 不输入任何信息直接登录
		LP.clickLoginButton();
		String errorText = LP.getFormErrorInfo();
		Assert.assertEquals(errorText, "请输入手机号");	
	}
	
	@Test
	public void testNoInputPwdLogin() {
		// 不输入密码直接登录
		LP.inputAccountInfo("18684720553");
		LP.clickLoginButton();
		String errorText = LP.getFormErrorInfo();
		Assert.assertEquals(errorText, "请输入密码");	
	}
}

结果截图:

 

 四、UI自动化用例设计原则

  •  用例之间尽可能不相互干扰(前边用例不影响后边用例执行)
  •  每一条用例都可以单独运行
  •  用例数据没有环境干扰

           单独服务器自动化执行 

           单独时间去执行(比如,Jekins定时凌晨执行)

五、框架设计 :

  •    地址URL常量化

  •    浏览器类型,参数化   ----- TestNG xml

  •    PO 模式 ---- 页面对象(元素/操作)  与 页面测试  分离开
  •    页面共性提取 ---- 元素是否存在,元素点击,输入等等操作
  •    数据分离   ------ 测试数据可单独管理,测试类中引入使用即可
  •    数据驱动   ------  dataProviderClass  和dataProvider 
  •    业务流程封装: 

           1) 用例层只需要关注业务; 

            2) 用例层基于业务组装,更加灵活简单;

            3)  实例化业务流程 : 对象数据初始化则需要有参构造以及set/get方法(如:登录实现登录功能,登录函数中包含登录信息输入、点击登录按钮等等)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值