当前位置:网站首页>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.py pytest Glue files pytest.ini pytest 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>
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
andCSS 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>
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>
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
边栏推荐
- Editor extensions in unity
- C language - structural basis
- 2022 G3 boiler water treatment simulation examination and G3 boiler water treatment simulation examination question bank
- openresty ngx_ Lua regular expression
- Metaverse ape received $3.5 million in seed round financing from negentropy capital
- Distributed resource management and task scheduling framework yarn
- All expansion and collapse of a-tree
- 利用LNMP实现wordpress站点搭建
- 终于搞懂什么是动态规划的
- Paddle Serving v0.9.0 重磅发布多机多卡分布式推理框架
猜你喜欢
我把开源项目alinesno-cloud-service关闭了
Starting from 1.5, build a micro Service Framework -- log tracking traceid
VOT toolkit environment configuration and use
TypeError: this. getOptions is not a function
Tensor attribute statistics
如何快速理解复杂业务,系统思考问题?
d3dx9_ What if 29.dll is missing? System missing d3dx9_ Solution of 29.dll file
Exponential weighted average and its deviation elimination
點到直線的距離直線的交點及夾角
分布式解决方案之TCC
随机推荐
audiopolicy
Element operation and element waiting in Web Automation
一文搞定垃圾回收器
Record several frequently asked questions (202207)
d3dx9_ What if 29.dll is missing? System missing d3dx9_ Solution of 29.dll file
Masked Autoencoders Are Scalable Vision Learners (MAE)
【无标题】
Metaverse ape received $3.5 million in seed round financing from negentropy capital
Exponential weighted average and its deviation elimination
The countdown to the launch of metaverse ape is hot
Activate function and its gradient
Arduino 测量交流电流
Evolution of APK reinforcement technology, APK reinforcement technology and shortcomings
Why does the C# compiler allow an explicit cast between IEnumerable&lt; T&gt; and TAlmostAnything?
Three.js-01 入门
基于STM32的ADC采样序列频谱分析
终于搞懂什么是动态规划的
30 optimization skills about mysql, super practical
PLC编程基础之数据类型、变量声明、全局变量和I/O映射(CODESYS篇 )
Yiwen gets rid of the garbage collector