web自动化实战之BasePage

什么是BasePage?

到目前为止,我们已经封装了四个PO:login_page、index_page、 invest_page和user_page。在这些PO当中,存在着很多重复使用的方法,比如访问页面、等待页面标题出现、find_element、各种等待等。其实这些方法我们都可以单独拉出来封装到一个模块中,然后供所有的PO调用。这种把每个PO的通用方法单独拉出来封装成到一个公共类当中的思想就叫做BasePage。

页面行为分类

页面行为分为两类:

  • 业务型页面行为 。这类行为是这个页面会用得到,换了另一个页面就用不上。比如说index_page中的抢投标操作:web自动化实战之BasePage
    这种行为只在首页能用到,因为只有首页需要进行抢投标的操作,而换成其他页面,比如说登录页,就用不到这种操作了。
  • 通用型页面行为。这类行为是所有的页面甚至所有项目都有可能会用到的行为。比如说通过url访问页面、等待元素可见,在登录页面用得到,在别的任何页面都可能用得到,甚至换个项目,这个项目下的任何页面都会用得上。像这一类通用的页面行为就可以封装到basepage当中。

哪些页面行为可以被封装成basepage?

  • 访问页面。根据所给的url访问对应页面;
  • 各类显性等待。如:等待页面标题出现、等待元素可见、等待元素可被点击、等待元素被加载到dom树中;
  • 各类元素操作。如:点击、输入字符、滚动、移动到某个元素、选择select下拉框等;
  • 三大切换。包括:iframe切换、窗口切换、alert切换;
  • 文件上传;
  • js操作。如改变页面元素的属性、窗口滚动等;
  • 页面截屏。当执行出现错误时截图。

代码实战

basepage放到哪?

basepage放到哪比较合适呢?无非就两个:middleware、common。middleware是当前项目中通用的,而common是换了个项目也还能用的。很显然,basepage并不只是当前项目通用,所以放到common中比较合适。

basepage.py

import os
from datetime import datetime

from selenium.webdriver import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from config import config
from middleware.handler import Handler




class BasePage:
    title = None

    def __init__(self, driver):
        self.driver = driver
        #等待页面标题出现
        try:
            WebDriverWait(self.driver, 20).until(
                expected_conditions.title_contains(self.title)
            )
        except:
            print("你的操作可能不在当前页面中,可能会引发异常{}".format(self.title))

	#查找元素
    def find_element(self, locator):
        try:
            el = self.driver.find_element(*locator)
            return el
        except:
            # 如果找不到元素,截图
            self.screen_shot()
            Handler.logger.error("元素找不到:{}".format(locator))

	#元素找不到时截图
    def screen_shot(self):
        path = config.IMG_PATH
        ts = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        filename = os.path.join(path, ts + ".png")
        self.driver.save_screenshot(filename)

	#等待元素可见
    def wait_element_visible(self, locator, timeout=20, poll=0.5):  
        try:
            el = WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(
                expected_conditions.visibility_of_element_located(locator)
            )
            return el
        except:
            self.screen_shot()
            Handler.logger.error("元素找不到{}".format(locator))

	#等待元素可被点击
    def wait_element_clickable(self, locator, timeout=20, poll=0.5):
        try:
            el = WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(
                expected_conditions.element_to_be_clickable(locator)
            )
            return el
        except:
            self.screen_shot()
            Handler.logger.error("元素找不到{}".format(locator))
	
	#等待元素被加载
    def wait_element_presence(self, locator, timeout=20, poll=0.5):
        try:
            el = WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(
                expected_conditions.presence_of_element_located(locator)
            )
            return el
        except:
            self.screen_shot()
            Handler.logger.error("元素找不到{}".format(locator))
	
	#点击元素
    def click(self, locator):
    #当元素可被点击时再点击
        self.wait_element_clickable(locator).click()
        return self
	
	#输入字符
    def write(self, locator, value=''):
   	#当元素被加载出来时再输入字符
        self.wait_element_presence(locator).send_keys(value)
        return self

	#窗口滚动
    def scroll(self, height=None, width=None):
        if not height:
            height = 0
        if not width:
            width = 0
        js_code = "window.scrollTo({}, {});".format(width, height)
        self.driver.execute_script(js_code)
        return self

	#移动到某个元素上面
    def move_to(self, locator):
        el = self.driver.find_element(*locator)
        ActionChains(self.driver).move_to_element(el).perform()
        return self
       
   	#iframe切换
    def switch_frame(self, locator, timeout=20):
        WebDriverWait(self.driver, timeout=timeout).until(
            expected_conditions.frame_to_be_available_and_switch_to_it(locator)
        )
        return self

