当前位置:网站首页>Selenium+pytest automated test framework practice

Selenium+pytest automated test framework practice

2022-07-05 22:59:00 Test Architect Bei fan

Preface

selenium automation + pytest The test framework

【 At the end of the article 】

What do you need in this chapter

  • definite python Basics —— At least understand classes and objects , Encapsulation inheritance

  • definite selenium Basics —— We do not speak selenium, No, you can see it yourself selenium Chinese Translation Network


Introduction to test framework

  • What are the advantages of a testing framework :

    • High code reuse rate , If you don't use a framework , The code will be redundant
    • You can assemble logs 、 The report 、 Some advanced functions such as email
    • Improve the maintainability of data such as elements , When the elements change , Just update the configuration file
    • Use more flexible PageObject Design patterns
  • The overall directory of the test framework

    Catalog / file explain Is it python package
    common This package contains common general classes , Such as reading configuration file yes
    config Profile directory yes
    logs Log directory
    page Yes selenium Deep packaging with square placement yes
    page_element Page element storage directory
    page_object Page object POM Design patterns , My understanding of this comes from Bitter leaf The blog of yes
    TestCase All test case sets yes
    utils Tool class yes
    script Script files
    conftest.pypytest Glue files
    pytest.inipytest The configuration file

   Such a simple frame structure is clear .

Knowing all this, let's start !

In the project, we first follow the above framework guidelines , Build every directory .

Be careful :python The package is yes , You need to add one __init__.py File to identify this directory as a python package .


First, manage time

First of all , Because many of our modules use time stamps , Or date string , So let's encapsulate time into a module .

Then let other modules call . stay utils New directory times.py modular

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">import</span> datetime
<span style="color:#7171bf">from</span> functools <span style="color:#7171bf">import</span> wraps


<span style="color:#7171bf">def</span> <span style="color:#61aeee">timestamp</span>():
    <span style="color:#98c379">""" Time stamp """</span>
    <span style="color:#7171bf">return</span> time.time()


<span style="color:#7171bf">def</span> <span style="color:#61aeee">dt_strftime</span>(fmt=<span style="color:#98c379">"%Y%m"</span>):
    <span style="color:#98c379">"""
    datetime Format time 
    :param fmt "%Y%m%d %H%M%S
    """</span>
    <span style="color:#7171bf">return</span> datetime.datetime.now().strftime(fmt)


<span style="color:#7171bf">def</span> <span style="color:#61aeee">sleep</span>(seconds=<span style="color:#d19a66">1.0</span>):
    <span style="color:#98c379">"""
     Sleep time 
    """</span>
    time.sleep(seconds)


<span style="color:#7171bf">def</span> <span style="color:#61aeee">running_time</span>(func):
    <span style="color:#98c379">""" Function run time """</span>

<span style="color:#61aeee">    @wraps(func)</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">wrapper</span>(*args, **kwargs):
        start = timestamp()
        res = func(*args, **kwargs)
        <span style="color:#7171bf">print</span>(<span style="color:#98c379">" Check element done! when %.3f second !"</span> % (timestamp() - start))
        <span style="color:#7171bf">return</span> res

    <span style="color:#7171bf">return</span> wrapper


<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    <span style="color:#7171bf">print</span>(dt_strftime(<span style="color:#98c379">"%Y%m%d%H%M%S"</span>))

</code></span></span>

Add configuration file

Configuration files are always an integral part of the project !

Centralize fixed information in fixed files

conf.py

There should be a file in the project to manage the overall Directory , I'm here, too python This file is set in the project .

In the project config directories creating conf.py file , All directory configuration information is written in this file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">from</span> selenium.webdriver.common.by <span style="color:#7171bf">import</span> By
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> dt_strftime


