WebUI自动化框架 - 关键字驱动

关键字驱动介绍

什么是关键字驱动框架?

  • 关键字驱动框架是一种功能自动化测试框架,它也被称为表格驱动测试或者基于动作字的测试。关键字驱动的框架的基本工作是将测试用例分成四个不同的部分。首先是测试步骤(Test Step),二是测试步骤中的对象(Test Object),三是测试对象执行的动作(Action),四是测试对象需要的数据(Test Data)。

四个主要组成部分

  • Test Step:是一个小的测试步骤的描述或者测试对象的一个操作说明。

  • Test Object:是指页面对象或元素,就像用户名、密码,

  • Action指页面操作的动作,打开浏览器,点击一个按钮,文本框输入一串文本等。

  • Test Data: 是任何对象操作时所需要的值,就像用户名、密码进行输入时的输入内容。

​ 其实我们做关键字的驱动的思想,就是把编码从测试用例和测试步骤中分离出来,这样对于不会编码的人员更容易理解自动化,从而让手工测试人员也可以编写自动脚本。(这并不意味这不需要自动化测试人员,对于自动化框架的构建,自动化代码的更新,结构调整等都需要一个技术性的人员)对于测试小的项目的团队,可以有两个手工测试人员和一个自动化测试人员。

举例:

  • 通过一个简单的登录功能例子来理解这个概念,想想看你的自动化流程需要做哪些事情:
  1. 打开一个浏览器
  2. 输入url跳转到网站首页
  3. 点击“登录”链接,进入登录页面
  4. 输入“用户名”
  5. 输入“密码”
  6. 点击“登录”按钮,进行登录
  7. 点击“注销”按钮,退出登录
  8. 关闭浏览器

优势

  1. 不需要太多的技术:一旦框架建立,手工测试人员和非技术人员都可以很容易的编写自动化测试脚本。
  2. 简单易懂:它存在Excel表格中,没有编码,测试脚本容易阅读和理解。关键字和操作行为这样的手工测试用例,使它变得更容易编写和维护。
  3. 早期介入:可以在应用未提交测试之前,就可以建立关键字驱动测试用例对象库,从而减少后期工作。使用需求和其它相关文档进行收集信息,关键字数据表可以建立手工测试程序。
  4. 组件的重用性:实施关键字驱动的模块化,进一步提高可重用性。
  5. 代码的重用性:作为关键字驱动框架中,只有一个执行引擎,它是鼓励极端的代码的复用。

下面是我们将要去实现的一些通用组件:

  1. excel: 是我们存放测试用例(Test Case)、测试步骤(Test Step)、测试对象(Test Object)和操作动作(Action)的关键字驱动数据表;
  2. action_page.py: 基于webdriver封装的方法文件,这个组件在关键字驱动框架中起着很重要的作用,它主要存放执行的Action,通过映射实现调用方法;
  3. run.py: 运行程序文件,程序入口。

关键字驱动实战

封装所有的动作的几类:action_page.py

from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
import logging
import time
import os


logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(filename)s - %(levelname)s - %(message)s",
    datefmt='%a, %Y - %m - %d %H: %M: %S',
)


