Selenium+Pytest automated testing framework practice (Part 2)

Preface

This article continues from the previous article.

1. Simple learning element positioning

In my daily work, I have seen many students who right-click Copy Xpath to copy elements directly in the browser. The element expression obtained in this way is often not stable enough to be run in webdriver. Some minor changes in the front end will cause a NoSuchElementException error when the element cannot be located.

Therefore, in actual work and study, we should strengthen our element positioning capabilities and use relatively stable positioning syntax such as xpath and CSS selector as much as possible. Because the syntax of CSS selector is blunt and difficult to understand, it is not friendly to novices, and it lacks some positioning syntax compared to xpath. So we choose xpath for our element positioning syntax.

1.1xpath

1.1.1 Grammar rules

xpath is a language for finding information in XML documents.

 

1.1.2 Positioning tools

  • Advantages of chromath: This is a test positioning plug-in for Chrome browser, similar to firepath. I tried it and the overall feeling is very good. The friendliness towards noobs is very good. Disadvantages: FQ is required to install this plug-in.
  • The script recorded by Katalon recording tool will also contain information about positioning elements.
  • Write it yourself - I recommend this advantage: This is the method I recommend, because when you are proficient to a certain level, what you write will be more intuitive and concise, and you can quickly locate problems when running automated tests. Disadvantages: It requires a certain accumulation of xpath and CSS selector syntax, and it is not easy to get started.

2. Manage page elements

The test address chosen for this article is Baidu's homepage, so the corresponding elements are also from Baidu's homepage.

There is a directory page_element in the project framework design that is specially used to store files for positioning elements.

Through comparison of various configuration files, I chose the YAML file format here. It's easy to read and interactive.

We create a new search.yaml file in page_element.

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

The element positioning file has been created, now we need to read this file.

Create the readelement.py file in the common directory.

#!/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['搜索框'])

The special method __getitem__ is used to call any attribute and read the value in yaml.

In this way, we realize the storage and calling of positioned elements.

But there is still a question, how can we ensure that every element we write does not go wrong? Human errors are inevitable, but we can run a review of the file through the code. Not all problems can currently be discovered.

So we write a file, create the inspect.py file in the script file directory, and inspect all element yaml files.

#!/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()

Execute this file:

校验元素done!用时0.002秒!

As you can see, within a short period of time, we reviewed the filled-in YAML file.

Now the basic components we need have been roughly completed.

Next we will carry out the most important step, encapsulating selenium.

3. Encapsulate Selenium base class

In factory mode we write like this:

#!/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()

Very straightforward, simple, and clear.

Create the driver object, open the Baidu web page, search for selenium, click search, then stay for 5 seconds to view the results, and finally close the browser.

So why do we encapsulate selenium's methods? First of all, our above-mentioned relatively primitive method is basically not suitable for daily UI automated testing, because the actual operation of the UI interface is far more complicated. It may be due to network reasons or control reasons that our elements have not been displayed yet. Click or enter. Therefore, we need to encapsulate the selenium method and build a stable method through built-in explicit wait or certain conditional statements. Moreover, encapsulating selenium methods is conducive to daily code maintenance.

We create the webpage.py file in the page directory.

#!/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)

In the file, we have made a secondary encapsulation of methods such as explicitly waiting for selenium's click, send_keys, etc. Improved operation success rate.

Okay, we have completed about half of the POM model. Next we enter the page object.

4. Create page object

Create a searchpage.py file in the page_object directory.

#!/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['搜索按钮'])

In this file, we have encapsulated the steps of entering search keywords, clicking search, and searching for Lenovo.

and configured annotations.

We should develop the habit of writing comments in daily life, because after a while, without comments, the code will be difficult to read.

Okay, our page object is now complete. Next we start writing test cases. Before we start testing, let’s get familiar with the pytest testing framework.

5. Briefly understand Pytest

Open the official website of the pytest framework.

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


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

I think the official tutorial is not suitable for introductory reading, and there is no Chinese version. If you need a tutorial, you can find it yourself.

5.1pytest.ini

The configuration file in the pytest project can provide global control over the operations during the execution of pytest.

Create a new pytest.ini file in the project root directory.

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

addopts specifies other parameters during execution: --html=report/report.html --self-contained-html Generate pytest-html styled report -s Output the debugging information in our use case -q Test quietly -v You can output more detailed execution information of the use case, such as the file where the use case is located and the name of the use case, etc.

6. Write test cases

We will use pytest to write test cases.

Create the test_search.py ​​file in the TestCase directory.

#!/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'])

We have written it for testing.

  • pytest.fixture implements the same pre-start and post-cleanup decorators as unittest setup and teardown.
  • First test case:
    • We implemented the selenium keyword in Baidu, clicked the search button, and used regular expressions to find the source code of the result page in the search results. If the number returned is greater than 10, we consider it passed.
  • Second test case:
    • We implemented, search for selenium, and then assert whether all results in the search candidates have the selenium keyword.

Finally, we write an execution startup statement below.

At this time we should enter execution, but there is still a problem, we have not passed the driver yet.

7. conftest.py

We create a new conftest.py file in the project root directory.

#!/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 is the glue file of the test framework pytest, which uses the fixture method to encapsulate and pass the driver.

8. Execution use cases

Above we have written the entire framework and test cases.

We enter the home directory of the current project and execute the command:

pytest

Command line output:

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

You can see that the two use cases have been executed successfully.

A report.html file is generated in the report directory of the project.

This is the generated test report file.

9. Send email

After the project is completed, you need to send it to yourself or other people's mailboxes to view the results.

We write a module for sending emails.

Create a new send_mail.py file in the utils directory

#!/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()

Execute this file:

测试邮件发送成功!

You can see that the test report email has been sent successfully. Open your mailbox.

 

Email received successfully.

This demo project has been completed as a whole. Isn’t it very rewarding? You feel a sense of accomplishment the moment you send the email.

Summarize

Through this article, you must have an overall understanding of the pytest+selenium framework, and you will have reached another level on the road to automated testing. If you like it, you can like, collect, comment and follow me. Follow me to give you different surprises every day.

Finally: The following are supporting learning materials. For those who are doing [software testing], it should be the most comprehensive and complete preparation warehouse. This warehouse has also accompanied me through the most difficult journey. I hope it can also help you!

Software testing interview applet

A software test question bank that has been used by millions of people! ! ! Who is who knows! ! ! The most comprehensive interview test mini program on the Internet, you can use your mobile phone to answer questions, take the subway, bus, and roll it up!

Covers the following interview question sections:

1. Basic theory of software testing, 2. web, app, interface function testing, 3. network, 4. database, 5. linux

6. Web, app, interface automation, 7. Performance testing, 8. Programming basics, 9. HR interview questions, 10. Open test questions, 11. Security testing, 12. Computer basics

  How to obtain the full set of information: Click on the small card below to get it yourself

Guess you like

Origin blog.csdn.net/weixin_57794111/article/details/132918100