<span style="color:#7171bf">class</span> <span style="color:#61aeee">ConfigManager</span>(<span style="color:#61aeee">object</span>):
    <span style="color:#5c6370"><em>#  Project directory </em></span>
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    <span style="color:#5c6370"><em>#  Page element directory </em></span>
    ELEMENT_PATH = os.path.join(BASE_DIR, <span style="color:#98c379">'page_element'</span>)

    <span style="color:#5c6370"><em>#  The report file </em></span>
    REPORT_FILE = os.path.join(BASE_DIR, <span style="color:#98c379">'report.html'</span>)

    <span style="color:#5c6370"><em>#  The type of element location </em></span>
    LOCATE_MODE = {
        <span style="color:#98c379">'css'</span>: By.CSS_SELECTOR,
        <span style="color:#98c379">'xpath'</span>: By.XPATH,
        <span style="color:#98c379">'name'</span>: By.NAME,
        <span style="color:#98c379">'id'</span>: By.ID,
        <span style="color:#98c379">'class'</span>: By.CLASS_NAME
    }

    <span style="color:#5c6370"><em>#  Email messages </em></span>
    EMAIL_INFO = {
        <span style="color:#98c379">'username'</span>: <span style="color:#98c379">'[email protected]'</span>,  <span style="color:#5c6370"><em>#  Switch to your own address </em></span>
        <span style="color:#98c379">'password'</span>: <span style="color:#98c379">'QQ Email authorization code '</span>,
        <span style="color:#98c379">'smtp_host'</span>: <span style="color:#98c379">'smtp.qq.com'</span>,
        <span style="color:#98c379">'smtp_port'</span>: <span style="color:#d19a66">465</span>
    }

    <span style="color:#5c6370"><em>#  The recipient </em></span>
    ADDRESSEE = [
        <span style="color:#98c379">'[email protected]'</span>,
    ]

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">log_file</span>(self):
        <span style="color:#98c379">""" Log directory """</span>
        log_dir = os.path.join(self.BASE_DIR, <span style="color:#98c379">'logs'</span>)
        <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(log_dir):
            os.makedirs(log_dir)
        <span style="color:#7171bf">return</span> os.path.join(log_dir, <span style="color:#98c379">'{}.log'</span>.<span style="color:#7171bf">format</span>(dt_strftime()))

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">ini_file</span>(self):
        <span style="color:#98c379">""" The configuration file """</span>
        ini_file = os.path.join(self.BASE_DIR, <span style="color:#98c379">'config'</span>, <span style="color:#98c379">'config.ini'</span>)
        <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(ini_file):
            <span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">" The configuration file %s non-existent !"</span> % ini_file)
        <span style="color:#7171bf">return</span> ini_file


cm = ConfigManager()
<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    <span style="color:#7171bf">print</span>(cm.BASE_DIR)
</code></span></span>
Fold

Be careful :QQ Email authorization code Click to view the generation tutorial

This conf I copied the file Django Of settings.py The setting style of the file , But there are some differences .

In this file, we can set our own directories , You can also view your current directory .

Followed the agreement : Invariant constant names are all capitalized , Function names are lowercase . It looks beautiful as a whole .

config.ini

In the project config Create a new directory config.ini file , For the time being, we need to put what we need to test URL

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[HOST]</span>
<span style="color:#d19a66">HOST</span> = https://www.baidu.com
</code></span></span>

Read configuration file

The configuration file is created , Next, we need to read the configuration file to use the information in it .

We are common Create a new one in the directory readconfig.py file

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> configparser
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

HOST = <span style="color:#98c379">'HOST'</span>


<span style="color:#7171bf">class</span> <span style="color:#61aeee">ReadConfig</span>(<span style="color:#61aeee">object</span>):
    <span style="color:#98c379">""" The configuration file """</span>

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):
        self.config = configparser.RawConfigParser()  <span style="color:#5c6370"><em>#  When there is % Please use the symbol of Raw Read </em></span>
        self.config.read(cm.ini_file, encoding=<span style="color:#98c379">'utf-8'</span>)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">_get</span>(self, section, option):
        <span style="color:#98c379">""" obtain """</span>
        <span style="color:#7171bf">return</span> self.config.get(section, option)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">_set</span>(self, section, option, value):
        <span style="color:#98c379">""" to update """</span>
        self.config.<span style="color:#7171bf">set</span>(section, option, value)
        <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.ini_file, <span style="color:#98c379">'w'</span>) <span style="color:#7171bf">as</span> f:
            self.config.write(f)

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">url</span>(self):
        <span style="color:#7171bf">return</span> self._get(HOST, HOST)


ini = ReadConfig()

<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    <span style="color:#7171bf">print</span>(ini.url)
</code></span></span>

You can see that we use python Built in configparser The module of config.ini The file was read .

about url Extraction of values , I used higher-order grammar @property Property value , It's easier to write .


Record operation log

journal , Everyone should be familiar with the term , Is to record the actions in the code .

stay utils Create a new logger.py file .

This file is what we use to record some operation steps in the process of automated testing .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> logging
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm


