Práctica del marco de pruebas automatizadas de Selenium + Pytest (Parte 2)

Prefacio

Este artículo es una continuación del artículo anterior.

1. Posicionamiento simple del elemento de aprendizaje

En mi trabajo diario, he visto a muchos estudiantes hacer clic derecho en Copiar XPath para copiar elementos directamente en el navegador. La expresión del elemento obtenida de esta manera a menudo no es lo suficientemente estable para ejecutarse en webdriver. Algunos cambios menores en la interfaz causarán un error NoSuchElementException cuando no se puede ubicar el elemento.

Por lo tanto, en el trabajo y estudio real, debemos fortalecer nuestras capacidades de posicionamiento de elementos y utilizar una sintaxis de posicionamiento relativamente estable como xpath y selector CSS tanto como sea posible. Debido a que la sintaxis del selector CSS es contundente y difícil de entender, no es amigable para los principiantes y carece de cierta sintaxis de posicionamiento en comparación con xpath. Entonces elegimos xpath para nuestra sintaxis de posicionamiento de elementos.

1.1xruta

1.1.1 Reglas gramaticales

XPath es un lenguaje para buscar información en documentos XML.

 

1.1.2 Herramientas de posicionamiento

  • Ventajas de chromath: Este es un complemento de posicionamiento de prueba para el navegador Chrome, similar a firepath, lo probé y la sensación general es muy buena. La amabilidad hacia los novatos es muy buena. Desventajas: Se requiere FQ para instalar este complemento.
  • El guión grabado por la herramienta de grabación Katalon también contendrá información sobre el posicionamiento de elementos.
  • Escríbalo usted mismo: recomiendo esta ventaja: este es el método que recomiendo, porque cuando domine hasta cierto nivel, lo que escriba será más intuitivo y conciso, y podrá localizar rápidamente problemas al ejecutar pruebas automatizadas. Desventajas: requiere una cierta acumulación de sintaxis de selector xpath y CSS, y no es fácil comenzar.

2. Administrar elementos de la página

La dirección de prueba elegida para este artículo es la página de inicio de Baidu, por lo que los elementos correspondientes también provienen de la página de inicio de Baidu.

Hay un directorio page_element en el diseño del marco del proyecto que se utiliza especialmente para almacenar archivos para posicionar elementos.

A través de la comparación de varios archivos de configuración, elegí aquí el formato de archivo YAML. Es fácil de leer e interactivo.

Creamos un nuevo archivo search.yaml en page_element.

搜索框: "id==kw"
候选: "css==.bdsug-overflow"
搜索候选: "css==#form div li"
搜索按钮: "id==su"

Se ha creado el archivo de posicionamiento del elemento, ahora necesitamos leer este archivo.

Cree el archivo readelement.py en el directorio común.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import yaml
from config.conf import cm


class Element(object):
    """获取元素"""

    def __init__(self, name):
        self.file_name = '%s.yaml' % name
        self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
        if not os.path.exists(self.element_path):
            raise FileNotFoundError("%s 文件不存在!" % self.element_path)
        with open(self.element_path, encoding='utf-8') as f:
            self.data = yaml.safe_load(f)

    def __getitem__(self, item):
        """获取属性"""
        data = self.data.get(item)
        if data:
            name, value = data.split('==')
            return name, value
        raise ArithmeticError("{}中不存在关键字:{}".format(self.file_name, item))


if __name__ == '__main__':
    search = Element('search')
    print(search['搜索框'])

El método especial __getitem__ se utiliza para llamar a cualquier atributo y leer el valor en yaml.

De esta forma realizamos el almacenamiento y llamada de elementos posicionados.

Pero todavía queda una pregunta, ¿cómo podemos asegurarnos de que cada elemento que escribimos no salga mal? Los errores humanos son inevitables, pero podemos ejecutar una revisión del archivo a través del código. Actualmente no se pueden descubrir todos los problemas.

Entonces escribimos un archivo, creamos el archivo inspect.py en el directorio del archivo de script e inspeccionamos todos los archivos yaml de elementos.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import yaml
from config.conf import cm
from utils.times import running_time


@running_time
def inspect_element():
    """检查所有的元素是否正确
    只能做一个简单的检查
    """
    for files in os.listdir(cm.ELEMENT_PATH):
        _path = os.path.join(cm.ELEMENT_PATH, files)
        with open(_path, encoding='utf-8') as f:
            data = yaml.safe_load(f)
        for k in data.values():
            try:
                pattern, value = k.split('==')
            except ValueError:
                raise Exception("元素表达式中没有`==`")
            if pattern not in cm.LOCATE_MODE:
                raise Exception('%s中元素【%s】没有指定类型' % (_path, k))
            elif pattern == 'xpath':
                assert '//' in value,\
                    '%s中元素【%s】xpath类型与值不配' % (_path, k)
            elif pattern == 'css':
                assert '//' not in value, \
                    '%s中元素【%s]css类型与值不配' % (_path, k)
            else:
                assert value, '%s中元素【%s】类型与值不匹配' % (_path, k)