在这里说明几点:

  • 元素操作前的等待。在进行元素操作前,最好要事先想想要不要加显性等待,很多情况下元素操作前没加等待的话可能会出现异常,比如说跳转至下一个页面,元素还没见到就去定位这个元素并点击,这自然是会出错的。所以这里在点击元素、输入字符时都增加了相应的等待;
  • 窗口滚动。这里窗口滚动是直接用javascript脚本实现。当height未传或传了None,则把高度设为0,也就是不进行纵向滚动;当width未传或传了None,则把宽度设为0,也就是不进行横向滚动;
  • 无论是报告还是截图,亦或是其他的文件,一般都会做两件事:一个是放到某个目录下,另一个是文件名以时间戳方式命名,方便查找和定位。规定文件放到某个目录下,需要调用os.path的join进行目录与项目路径的拼接,形成新路径,这个路径一般放到config目录当中;以时间戳方式命名文件一般需要调用datetime.now().strftime(),调用现在的时间并以特定格式显示,需要在strfime中传入格式的样式;
  • 标题变量的位置。这里title变量不宜放到初始化方法当中,因为这样在测试方法中调用PO时都需要传入页面标题,很不好,所以应作为类方法出现,这样每个PO下都能自行定义各自的标题。

pageobject继承basepage

basepage写好了,那么怎么给每个po类进行调用呢?像这种定好了一个模板,其他类都需要用到这个模板的场景应该用继承。

login_page.py

from config import config
from middleware.page.index_page import Index_page
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
from common import basepage

class Login_page(basepage.BasePage):
    title = "前程贷官网 - 欢迎登录"
    url = config.HOST + "/Index/login.html"
    def __init__(self,driver):
        self.driver = driver

    #元素定位(定位方法+定位表达式)
    login_btn_locator = (By.CLASS_NAME, "btn-special")
    username_locator = (By.NAME, "phone")
    pwd_locator =  (By.NAME, "password")
    error_msg_locator = (By.CLASS_NAME, "form-error-info")
    invalid_msg_locator = (By.CLASS_NAME, "layui-layer-content")

    def get_url(self):
        self.driver.get(self.url)
        return self

    #登录失败
    def login_failed(self,username,password):
        # 元素定位、元素操作(定位用户名、密码并输入,点击登录)
        self.enter_username(username)
        self.enter_password(password)
        self.click_login_button()
        return self

    #登录成功
    def login_success(self,username,password):
        # 元素定位、元素操作(定位用户名、密码并输入,点击登录)
        self.enter_username(username)
        self.enter_password(password)
        self.click_login_button()
        return Index_page(self.driver)

    #登录未授权用户
    def login_invaild(self,username,password):
        #元素定位、元素操作(定位用户名、密码并输入,点击登录)
        self.enter_username(username)
        self.enter_password(password)
        self.click_login_button()
        return self


    #输入用户名
    def enter_username(self,username):
        self.write(self.username_locator,username)
        return self

    #输入密码
    def enter_password(self,password):
        self.write(self.pwd_locator,password)
        return self

    #点击登录按钮
    def click_login_button(self):
        self.click(self.login_btn_locator)
        return self

    #用户名、密码为空时获取错误信息
    def get_error_message(self):
        error_text = self.find_element(self.error_msg_locator).text
        return error_text

    #用户未授权时获取错误信息
    def get_invaild_message(self):
        invaild_locator = self.find_element(self.invalid_msg_locator).text
        return invaild_locator
上一篇:Java+Selenium显式等待常用函数之ExpectedConditions


下一篇:【python+selenium-01】基础版本演变为关键字驱动