<span style="color:#7171bf">class</span> <span style="color:#61aeee">Log</span>:
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):
        self.logger = logging.getLogger()
        <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            <span style="color:#5c6370"><em>#  Create a handle write file </em></span>
            fh = logging.FileHandler(cm.log_file, encoding=<span style="color:#98c379">'utf-8'</span>)
            fh.setLevel(logging.INFO)

            <span style="color:#5c6370"><em>#  Create a handle Output to console </em></span>
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)

            <span style="color:#5c6370"><em>#  Define the format of the output </em></span>
            formatter = logging.Formatter(self.fmt)
            fh.setFormatter(formatter)
            ch.setFormatter(formatter)

            <span style="color:#5c6370"><em>#  Add to handle</em></span>
            self.logger.addHandler(fh)
            self.logger.addHandler(ch)

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">fmt</span>(self):
        <span style="color:#7171bf">return</span> <span style="color:#98c379">'%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'</span>


log = Log().logger

<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    log.info(<span style="color:#98c379">'hello world'</span>)
</code></span></span>

Run the file in the terminal , You can see that the command line prints out :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">INFO	2020-12-01 16:00:05,467	[logger.py:38]	hello world
</code></span></span>

And then in the project logs The log file of the current month is generated under the directory .


Simple understanding POM Model

Because we'll talk about elements related , So first understand POM Model

Page Object Mode has the following advantages .

This view comes from 《Selenium automated testing —— be based on Python Language 》

  • Abstracting out objects can minimize the impact of developers modifying page code on testing , therefore , You just need to page
    Adjust to the elephant , It has no effect on the test ;
  • You can reuse part of the test code in multiple test cases ;
  • Test code becomes more readable 、 flexible 、 Maintainable

Page Object Pattern

  • basepage ——selenium Base class of , Yes selenium To encapsulate
  • pageelements—— Page elements , Extract the page elements separately , Put it in a file
  • searchpage —— Page object class , hold selenium Methods and page elements
  • testcase —— Use pytest For integration searchpage Write test cases

We can see from the picture above , adopt POM Model thinking , We put :

  • selenium Method
  • Page elements
  • Page object
  • The test case

The above four code bodies are split , Although doing it with few use cases will increase the code , But when there are many use cases, it makes a lot of sense , The amount of code will decrease significantly as use cases increase . Our maintenance code becomes more intuitive and obvious , Code readability has also become much better than factory mode , The code reuse rate has also been greatly improved .


Simple learning element positioning

In daily work , I've seen a lot of right clicking directly in the browser Copy Xpath Students who copy elements . The element expression thus obtained is placed in webdriver Running in the middle is often not stable enough , Some minor changes like the front end , Will cause the element to be unable to locate NoSuchElementException Report errors .

Therefore, in practical work and study, we should strengthen our element positioning ability , Use as much as possible xpath and CSS selector  This relatively stable positioning Syntax . because CSS selector Your grammar is hard to understand , It's very unfriendly to new people , And compared to xpath Some positioning syntax is missing . So we choose xpath Carry out our element positioning Syntax .

xpath#

Rule of grammar

Cuisine tutorial For in the xpath My introduction is a course in XML The language in which information is found in a document .

expression Introduce remarks
/ The root node Absolute path
// All children of the current node Relative paths
* Of all node elements
@ Prefix of attribute name @class   @id
*[1][] Subscript operator
[][ ] Predicate expression //input[@id='kw']
Following-sibling Siblings after the current node
preceding-sibling The sibling before the current node
parent The parent node of the current node

Positioning tools

  • chropath
    • advantage : This is a Chrome Browser test location plug-in , Be similar to firepath, I tried it out. The overall feeling is very good . He is very friendly to Xiaobai .
    • shortcoming : Installing this plug-in requires FQ.
  • Katalon Recording tools
    • The recorded script will also have the information of locating elements
  • Write it yourself —— I recommend this kind of
    • advantage : The way I recommend , Because when proficient to a certain extent , It will be more intuitive to write , And when there are problems running automated tests , Can quickly locate .
    • shortcoming : It needs to be xpath and CSS selector Grammar accumulation , It's not easy to get started .

Manage page elements

The test address selected for this tutorial is Baidu home page , So the corresponding elements are also Baidu home page .

There is a directory in the project framework design page_element It is specially used to store the files of locating elements .

Through the comparison of various configuration files , What I choose here is YAML File format . It's easy to read , Good interaction .