if __name__ == '__main__':
    inspect_element()

Ejecute este archivo:

校验元素done!用时0.002秒!

Como puede ver, en un corto período de tiempo, revisamos el archivo YAML completado.

Ahora los componentes básicos que necesitamos se han completado aproximadamente.

A continuación realizaremos el paso más importante, encapsular el selenio.

3. Encapsular la clase base de selenio

En modo fábrica escribimos así:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import time
from selenium import webdriver


driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_xpath("//input[@id='kw']").send_keys('selenium')
driver.find_element_by_xpath("//input[@id='su']").click()
time.sleep(5)
driver.quit()

Muy sencillo, sencillo y claro.

Cree el objeto del controlador, abra la página web de Baidu, busque selenio, haga clic en buscar, luego permanezca durante 5 segundos para ver los resultados y finalmente cierre el navegador.

Entonces, ¿por qué encapsulamos los métodos del selenio? En primer lugar, nuestro método relativamente primitivo mencionado anteriormente básicamente no es adecuado para las pruebas automatizadas diarias de la interfaz de usuario, porque el funcionamiento real de la interfaz de la interfaz de usuario es mucho más complicado. Puede deberse a razones de red o de control que nuestros elementos no hayan sido mostrado todavía. Haga clic o ingrese. Por lo tanto, necesitamos encapsular el método del selenio y construir un método estable mediante una espera explícita incorporada o ciertas declaraciones condicionales. Además, encapsular métodos de selenio es beneficioso para el mantenimiento diario del código.

