当前位置:网站首页>Selenium+pytest automated test framework practice (Part 2)
Selenium+pytest automated test framework practice (Part 2)
2022-07-06 09:00:00 【Automated test seventh uncle】
Preface
This article continues from the previous article .
One 、 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 .
1.1xpath
1.1.1 Rule of grammar
xpath Is a door 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 |
1.1.2 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 The script recorded by the recording tool will also contain information about locating elements
- Write it yourself —— I recommend this 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 .
Two 、 Manage page elements
The test address selected for this article is Baidu homepage , 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 .
Search box : "id==kw"
The candidate : "css==.bdsug-overflow"
Search for candidates : "css==#form div li"
Search button : "id==su"
The element location file is created , Next, we need to read this file .
stay common Create... In the directory readelement.py file .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import yaml
from config.conf import cm
class Element(object):
""" Get elements """
def __init__(self, name):
self.file_name = '%s.yaml' % name
self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
if not os.path.exists(self.element_path):
raise FileNotFoundError("%s file does not exist !" % self.element_path)
with open(self.element_path, encoding='utf-8') as f:
self.data = yaml.safe_load(f)
def __getitem__(self, item):
""" get attribute """
data = self.data.get(item)
if data:
name, value = data.split('==')
return name, value
raise ArithmeticError("{} Keyword does not exist in :{}".format(self.file_name, item))
if __name__ == '__main__':
search = Element('search')
print(search[' Search box '])
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 .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import yaml
from config.conf import cm
from utils.times import running_time
@running_time
def inspect_element():
""" Check that all elements are correct
You can only do a simple check
"""
for files in os.listdir(cm.ELEMENT_PATH):
_path = os.path.join(cm.ELEMENT_PATH, files)
with open(_path, encoding='utf-8') as f:
data = yaml.safe_load(f)
for k in data.values():
try:
pattern, value = k.split('==')
except ValueError:
raise Exception(" There is no... In the element expression `==`")
if pattern not in cm.LOCATE_MODE:
raise Exception('%s Medium element 【%s】 No type specified ' % (_path, k))
elif pattern == 'xpath':
assert '//' in value,\
'%s Medium element 【%s】xpath Type does not match value ' % (_path, k)
elif pattern == 'css':
assert '//' not in value, \
'%s Medium element 【%s]css Type does not match value ' % (_path, k)
else:
assert value, '%s Medium element 【%s】 Type does not match value ' % (_path, k)
if __name__ == '__main__':
inspect_element()
Execute the document :
Check element done! when 0.002 second !
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.
3、 ... and 、 encapsulation Selenium Base class
In the factory model, we write this :
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_xpath("//input[@id='kw']").send_keys('selenium')
driver.find_element_by_xpath("//input[@id='su']").click()
time.sleep(5)
driver.quit()
Very straightforward , Simple , And clear .
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 .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
"""
selenium Base class
This document contains selenium Encapsulation method of base class
"""
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from config.conf import cm
from utils.times import sleep
from utils.logger import log
class WebPage(object):
"""selenium Base class """
def __init__(self, driver):
# self.driver = webdriver.Chrome()
self.driver = driver
self.timeout = 20
self.wait = WebDriverWait(self.driver, self.timeout)
def get_url(self, url):
""" Open the URL and verify """
self.driver.maximize_window()
self.driver.set_page_load_timeout(60)
try:
self.driver.get(url)
self.driver.implicitly_wait(10)
log.info(" Open the web page :%s" % url)
except TimeoutException:
raise TimeoutException(" open %s Timeout, please check the network or URL server " % url)
@staticmethod
def element_locator(func, locator):
""" Element locator """
name, value = locator
return func(cm.LOCATE_MODE[name], value)
def find_element(self, locator):
""" Look for a single element """
return WebPage.element_locator(lambda *args: self.wait.until(
EC.presence_of_element_located(args)), locator)
def find_elements(self, locator):
""" Find multiple identical elements """
return WebPage.element_locator(lambda *args: self.wait.until(
EC.presence_of_all_elements_located(args)), locator)
def elements_num(self, locator):
""" Get the number of the same elements """
number = len(self.find_elements(locator))
log.info(" The same elements :{}".format((locator, number)))
return number
def input_text(self, locator, txt):
""" Input ( Clear... Before entering )"""
sleep(0.5)
ele = self.find_element(locator)
ele.clear()
ele.send_keys(txt)
log.info(" Input text :{}".format(txt))
def is_click(self, locator):
""" Click on """
self.find_element(locator).click()
sleep()
log.info(" Click on the element :{}".format(locator))
def element_text(self, locator):
""" Get current text"""
_text = self.find_element(locator).text
log.info(" Get text :{}".format(_text))
return _text
@property
def get_source(self):
""" Get the page source code """
return self.driver.page_source
def refresh(self):
""" Refresh the page F5"""
self.driver.refresh()
self.driver.implicitly_wait(30)
In the file, we use explicit wait for 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 .
Four 、 Create page objects
stay page_object Create one in the directory searchpage.py file .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from page.webpage import WebPage, sleep
from common.readelement import Element
search = Element('search')
class SearchPage(WebPage):
""" Search class """
def input_search(self, content):
""" Enter search """
self.input_text(search[' Search box '], txt=content)
sleep()
@property
def imagine(self):
""" Search Lenovo """
return [x.text for x in self.find_elements(search[' The candidate '])]
def click_search(self):
""" Click on the search """
self.is_click(search[' Search button '])
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 .
5、 ... and 、 Simple understanding Pytest
open pytest The official website of the framework .
# content of test_sample.py
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
I don't think the official tutorial is suitable for introductory reading , And there is no Chinese version . If you need a tutorial, you can find it by yourself .
5.1pytest.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 .
[pytest]
addopts = --html=report.html --self-contained-html
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
6、 ... and 、 Write test cases
We will use pytest Write test cases .
stay TestCase Create... In the directory test_search.py file .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
import pytest
from utils.logger import log
from common.readconfig import ini
from page_object.searchpage import SearchPage
class TestSearch:
@pytest.fixture(scope='function', autouse=True)
def open_baidu(self, drivers):
""" Open the baidu """
search = SearchPage(drivers)
search.get_url(ini.url)
def test_001(self, drivers):
""" Search for """
search = SearchPage(drivers)
search.input_search("selenium")
search.click_search()
result = re.search(r'selenium', search.get_source)
log.info(result)
assert result
def test_002(self, drivers):
""" Test search candidates """
search = SearchPage(drivers)
search.input_search("selenium")
log.info(list(search.imagine))
assert all(["selenium" in i for i in search.imagine])
if __name__ == '__main__':
pytest.main(['TestCase/test_search.py'])
We'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 .
7、 ... and 、conftest.py
Let's create a new one in the root of the project conftest.py file .
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import pytest
from py.xml import html
from selenium import webdriver
driver = None
@pytest.fixture(scope='session', autouse=True)
def drivers(request):
global driver
if driver is None:
driver = webdriver.Chrome()
driver.maximize_window()
def fn():
driver.quit()
request.addfinalizer(fn)
return driver
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
"""
When the test fails , Automatic screenshot , Show to html In the report
:param item:
"""
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
report.description = str(item.function.__doc__)
extra = getattr(report, 'extra', [])
if report.when == 'call' or report.when == "setup":
xfail = hasattr(report, 'wasxfail')
if (report.skipped and xfail) or (report.failed and not xfail):
file_name = report.nodeid.replace("::", "_") + ".png"
screen_img = _capture_screenshot()
if file_name:
html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
'onclick="window.open(this.src)" align="right"/></div>' % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra
def pytest_html_results_table_header(cells):
cells.insert(1, html.th(' Use case name '))
cells.insert(2, html.th('Test_nodeid'))
cells.pop(2)
def pytest_html_results_table_row(report, cells):
cells.insert(1, html.td(report.description))
cells.insert(2, html.td(report.nodeid))
cells.pop(2)
def pytest_html_results_table_html(report, data):
if report.passed:
del data[:]
data.append(html.div(' The passed use case does not capture the log output .', class_='empty log'))
def _capture_screenshot():
'''
The screenshot is saved as base64
:return:
'''
return driver.get_screenshot_as_base64()
conftest.py The test framework pytest Glue file , It uses fixture Methods , Encapsulate and deliver driver.
8、 ... and 、 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 :
pytest
Command line output :
Test session starts (platform: win32, Python 3.7.7, pytest 5.3.2, pytest-sugar 0.9.2)
cachedir: .pytest_cache
metadata: {'Python': '3.7.7', 'Platform': 'Windows-10-10.0.18362-SP0', 'Packages': {'pytest': '5.3.2', 'py': '1.8.0', 'pluggy': '0.13.1'}, 'Plugins': {'forked': '1.1.3', 'html': '2.0.1', 'metadata': '1.8.0', 'ordering': '0.6', 'rerunfailures': '8.0', 'sugar': '0.9.2', 'xdist': '1.31.0'}, 'JAVA_HOME': 'D:\\Program Files\\Java\\jdk1.8.0_131'}
rootdir: C:\Users\hoou\PycharmProjects\web-demotest, inifile: pytest.ini
plugins: forked-1.1.3, html-2.0.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0, sugar-0.9.2, xdist-1.31.0
collecting ...
DevTools listening on ws://127.0.0.1:10351/devtools/browser/78bef34d-b94c-4087-b724-34fb6b2ef6d1
TestCase\test_search.py::TestSearch.test_001 * 50% █████
TestCase\test_search.py::TestSearch.test_002 * 100% ██████████
------------------------------- generated html file: file://C:\Users\hoou\PycharmProjects\web-demotest\report\report.html --------------------------------
Results (12.90s):
2 passed
You can see that 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 .
Nine 、 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
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import zmail
from config.conf import cm
def send_report():
""" Send report """
with open(cm.REPORT_FILE, encoding='utf-8') as f:
content_html = f.read()
try:
mail = {
'from': '[email protected]',
'subject': ' The latest test report email ',
'content_html': content_html,
'attachments': [cm.REPORT_FILE, ]
}
server = zmail.server(*cm.EMAIL_INFO.values())
server.send_mail(cm.ADDRESSEE, mail)
print(" Test email sent successfully !")
except Exception as e:
print("Error: Unable to send mail ,{}!", format(e))
if __name__ == "__main__":
''' Please go to config/conf.py File settings QQ Email account number and password '''
send_report()
Execute the document :
Test email sent successfully !
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 .
summary
Through this article , 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 . If you like, you can like collection comments and pay attention , Pay attention to the different surprises I give you every day .
边栏推荐
- LeetCode:394. 字符串解码
- LeetCode:836. Rectangle overlap
- LeetCode:673. Number of longest increasing subsequences
- TDengine 社区问题双周精选 | 第三期
- LeetCode:39. Combined sum
- LeetCode:394. String decoding
- 有效提高软件产品质量,就找第三方软件测评机构
- Marathon envs project environment configuration (strengthen learning and imitate reference actions)
- SimCLR:NLP中的对比学习
- Mongodb installation and basic operation
猜你喜欢
使用latex导出IEEE文献格式
Current situation and trend of character animation
Tcp/ip protocol
Ijcai2022 collection of papers (continuously updated)
一篇文章带你了解-selenium工作原理详解
Using pkgbuild:: find in R language_ Rtools check whether rtools is available and use sys The which function checks whether make exists, installs it if not, and binds R and rtools with the writelines
I-BERT
TP-LINK 企业路由器 PPTP 配置
[OC foundation framework] - string and date and time >
广州推进儿童友好城市建设,将探索学校周边200米设安全区域
随机推荐
Target detection - pytorch uses mobilenet series (V1, V2, V3) to build yolov4 target detection platform
KDD 2022 paper collection (under continuous update)
一改测试步骤代码就全写 为什么不试试用 Yaml实现数据驱动?
Intel Distiller工具包-量化实现1
@Jsonbackreference and @jsonmanagedreference (solve infinite recursion caused by bidirectional references in objects)
Promise 在uniapp的简单使用
TDengine 社区问题双周精选 | 第三期
requests的深入刨析及封装调用
What is the role of automated testing frameworks? Shanghai professional third-party software testing company Amway
IJCAI2022论文合集(持续更新中)
LeetCode:剑指 Offer 04. 二维数组中的查找
Leetcode: Jianzhi offer 04 Search in two-dimensional array
R language uses the principal function of psych package to perform principal component analysis on the specified data set. PCA performs data dimensionality reduction (input as correlation matrix), cus
LeetCode:221. 最大正方形
Using label template to solve the problem of malicious input by users
CUDA realizes focal_ loss
LeetCode:387. The first unique character in the string
可变长参数
Navicat premium create MySQL create stored procedure
自动化测试框架有什么作用?上海专业第三方软件测试公司安利