We are page_element Create a new one in search.yaml file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-yaml"><span style="color:#98c379"> Search box :</span> <span style="color:#98c379">"id==kw"</span>
<span style="color:#98c379"> The candidate :</span> <span style="color:#98c379">"css==.bdsug-overflow"</span>
<span style="color:#98c379"> Search for candidates :</span> <span style="color:#98c379">"css==#form div li"</span>
<span style="color:#98c379"> Search button :</span> <span style="color:#98c379">"id==su"</span>
</code></span></span>

The element location file is created , Next, we need to read this file .

stay common Create... In the directory readelement.py file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm


<span style="color:#7171bf">class</span> <span style="color:#61aeee">Element</span>(<span style="color:#61aeee">object</span>):
    <span style="color:#98c379">""" Get elements """</span>

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, name):
        self.file_name = <span style="color:#98c379">'%s.yaml'</span> % name
        self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
        <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(self.element_path):
            <span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"%s  file does not exist !"</span> % self.element_path)
        <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(self.element_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
            self.data = yaml.safe_load(f)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">__getitem__</span>(self, item):
        <span style="color:#98c379">""" get attribute """</span>
        data = self.data.get(item)
        <span style="color:#7171bf">if</span> data:
            name, value = data.split(<span style="color:#98c379">'=='</span>)
            <span style="color:#7171bf">return</span> name, value
        <span style="color:#7171bf">raise</span> ArithmeticError(<span style="color:#98c379">"{} Keyword does not exist in :{}"</span>.<span style="color:#7171bf">format</span>(self.file_name, item))


<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    search = Element(<span style="color:#98c379">'search'</span>)
    <span style="color:#7171bf">print</span>(search[<span style="color:#98c379">' Search box '</span>])
</code></span></span>

By special means __getitem__ Implementation calls any property , Read yaml The value in .

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

But there is a problem , How can we make sure that every element we write doesn't go wrong , Human error is inevitable , But we can run the review of files through code . At present, not all problems can be found .

So we write a document , stay script Create a script in the script file directory inspect.py file , For all elements yaml Document review .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> os
<span style="color:#7171bf">import</span> yaml
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> running_time


<span style="color:#61aeee">@running_time</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inspect_element</span>():
    <span style="color:#98c379">""" Check that all elements are correct 
     You can only do a simple check 
    """</span>
    <span style="color:#7171bf">for</span> files <span style="color:#7171bf">in</span> os.listdir(cm.ELEMENT_PATH):
        _path = os.path.join(cm.ELEMENT_PATH, files)
        <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
            data = yaml.safe_load(f)
        <span style="color:#7171bf">for</span> k <span style="color:#7171bf">in</span> data.values():
            <span style="color:#7171bf">try</span>:
                pattern, value = k.split(<span style="color:#98c379">'=='</span>)
            <span style="color:#7171bf">except</span> ValueError:
                <span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">" There is no... In the element expression `==`"</span>)
            <span style="color:#7171bf">if</span> pattern <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> cm.LOCATE_MODE:
                <span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">'%s Medium element 【%s】 No type specified '</span> % (_path, k))
            <span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'xpath'</span>:
                <span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">in</span> value,\
                    <span style="color:#98c379">'%s Medium element 【%s】xpath Type does not match value '</span> % (_path, k)
            <span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'css'</span>:
                <span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> value, \
                    <span style="color:#98c379">'%s Medium element 【%s]css Type does not match value '</span> % (_path, k)
            <span style="color:#7171bf">else</span>:
                <span style="color:#7171bf">assert</span> value, <span style="color:#98c379">'%s Medium element 【%s】 Type does not match value '</span> % (_path, k)


<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    inspect_element()
</code></span></span>

Execute the document :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell"> Check element done! when <span style="color:#d19a66">0.002</span> second !
</code></span></span>

You can see , In a very short time , We'll just fill in YAML The document was reviewed .

Now the basic components we need have been roughly completed .

Next, we will take the most important step , encapsulation selenium.


encapsulation Selenium Base class

In the factory model, we write this :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> time
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver


driver = webdriver.Chrome()
driver.get(<span style="color:#98c379">'https://www.baidu.com'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='kw']"</span>).send_keys(<span style="color:#98c379">'selenium'</span>)
driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='su']"</span>).click()
time.sleep(<span style="color:#d19a66">5</span>)
driver.quit()
</code></span></span>

Very straightforward , Simple , And clear .

establish driver object , Open Baidu web page , Search for selenium, Click on the search , Then stay 5 second , View results , Finally, close the browser .