Creamos el archivo webpage.py en el directorio de la página.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
selenium基类
本文件存放了selenium基类的封装方法
"""
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

from config.conf import cm
from utils.times import sleep
from utils.logger import log


class WebPage(object):
    """selenium基类"""

    def __init__(self, driver):
        # self.driver = webdriver.Chrome()
        self.driver = driver
        self.timeout = 20
        self.wait = WebDriverWait(self.driver, self.timeout)

    def get_url(self, url):
        """打开网址并验证"""
        self.driver.maximize_window()
        self.driver.set_page_load_timeout(60)
        try:
            self.driver.get(url)
            self.driver.implicitly_wait(10)
            log.info("打开网页:%s" % url)
        except TimeoutException:
            raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)

    @staticmethod
    def element_locator(func, locator):
        """元素定位器"""
        name, value = locator
        return func(cm.LOCATE_MODE[name], value)

    def find_element(self, locator):
        """寻找单个元素"""
        return WebPage.element_locator(lambda *args: self.wait.until(
            EC.presence_of_element_located(args)), locator)

    def find_elements(self, locator):
        """查找多个相同的元素"""
        return WebPage.element_locator(lambda *args: self.wait.until(
            EC.presence_of_all_elements_located(args)), locator)

    def elements_num(self, locator):
        """获取相同元素的个数"""
        number = len(self.find_elements(locator))
        log.info("相同元素:{}".format((locator, number)))
        return number

    def input_text(self, locator, txt):
        """输入(输入前先清空)"""
        sleep(0.5)
        ele = self.find_element(locator)
        ele.clear()
        ele.send_keys(txt)
        log.info("输入文本:{}".format(txt))

    def is_click(self, locator):
        """点击"""
        self.find_element(locator).click()
        sleep()
        log.info("点击元素:{}".format(locator))

    def element_text(self, locator):
        """获取当前的text"""
        _text = self.find_element(locator).text
        log.info("获取文本:{}".format(_text))
        return _text

    @property
    def get_source(self):
        """获取页面源代码"""
        return self.driver.page_source

    def refresh(self):
        """刷新页面F5"""
        self.driver.refresh()
        self.driver.implicitly_wait(30)

En el archivo, hemos realizado una encapsulación secundaria de métodos como esperar explícitamente el clic de Selenium, send_keys, etc. Tasa de éxito de operación mejorada.

Bien, hemos completado aproximadamente la mitad del modelo POM. A continuación ingresamos al objeto de la página.

4. Crear objeto de página

Cree un archivo searchpage.py en el directorio page_object.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from page.webpage import WebPage, sleep
from common.readelement import Element

search = Element('search')


class SearchPage(WebPage):
    """搜索类"""

    def input_search(self, content):
        """输入搜索"""
        self.input_text(search['搜索框'], txt=content)
        sleep()

    @property
    def imagine(self):
        """搜索联想"""
        return [x.text for x in self.find_elements(search['候选'])]

    def click_search(self):
        """点击搜索"""
        self.is_click(search['搜索按钮'])

En este archivo, hemos resumido los pasos para ingresar palabras clave de búsqueda, hacer clic en buscar y buscar Lenovo.

y anotaciones configuradas.

Deberíamos desarrollar el hábito de escribir comentarios en la vida diaria, porque después de un tiempo, sin comentarios, el código será difícil de leer.

Bien, nuestro objeto de página ya está completo. A continuación comenzamos a escribir casos de prueba. Antes de comenzar a probar, familiaricémonos con el marco de prueba de pytest.

5. Comprenda brevemente Pytest

Abra el sitio web oficial del marco pytest.

# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

Creo que el tutorial oficial no es adecuado para una lectura introductoria y no existe una versión china. Si necesita un tutorial, puede encontrarlo usted mismo.

5.1pytest.ini

El archivo de configuración en el proyecto pytest puede proporcionar control global sobre las operaciones durante la ejecución de pytest.

Cree un nuevo archivo pytest.ini en el directorio raíz del proyecto.

[pytest]
addopts = --html=report.html --self-contained-html

addopts especifica otros parámetros durante la ejecución: --html=report/report.html --self-contained-html Generar informe con estilo pytest-html -s Muestra la información de depuración en nuestro caso de uso -q Prueba silenciosamente -v Puede generar información más detallada información de ejecución del caso de uso, como el archivo donde se encuentra el caso de uso y el nombre del caso de uso, etc.

6. Escribe casos de prueba.

Usaremos pytest para escribir casos de prueba.

Cree el archivo test_search.py ​​​​en el directorio TestCase.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
import pytest
from utils.logger import log
from common.readconfig import ini
from page_object.searchpage import SearchPage


class TestSearch:
    @pytest.fixture(scope='function', autouse=True)
    def open_baidu(self, drivers):
        """打开百度"""
        search = SearchPage(drivers)
        search.get_url(ini.url)

    def test_001(self, drivers):
        """搜索"""
        search = SearchPage(drivers)
        search.input_search("selenium")
        search.click_search()
        result = re.search(r'selenium', search.get_source)
        log.info(result)
        assert result

    def test_002(self, drivers):
        """测试搜索候选"""
        search = SearchPage(drivers)
        search.input_search("selenium")
        log.info(list(search.imagine))
        assert all(["selenium" in i for i in search.imagine])


if __name__ == '__main__':
    pytest.main(['TestCase/test_search.py'])

Lo hemos escrito para probarlo.

  • pytest.fixture implementa los mismos decoradores previos al inicio y posteriores a la limpieza que la configuración y el desmontaje de la prueba unitaria.
  • Primer caso de prueba:
    • Implementamos la palabra clave selenio en Baidu, hicimos clic en el botón de búsqueda y usamos expresiones regulares para encontrar el código fuente de la página de resultados en los resultados de búsqueda. Si el número devuelto es mayor que 10, lo consideramos aprobado.
  • Segundo caso de prueba:
    • Implementamos, buscamos selenio y luego afirmamos si todos los resultados de los candidatos de búsqueda tienen la palabra clave selenio.

Finalmente, escribimos una declaración de inicio de ejecución a continuación.

En este momento deberíamos entrar en ejecución, pero todavía hay un problema, aún no hemos pasado el controlador.

7.conftest.py

Creamos un nuevo archivo conftest.py en el directorio raíz del proyecto.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import pytest
from py.xml import html
from selenium import webdriver


driver = None


@pytest.fixture(scope='session', autouse=True)
def drivers(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    """
    当测试失败的时候,自动截图,展示到html报告中
    :param item:
    """
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    report.description = str(item.function.__doc__)
    extra = getattr(report, 'extra', [])

    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = report.nodeid.replace("::", "_") + ".png"
            screen_img = _capture_screenshot()
            if file_name:
                html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
                       'onclick="window.open(this.src)" align="right"/></div>' % screen_img
                extra.append(pytest_html.extras.html(html))
        report.extra = extra


def pytest_html_results_table_header(cells):
    cells.insert(1, html.th('用例名称'))
    cells.insert(2, html.th('Test_nodeid'))
    cells.pop(2)


def pytest_html_results_table_row(report, cells):
    cells.insert(1, html.td(report.description))
    cells.insert(2, html.td(report.nodeid))
    cells.pop(2)


def pytest_html_results_table_html(report, data):
    if report.passed:
        del data[:]
        data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))


def _capture_screenshot():
    '''
    截图保存为base64
    :return:
    '''
    return driver.get_screenshot_as_base64()

conftest.py es el archivo adhesivo del marco de prueba pytest, que utiliza el método de fijación para encapsular y pasar el controlador.

8. Casos de uso de ejecución

Arriba hemos escrito el marco completo y los casos de prueba.

Ingresamos al directorio de inicio del proyecto actual y ejecutamos el comando:

pytest

Salida de la línea de comando:

Test session starts (platform: win32, Python 3.7.7, pytest 5.3.2, pytest-sugar 0.9.2)
cachedir: .pytest_cache
metadata: {'Python': '3.7.7', 'Platform': 'Windows-10-10.0.18362-SP0', 'Packages': {'pytest': '5.3.2', 'py': '1.8.0', 'pluggy': '0.13.1'}, 'Plugins': {'forked': '1.1.3', 'html': '2.0.1', 'metadata': '1.8.0', 'ordering': '0.6', 'rerunfailures': '8.0', 'sugar': '0.9.2', 'xdist': '1.31.0'}, 'JAVA_HOME': 'D:\\Program Files\\Java\\jdk1.8.0_131'}
rootdir: C:\Users\hoou\PycharmProjects\web-demotest, inifile: pytest.ini
plugins: forked-1.1.3, html-2.0.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0, sugar-0.9.2, xdist-1.31.0
collecting ... 
DevTools listening on ws://127.0.0.1:10351/devtools/browser/78bef34d-b94c-4087-b724-34fb6b2ef6d1

 TestCase\test_search.py::TestSearch.test_001 ✓                                                                                              50% █████     

 TestCase\test_search.py::TestSearch.test_002 ✓                                                                                             100% ██████████
------------------------------- generated html file: file://C:\Users\hoou\PycharmProjects\web-demotest\report\report.html -------------------------------- 

Results (12.90s):
       2 passed

Puede ver que los dos casos de uso se han ejecutado correctamente.

Se genera un archivo report.html en el directorio de informes del proyecto.

Este es el archivo del informe de prueba generado.

9. Enviar correo electrónico

Una vez completado el proyecto, debe enviárselo a usted mismo o a los buzones de correo de otras personas para ver los resultados.

Escribimos un módulo para enviar correos electrónicos.

Cree un nuevo archivo send_mail.py en el directorio de utilidades

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import zmail
from config.conf import cm


def send_report():
    """发送报告"""
    with open(cm.REPORT_FILE, encoding='utf-8') as f:
        content_html = f.read()
    try:
        mail = {
            'from': '[email protected]',
            'subject': '最新的测试报告邮件',
            'content_html': content_html,
            'attachments': [cm.REPORT_FILE, ]
        }
        server = zmail.server(*cm.EMAIL_INFO.values())
        server.send_mail(cm.ADDRESSEE, mail)
        print("测试邮件发送成功!")
    except Exception as e:
        print("Error: 无法发送邮件,{}!", format(e))


if __name__ == "__main__":
    '''请先在config/conf.py文件设置QQ邮箱的账号和密码'''
    send_report()

Ejecute este archivo:

测试邮件发送成功!

Puede ver que el correo electrónico del informe de prueba se envió correctamente. Abre tu buzón.

 

Correo electrónico recibido exitosamente.

Este proyecto de demostración se ha completado en su totalidad. ¿No es muy gratificante? Sientes una sensación de logro en el momento en que envías el correo electrónico.

Resumir

A través de este artículo, debe tener una comprensión general del marco pytest+selenium y habrá alcanzado otro nivel en el camino hacia las pruebas automatizadas. Si te gusta puedes darle me gusta, coleccionar, comentar y seguirme, sígueme para darte sorpresas diferentes cada día.

Finalmente: Los siguientes son los materiales de aprendizaje de apoyo, que deberían ser el almacén de preparación más completo y completo para los amigos que hacen [pruebas de software]. Este almacén también me ha acompañado en el viaje más difícil. ¡Espero que también pueda ayudarlo a usted!

Subprograma de entrevista de prueba de software

¡Un banco de preguntas de prueba de software que ha sido utilizado por millones de personas! ! ! ¡Quién es quién lo sabe! ! ! El miniprograma de prueba de entrevistas más completo de Internet. Puede usar su teléfono móvil para responder preguntas, tomar el metro, el autobús y enrollarlo.

Cubre las siguientes secciones de preguntas de la entrevista:

1. Teoría básica de pruebas de software, 2. web, aplicaciones, pruebas de función de interfaz, 3. red, 4. base de datos, 5. linux

6. Web, aplicaciones, automatización de interfaces, 7. Pruebas de rendimiento, 8. Conceptos básicos de programación, 9. Preguntas de la entrevista de recursos humanos, 10. Preguntas de prueba abiertas, 11. Pruebas de seguridad, 12. Conceptos básicos de informática

  Cómo obtener el conjunto completo de información: haga clic en la pequeña tarjeta a continuación para obtenerla usted mismo

Supongo que te gusta

Origin blog.csdn.net/weixin_57794111/article/details/132918100
Recomendado
Clasificación