class ActionPage(object):

    def __init__(self):
        self.driver = None
        if self.driver is None:
            self.driver = webdriver.Chrome()

    def open_browser(self, browser: str):
        browser = str(browser).title()
        if browser == 'Chrome':
            self.driver = webdriver.Chrome()
        elif browser == 'Firefox':
            self.driver = webdriver.Firefox()
        else:
            self.driver = 'type error'
        return self.driver

    def open_url(self, url: str):
        """
        获取url地址
        使窗口最大化
        设置隐性等待30秒
        :param url:
        :return:
        """
        self.driver.get(url)
        logging.info("Open url: {}".format(url))
        self.driver.maximize_window()
        logging.info("Maximizes the current window")
        self.driver.implicitly_wait(30)

    def quit(self):
        logging.info("Quit the browser!")
        self.driver.quit()

    def close(self):
        self.sleep(2)
        logging.info("Closing and quit the browser.")
        self.driver.close()

    def sleep(self, seconds):
        logging.info("Sleep for {} seconds".format(seconds))
        time.sleep(seconds)

    def is_displayed(self, element):
        """
        判断元素是否存在
        :param element: 元素对象(WebElement对象)
        :return:
        """
        value = element.is_displayed()
        return value

    def locate_element(self, loc):
        """
        定位元素方法
        :param loc: 定位器
        :return:
        """
        locator = loc

        try:
            # 注意:以下入参本身是元组,不需要加*
            element = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(locator))
            return element
        except NoSuchElementException:
            logging.error("Page not found the element: {}".format(locator))
        except Exception as e:
            logging.error("Locate Failed,Error:{}".format(e))

        return self.driver.find_element(locator)

    def send_value(self, loc: tuple, value: str, clear=True):
        """
        输入值
        :param loc: 定位器
        :param value: 输入数据
        :param clear: 是否清除
        :return:
        """
        element = self.locate_element(loc)

        if clear:
            element.clear()
        try:
            self.sleep(0.5)
            element.send_keys(value)
            logging.info("Input data: {}".format(value))
        except Exception as e:
            logging.error("Faild to input: {},Error: {}".format(value, e))

    def click(self, loc):
        """
        点击元素
        :param loc: 定位器
        :return:
        """
        element = self.locate_element(loc)
        try:
            element.click()
            logging.info("The element \' {} \' was clicked.".format(element.get_attribute('value')))
        except Exception as e:
            display = self.is_displayed(element)
            if display is True:
                self.sleep(3)
                element.click()
                logging.info('The element was clicked')
            else:
                self.save_image()
                logging.error('Failed to click the element, Error: {}'.format(e))

    def save_image(self):
        """
        截图
        :return:
        """
        date = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
        screen_name = os.path.join("/screenshots", '{}.png'.format(date))

        try:
            self.driver.get_screenshot_as_file(screen_name)
            logging.info("Had take screenshot and save to folder : /screenshots")
        except NameError as e:
            logging.error("Failed to take screenshot! Error: {}".format(e))

    def switch_frame(self, loc):
        """
        切入 frame
        :param loc: 定位器
        :return:
        """
        try:
            frame = self.locate_element(loc)
            self.driver.switch_to.frame(frame)
            logging.info("Successful to switch to frame!")
        except Exception as e:
            logging.error("Failed to switch to frame: {}, Error: {}".format(loc, e))

    def quit_frame(self):
        """
        退出当前frame
        :return:
        """
        self.driver.switch_to.default_content()
        logging.info("Successful to quit the current frame!")

    def get_attribute(self, loc, attribute):
        """
        获取元素属性
        :param loc: 定位器
        :param attribute: 属性名称
        :return:
        """
        try:
            element = self.locate_element(loc)
            return element.get_attribute(attribute)
        except Exception as e:
            logging.info("Failed to get attribute with %s" % e)
            self.save_image()
            raise

    def get_title(self):
        """
        获取title
        :return:
        """
        title = self.driver.title
        logging.info('Current page title is:%s' % title)
        return title

    def get_text(self, loc):
        """
        获取元素对象文本
        :param loc: 定位器
        :return:
        """
        try:
            text = self.locate_element(loc).text
            logging.info("Get the text: {}".format(text))
            return text
        except Exception as e:
            logging.error("Faild to Get the text: {} Error: {}".format(loc, e))

    def forward(self):
        """
        浏览器前进
        :return:
        """
        self.driver.forward()
        logging.info("Click forward on current page.")

    def back(self):
        """
        浏览器后退
        :return:
        """
        self.driver.back()
        logging.info("Click back on current page.")

    def execute_js(self, script):
        """
        执行 script 语句
        :param script: JS 语句
        :return:
        """
        try:
            self.driver.execute_script(script)
            logging.info("Successful to execute JS statements: {}" % script)
        except Exception as e:
            logging.error("Faild to execute JS statements, Error: {}".format(e))

    def assert_title(self, title):
        """
        断言title是否匹配
        :param title:
        :return:
        """
        if title != self.driver.title:
            logging.error("Assert Failed: {} is equal driver.title".format(title))
            return 0
        logging.info("Assert Passed: {} is not equal driver.title".format(title))
        return 1

    def assert_in_page_source(self, data):
        """
        断言数据是否存在page_source资源中
        :param data:
        :return:
        """
        if data not in self.driver.page_source:
            logging.error("Assert Failed: {} not in page_source".format(data))
            return 0
        logging.info("Assert Passed: {} in page_source".format(data))
        return 1