Then why should we package selenium How about the method . First of all, we use the more primitive method mentioned above , Basically not suitable for doing UI Automated testing , Because in UI The actual operation of the interface is far more complex , Maybe because of the Internet , Or control reason , Our elements haven't been shown yet , Click or enter . So we need to encapsulate selenium Method , Through built-in explicit wait or certain conditional statements , To build a stable method . And put selenium Methods are encapsulated , It is conducive to normal code maintenance .

We are page directories creating webpage.py file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#98c379">"""
selenium Base class 
 This document contains selenium Encapsulation method of base class 
"""</span>
<span style="color:#7171bf">from</span> selenium.webdriver.support <span style="color:#7171bf">import</span> expected_conditions <span style="color:#7171bf">as</span> EC
<span style="color:#7171bf">from</span> selenium.webdriver.support.ui <span style="color:#7171bf">import</span> WebDriverWait
<span style="color:#7171bf">from</span> selenium.common.exceptions <span style="color:#7171bf">import</span> TimeoutException

<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm
<span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> sleep
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log


<span style="color:#7171bf">class</span> <span style="color:#61aeee">WebPage</span>(<span style="color:#61aeee">object</span>):
    <span style="color:#98c379">"""selenium Base class """</span>

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, driver):
        <span style="color:#5c6370"><em># self.driver = webdriver.Chrome()</em></span>
        self.driver = driver
        self.timeout = <span style="color:#d19a66">20</span>
        self.wait = WebDriverWait(self.driver, self.timeout)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">get_url</span>(self, url):
        <span style="color:#98c379">""" Open the URL and verify """</span>
        self.driver.maximize_window()
        self.driver.set_page_load_timeout(<span style="color:#d19a66">60</span>)
        <span style="color:#7171bf">try</span>:
            self.driver.get(url)
            self.driver.implicitly_wait(<span style="color:#d19a66">10</span>)
            log.info(<span style="color:#98c379">" Open the web page :%s"</span> % url)
        <span style="color:#7171bf">except</span> TimeoutException:
            <span style="color:#7171bf">raise</span> TimeoutException(<span style="color:#98c379">" open %s Timeout, please check the network or URL server "</span> % url)

<span style="color:#61aeee">    @staticmethod</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">element_locator</span>(func, locator):
        <span style="color:#98c379">""" Element locator """</span>
        name, value = locator
        <span style="color:#7171bf">return</span> func(cm.LOCATE_MODE[name], value)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">find_element</span>(self, locator):
        <span style="color:#98c379">""" Look for a single element """</span>
        <span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(
            EC.presence_of_element_located(args)), locator)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">find_elements</span>(self, locator):
        <span style="color:#98c379">""" Find multiple identical elements """</span>
        <span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(
            EC.presence_of_all_elements_located(args)), locator)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">elements_num</span>(self, locator):
        <span style="color:#98c379">""" Get the number of the same elements """</span>
        number = <span style="color:#7171bf">len</span>(self.find_elements(locator))
        log.info(<span style="color:#98c379">" The same elements :{}"</span>.<span style="color:#7171bf">format</span>((locator, number)))
        <span style="color:#7171bf">return</span> number

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">input_text</span>(self, locator, txt):
        <span style="color:#98c379">""" Input ( Clear... Before entering )"""</span>
        sleep(<span style="color:#d19a66">0.5</span>)
        ele = self.find_element(locator)
        ele.clear()
        ele.send_keys(txt)
        log.info(<span style="color:#98c379">" Input text :{}"</span>.<span style="color:#7171bf">format</span>(txt))

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">is_click</span>(self, locator):
        <span style="color:#98c379">""" Click on """</span>
        self.find_element(locator).click()
        sleep()
        log.info(<span style="color:#98c379">" Click on the element :{}"</span>.<span style="color:#7171bf">format</span>(locator))

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">element_text</span>(self, locator):
        <span style="color:#98c379">""" Get current text"""</span>
        _text = self.find_element(locator).text
        log.info(<span style="color:#98c379">" Get text :{}"</span>.<span style="color:#7171bf">format</span>(_text))
        <span style="color:#7171bf">return</span> _text

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">get_source</span>(self):
        <span style="color:#98c379">""" Get the page source code """</span>
        <span style="color:#7171bf">return</span> self.driver.page_source

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">refresh</span>(self):
        <span style="color:#98c379">""" Refresh the page F5"""</span>
        self.driver.refresh()
        self.driver.implicitly_wait(<span style="color:#d19a66">30</span>)
</code></span></span>
Fold

In the document, we use Explicit waiting Yes selenium Of click,send_keys Other methods , We did a second encapsulation . Improve the success rate of operation .

All right, we're done POM About half of the model . Next, let's go to the page object .


Create page objects

stay page_object Create one in the directory searchpage.py file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">from</span> page.webpage <span style="color:#7171bf">import</span> WebPage, sleep
<span style="color:#7171bf">from</span> common.readelement <span style="color:#7171bf">import</span> Element

search = Element(<span style="color:#98c379">'search'</span>)


<span style="color:#7171bf">class</span> <span style="color:#61aeee">SearchPage</span>(<span style="color:#61aeee">WebPage</span>):
    <span style="color:#98c379">""" Search class """</span>

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">input_search</span>(self, content):
        <span style="color:#98c379">""" Enter search """</span>
        self.input_text(search[<span style="color:#98c379">' Search box '</span>], txt=content)
        sleep()

<span style="color:#61aeee">    @property</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">imagine</span>(self):
        <span style="color:#98c379">""" Search Lenovo """</span>
        <span style="color:#7171bf">return</span> [x.text <span style="color:#7171bf">for</span> x <span style="color:#7171bf">in</span> self.find_elements(search[<span style="color:#98c379">' The candidate '</span>])]

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">click_search</span>(self):
        <span style="color:#98c379">""" Click on the search """</span>
        self.is_click(search[<span style="color:#98c379">' Search button '</span>])
</code></span></span>

In this document, we are interested in , Enter search keywords , Click on the search , Search Lenovo , It was packaged .

And configured comments .

At ordinary times, we should form the habit of writing notes , Because after a while , No comment , The code is hard to read .

Well, our page object is now complete . Now let's start writing test cases . Before we start the test, let's get familiar with pytest The test framework .


Simple understanding Pytest

open pytest The official website of the framework .pytest: helps you write better programs — pytest documentation

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em># content of test_sample.py</em></span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">inc</span>(x):
    <span style="color:#7171bf">return</span> x + <span style="color:#d19a66">1</span>


<span style="color:#7171bf">def</span> <span style="color:#61aeee">test_answer</span>():
    <span style="color:#7171bf">assert</span> inc(<span style="color:#d19a66">3</span>) == <span style="color:#d19a66">5</span>
</code></span></span>

I don't think the official tutorial is suitable for introductory reading , And there is no Chinese version .

pytest.ini

pytest Configuration files in the project , It can be done to pytest Perform global control of operations during execution .

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

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[pytest]</span>
<span style="color:#d19a66">addopts</span> = --html=report.html --self-contained-html
</code></span></span>
  • addopts Specify other parameter descriptions during execution :
    • --html=report/report.html --self-contained-html  Generate pytest-html Styled report
    • -s  Output the mode information in our use case
    • -q  Test quietly
    • -v  You can output more detailed execution information of use cases , For example, the file where the use case is located and the name of the use case

Write test cases

We will use pytest Write test cases .

stay TestCase Create... In the directory test_search.py file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> re
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log
<span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini
<span style="color:#7171bf">from</span> page_object.searchpage <span style="color:#7171bf">import</span> SearchPage


<span style="color:#7171bf">class</span> <span style="color:#61aeee">TestSearch</span>:
<span style="color:#61aeee">    @pytest.fixture(scope=<span style="color:#3388aa">'function'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
    <span style="color:#7171bf">def</span> <span style="color:#61aeee">open_baidu</span>(self, drivers):
        <span style="color:#98c379">""" Open the baidu """</span>
        search = SearchPage(drivers)
        search.get_url(ini.url)

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">test_001</span>(self, drivers):
        <span style="color:#98c379">""" Search for """</span>
        search = SearchPage(drivers)
        search.input_search(<span style="color:#98c379">"selenium"</span>)
        search.click_search()
        result = re.search(<span style="color:#98c379">r'selenium'</span>, search.get_source)
        log.info(result)
        <span style="color:#7171bf">assert</span> result

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">test_002</span>(self, drivers):
        <span style="color:#98c379">""" Test search candidates """</span>
        search = SearchPage(drivers)
        search.input_search(<span style="color:#98c379">"selenium"</span>)
        log.info(<span style="color:#7171bf">list</span>(search.imagine))
        <span style="color:#7171bf">assert</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])


<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:
    pytest.main([<span style="color:#98c379">'TestCase/test_search.py'</span>])

</code></span></span>

We'll write it when we use it .

  • pytest.fixture This implements and unittest Of setup,teardown The same pre start , Rear cleaning decorator .

  • The first test case :

    • We achieved in Baidu selenium keyword , And click the search button , And in the search results , Using regular search results page source code , The returned quantity is greater than 10 We think that through .
  • Second test case :

    • We did , Search for selenium, Then assert whether all the results in the search candidates have selenium keyword .

Finally, we write a statement to execute the startup below .

At this time, we should enter the execution , But there is a problem , We haven't put driver Pass on .

conftest.py

Let's create a new one in the root of the project conftest.py file .

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> pytest
<span style="color:#7171bf">from</span> py.xml <span style="color:#7171bf">import</span> html
<span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver


driver = <span style="color:#56b6c2">None</span>


<span style="color:#61aeee">@pytest.fixture(scope=<span style="color:#3388aa">'session'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">drivers</span>(request):
    <span style="color:#7171bf">global</span> driver
    <span style="color:#7171bf">if</span> driver <span style="color:#7171bf">is</span> <span style="color:#56b6c2">None</span>:
        driver = webdriver.Chrome()
        driver.maximize_window()

    <span style="color:#7171bf">def</span> <span style="color:#61aeee">fn</span>():
        driver.quit()

    request.addfinalizer(fn)
    <span style="color:#7171bf">return</span> driver


<span style="color:#61aeee">@pytest.hookimpl(hookwrapper=<span style="color:#56b6c2">True</span>)</span>
<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_runtest_makereport</span>(item):
    <span style="color:#98c379">"""
     When the test fails , Automatic screenshot , Show to html In the report 
    :param item:
    """</span>
    pytest_html = item.config.pluginmanager.getplugin(<span style="color:#98c379">'html'</span>)
    outcome = <span style="color:#7171bf">yield</span>
    report = outcome.get_result()
    report.description = <span style="color:#7171bf">str</span>(item.function.__doc__)
    extra = <span style="color:#7171bf">getattr</span>(report, <span style="color:#98c379">'extra'</span>, [])

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


<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_header</span>(cells):
    cells.insert(<span style="color:#d19a66">1</span>, html.th(<span style="color:#98c379">' Use case name '</span>))
    cells.insert(<span style="color:#d19a66">2</span>, html.th(<span style="color:#98c379">'Test_nodeid'</span>))
    cells.pop(<span style="color:#d19a66">2</span>)


<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_row</span>(report, cells):
    cells.insert(<span style="color:#d19a66">1</span>, html.td(report.description))
    cells.insert(<span style="color:#d19a66">2</span>, html.td(report.nodeid))
    cells.pop(<span style="color:#d19a66">2</span>)


<span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_html</span>(report, data):
    <span style="color:#7171bf">if</span> report.passed:
        <span style="color:#7171bf">del</span> data[:]
        data.append(html.div(<span style="color:#98c379">' The passed use case does not capture the log output .'</span>, class_=<span style="color:#98c379">'empty log'</span>))


<span style="color:#7171bf">def</span> <span style="color:#61aeee">_capture_screenshot</span>():
    <span style="color:#98c379">'''
     The screenshot is saved as base64
    :return:
    '''</span>
    <span style="color:#7171bf">return</span> driver.get_screenshot_as_base64()

</code></span></span>
Fold

conftest.py The test framework pytest Glue file , It uses fixture Methods , Encapsulate and deliver driver.


Execute use cases

Above, we have completed the whole framework and test cases .

We go to the home directory of the current project and execute the command :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">pytest
</code></span></span>

Command line output :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">Test session starts (platform: win32, Python <span style="color:#d19a66">3.7</span>.<span style="color:#d19a66">7</span>, pytest <span style="color:#d19a66">5.3</span>.<span style="color:#d19a66">2</span>, py<span style="color:#7171bf">test-sugar</span> <span style="color:#d19a66">0.9</span>.<span style="color:#d19a66">2</span>)
cachedir: .pytest_cache
metadata: {<span style="color:#98c379">'Python'</span>: <span style="color:#98c379">'3.7.7'</span>, <span style="color:#98c379">'Platform'</span>: <span style="color:#98c379">'Windows-10-10.0.18362-SP0'</span>, <span style="color:#98c379">'Packages'</span>: {<span style="color:#98c379">'pytest'</span>: <span style="color:#98c379">'5.3.2'</span>, <span style="color:#98c379">'py'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'pluggy'</span>: <span style="color:#98c379">'0.13.1'</span>}, <span style="color:#98c379">'Plugins'</span>: {<span style="color:#98c379">'forked'</span>: <span style="color:#98c379">'1.1.3'</span>, <span style="color:#98c379">'html'</span>: <span style="color:#98c379">'2.0.1'</span>, <span style="color:#98c379">'metadata'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'ordering'</span>: <span style="color:#98c379">'0.6'</span>, <span style="color:#98c379">'rerunfailures'</span>: <span style="color:#98c379">'8.0'</span>, <span style="color:#98c379">'sugar'</span>: <span style="color:#98c379">'0.9.2'</span>, <span style="color:#98c379">'xdist'</span>: <span style="color:#98c379">'1.31.0'</span>}, <span style="color:#98c379">'JAVA_HOME'</span>: <span style="color:#98c379">'D:\\Program Files\\Java\\jdk1.8.0_131'</span>}
rootdir: C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>, inifile: pytest.ini
plugins: forked<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">1.3</span>, html<span style="color:#56b6c2">-2</span>.<span style="color:#d19a66">0.1</span>, metadata<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">8.0</span>, ordering<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">6</span>, rerunfailures<span style="color:#56b6c2">-8</span>.<span style="color:#d19a66">0</span>, sugar<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">9.2</span>, xdist<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">31.0</span>
collecting ... 
DevTools listening on ws://<span style="color:#d19a66">127.0</span>.<span style="color:#d19a66">0.1</span>:<span style="color:#d19a66">10351</span>/devtools/browser/<span style="color:#d19a66">78</span>bef34d<span style="color:#56b6c2">-b94c-4087-b724-34fb6b2ef6d1</span>

 TestCase\test_search.py::TestSearch.test_001 *                                                                                              <span style="color:#d19a66">50</span>% █████     

 TestCase\test_search.py::TestSearch.test_002 *                                                                                             <span style="color:#d19a66">100</span>% ██████████
<span style="color:#56b6c2">-------------------------------</span> generated html file: file://C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>\report\report.html <span style="color:#56b6c2">--------------------------------</span> 

Results (<span style="color:#d19a66">12.90</span>s):
       <span style="color:#d19a66">2</span> passed
</code></span></span>

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

Project report A... Is generated in the directory report.html file .

This is the generated test report file .


Send E-mail

When the project is completed , You need to send it to yourself or someone else's email to check the results .

Send our email module .

stay utils Create a new send_mail.py file

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>
<span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>
<span style="color:#7171bf">import</span> zmail
<span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm


<span style="color:#7171bf">def</span> <span style="color:#61aeee">send_report</span>():
    <span style="color:#98c379">""" Send report """</span>
    <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.REPORT_FILE, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:
        content_html = f.read()
    <span style="color:#7171bf">try</span>:
        mail = {
            <span style="color:#98c379">'from'</span>: <span style="color:#98c379">'[email protected]'</span>,
            <span style="color:#98c379">'subject'</span>: <span style="color:#98c379">' The latest test report email '</span>,
            <span style="color:#98c379">'content_html'</span>: content_html,
            <span style="color:#98c379">'attachments'</span>: [cm.REPORT_FILE, ]
        }
        server = zmail.server(*cm.EMAIL_INFO.values())
        server.send_mail(cm.ADDRESSEE, mail)
        <span style="color:#7171bf">print</span>(<span style="color:#98c379">" Test email sent successfully !"</span>)
    <span style="color:#7171bf">except</span> Exception <span style="color:#7171bf">as</span> e:
        <span style="color:#7171bf">print</span>(<span style="color:#98c379">"Error:  Unable to send mail ,{}!"</span>, <span style="color:#7171bf">format</span>(e))


<span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">"__main__"</span>:
    <span style="color:#98c379">''' Please go to config/conf.py File settings QQ Email account number and password '''</span>
    send_report()
</code></span></span>

Execute the document :

<span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell"> Test email sent successfully !
</code></span></span>

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

Successfully received the email .

This demo Even if the project is completed as a whole ; Isn't it interesting , I have a great sense of achievement at the moment of sending email .

Last , You must have been right about pytest+selenium The framework has an overall cognition , On the road of automated testing, another step has been taken

原网站

版权声明
本文为[Test Architect Bei fan]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207052248137938.html