if __name__ == '__main__':
    ap = ActionPage()
    ap.open_url('https://www.baidu.com')
    locator = ('id', 'kw')
    ap.send_value(loc=locator, value='刘亦菲')
    locator2 = ('id', 'su')
    ap.click(locator2)
    ap.assert_in_page_source('刘亦菲')
    ap.close()

run.py

from openpyxl import load_workbook
from datetime import datetime
from action.action_page import ActionPage
import traceback
import os


def run_test(filename):
    wb = load_workbook(filename)

    # 获取sheet_names
    for sheetname in wb.sheetnames:
        ws = wb[sheetname]

        run = Run(ActionPage())

        # 获取最大行数
        ws_rows_max = ws.max_row
        # sheet.rows为生成器, 里面是每一行的数据,每一行又由一个tuple包裹
        idx = 2
        while idx <= ws_rows_max:
            # 关键字
            keyword = ws.cell(row=idx, column=3).value

            # 定位方式
            by = ws.cell(row=idx, column=4).value

            # 元素定位表达式
            loc = ws.cell(row=idx, column=5).value

            # 构造定位器
            if by and loc:
                locator = (by, loc)
            else:
                locator = None

            # 操作值
            data = ws.cell(row=idx, column=6).value

            if not data:
                data = None

            try:
                # 根据传入的方法名来确定每一步执行哪一个方法
                status = run.run_method(keyword, locator, data)

                # 获取当前系统时间,作为测试用例执行时间
                curr_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                ws.cell(row=idx, column=7).value = curr_time

                # 断言
                if str(keyword).startswith('assert'):
                    if status == 1:
                        # 如果执行无误则将执行结果写入Pass
                        ws.cell(row=idx, column=8).value = 'Passed'
                    elif status == 0:
                        # 如果执行错误则将执行结果写入Failed
                        ws.cell(row=idx, column=8).value = 'Failed'
                    wb.save(filename)
                else:
                    # 如果执行无误则将执行结果写入Pass
                    ws.cell(row=idx, column=8).value = 'Pass'
                    wb.save(filename)
            except Exception:
                # # 出现错误时截图保存到表格:只用到一个PIL模块,但PIL是Python2专有的,没有Python3版本。Python3应安装pillow,用的时候和PIL是一样的。
                # im = ImageGrab.grab()
                # image_path = f'screenshot/{idx-1}.png'
                #
                # im.save(image_path)
                # ws.cell(row=idx, column=10).value = image_path

                # 如果执行出现异常,测试结果写入 异常,同时将异常信息写入到表格中
                ws.cell(row=idx, column=8).value = '异常'

                # 写入异常信息
                ws.cell(row=idx, column=9).value = traceback.format_exc()
            idx += 1

        wb.save(filename)


class Run:

    def __init__(self, obj):
        self.obj = obj

    def run_method(self, method: str, locator=None, send_value=None):
        """
        反射机制
        :param method: 方法名称
        :param locator: 定位器
        :param send_value: 传入数据
        :return:
        """
        if hasattr(ActionPage, method):
            action_func = getattr(self.obj, method)
            if send_value:
                if locator:
                    return action_func(locator, send_value)
                else:
                    return action_func(send_value)
            else:
                if locator:
                    return action_func(locator)
                else:
                    return action_func()
        else:
            print("ActionPage not has attribute: {}".format(method))


if __name__ == '__main__':
    BASE_DIR = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
    DATA_DIR = os.path.join(BASE_DIR, "data")
    xlsx_path = os.path.join(DATA_DIR, "data.xlsx")
    print(xlsx_path)
    run_test(xlsx_path)

    # run = Run(ActionPage())
    # run.run_method('open_url', send_value="https://www.baidu.com")
    # run.run_method('send_value', ('id', 'kw'), "刘亦菲")
    # run.run_method('click', ('id', 'su'))
    # run.run_method('close')

EXCEL文件:data.xlsx

序号 操作步骤 关键词 定位方式 定位元素 传入数据 测试执行时间 测试结果 错误信息
1 打开百度 open_url https://www.baidu.com
2 定位输入框,输入:刘亦菲 send_value id kw 林俊杰
3 点击百度一下 click id su
4 断言数据存在于源码中 assert_in_page_source 林俊杰
5 关闭页面 close

你可能感兴趣的:(自动化测试,Python编程,自动化,python,单元测试)