Step by step process and summary of building a data-driven testing framework
After learning from Mr. Wu to build an automated data-driven framework, when I was practicing on my own, I tried to expand and optimize the simple program bit by bit to implement this data-driven framework.
Let’s first talk about the purpose of building an automated testing framework:
One is to realize the automated operation of a function without having to manually perform some repetitive work every time. Use automated programs to replace manual work and improve efficiency, such as repeated execution of regression tests.
The second is to encapsulate the data and operations on the data, reduce the writing of repeated codes, and separate the test data and programs, so that automated testers do not need to pay too much attention to the code.
By maintaining the data well, you can carry out effective automated test execution, good reusability, and many other benefits. . .
Let me talk about the summary of the process after I built this framework from scratch:
First implement the function to be implemented in a file, then encapsulate a certain scattered function, call the encapsulated method in the main program, and divide the whole process into multiple steps. Each step only implements one encapsulation or optimization.
Just like placing building blocks, after completing a place, stand at this node and think about what I can encapsulate and optimize next, then take a step forward, and then consider what can be optimized on this basis.
Until the test framework is built, the process is first separated from the framework structure. Instead of thinking about what the framework is going to be like, we just think about what encapsulation and optimization can be done with the existing program, and how to change it to make it more usable. convenient,
It looks more organized, and then when I think about what parts can be extracted to make separate packages, I will compare some framework structures and what kind of packages this type of package can be placed under. I will optimize the package and classify it at the same time.
When we encapsulate the program and put it in the configuration file, we put it in the configuration file. In the end, you will find that what you get from this process is a testing framework.
After this process is completed, it will be much easier to understand the function of each module from the overall architecture of the test framework, because when you optimize and encapsulate it from scratch, you will finally get these modules.
Then when you learn test frameworks for different driver types, the principles are basically the same.
Here is the overall process of building this framework:
First of all, it is clear that what I want to achieve is the function of logging into the 126 mailbox and adding contacts.
Step 1--If I don't use the framework and list the code directly, how can I achieve it? Let's try to complete this first. Create
a new project in pycharm -dataDrivenTestPractice1.
Create a new TestScript package under the project.
Create a TestScript package under the TestScript package. python file
code:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
try:
wait=WebDriverWait(driver,10,0.2)#Show waiting
driver.switch_to.frame(driver.find_element_by_xpath("//iframe[@id='x-URS-iframe']"))#Switch to the frame element where the user name and password input box is located name=wait.until(lambda x
: x.find_element_by_xpath("//input[@placeholder='Email account or mobile phone number' and @name='email']"))
name.send_keys('xiaxiaoxu1987')
password=wait.until(lambda x:x.find_element_by_xpath ("//input[@placeholder='password']"))
password.send_keys('gloryroad')
submit=wait.until(lambda x:x.find_element_by_xpath("//a[@id='dologin']" ))
submit.click()
driver.switch_to.default_content()#Using switch_to_default_content() in pycharm will be strikethrough, and
time.sleep(5)
assert u"exit" in driver.page_source,"no exist in page_source"
address_book_link=wait.until(lambda x:x.find_element_by_xpath("//div[text()='通讯录']"))
address_book_link.click()
#assert u"新建联系人" in driver.page_source
add_contact_button=wait.until(lambda x:x.find_element_by_xpath("//span[text()='新建联系人']"))
add_contact_button.click()
contact_name=wait.until(lambda x:x.find_element_by_xpath("//a[@title='编辑详细姓名']/preceding-sibling::div/input"))
contact_name.send_keys(u"徐凤钗")
contact_email=wait.until(lambda x:x.find_element_by_xpath("//*[@id='iaddress_MAIL_wrap']//input"))
contact_email.send_keys("[email protected]")
contact_is_star=wait.until(lambda x:x.find_element_by_xpath("//span[text()='Set as star contact']/preceding-sibling::span/b"))
contact_is_star.click()
contact_mobile=wait.until(lambda x:x.find_element_by_xpath("//*[@id='iaddress_TEL_wrap']//dd//input"))
contact_mobile.send_keys('18141134488')
contact_other_info=wait.until(lambda x:x.find_element_by_xpath("//textarea"))
contact_other_info.send_keys('my wife')
contact_save_button=wait.until(lambda x:x.find_element_by_xpath("//span[.='确 定']"))
contact_save_button.click()
except TimeoutException, e:
# 捕获TimeoutException异常
print traceback.print_exc()
except NoSuchElementException, e:
# 捕获NoSuchElementException异常
print traceback.print_exc()
except Exception, e:
#Catch other exceptions
print traceback.print_exc()
result: logging in and adding contacts are ok
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript.py
Process finished with exit code 0
At this point, the first step has been implemented. Next, let’s look at how to separate the program and data in this program.
Let’s take a look at the main functions of the program:
Log in to your email-->Open the contact page-->Click the Add Contact button-->Enter the contact information in the pop-up window-->Click the Save button.
There is also the core of data-driven --> separation of data and programs
First of all, the steps of logging in to the mailbox can be extracted and packaged independently. The login module is tentatively designated.
Next, we will encapsulate the login function and call it in the program.
Step 2—Extract the login function, encapsulate it, and call it in the main program
Create a new PageObject package under the project
Create a new python file of LoginPage under the PageObject package to encapsulate the login operation.
Code:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
class LoginPage(object):
def __init__(self,driver):
self.driver=driver
def login(self):
try:
wait = WebDriverWait(driver, 10, 0.2) # Display waiting
self.driver.switch_to.frame(self.driver.find_element_by_xpath("//iframe[@id='x-URS-iframe']")) # Switch to the frame element where the username and password input boxes are located
name = wait.until(lambda x: x.find_element_by_xpath("//input[@placeholder='Email account or mobile phone number' and @name='email']"))
name.send_keys('xiaxiaoxu1987')
password = wait.until(lambda x: x.find_element_by_xpath("//input[@placeholder='密码']"))
password.send_keys('gloryroad')
submit = wait.until(lambda x: x.find_element_by_xpath("//a[@id='dologin']"))
submit.click()
self.driver.switch_to.default_content() # If you use switch_to_default_content() in pycharm, it will be strikethrough and out.
time.sleep(5)
assert u"退出" in self.driver.page_source, "no exist in page_source"
except TimeoutException, e:
#Catch TimeoutException exception
print traceback.print_exc()
except NoSuchElementException, e:
#Catch NoSuchElementException exception
print traceback.print_exc()
except Exception, e:
#Catch other exceptions
print traceback.print_exc()
if __name__=="__main__":
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http:\\mail.126.com")
login=LoginPage(driver)
login.login()
Result: Login successful
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/PageObject/Login_page.py
Process finished with exit code 0
At this point, the login function is encapsulated. Next, modify the main program and call LoginPage.
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
lp=LoginPage(driver)
wait = WebDriverWait(driver, 10, 0.2) # Display waiting
try:
lp.login()
address_book_link=wait.until(lambda x:x.find_element_by_xpath("//div[text()='通讯录']"))
address_book_link.click()
#assert u"New Contact" in driver.page_source
add_contact_button=wait.until(lambda x:x.find_element_by_xpath("//span[text()='New Contact']"))
add_contact_button.click()
contact_name=wait.until(lambda x:x.find_element_by_xpath("//a[@title='Edit detailed name']/preceding-sibling::div/input"))
contact_name.send_keys(u"徐凤钗")
contact_email=wait.until(lambda x:x.find_element_by_xpath("//*[@id='iaddress_MAIL_wrap']//input"))
contact_email.send_keys("[email protected]")
contact_is_star=wait.until(lambda x:x.find_element_by_xpath("//span[text()='Set as star contact']/preceding-sibling::span/b"))
contact_is_star.click()
contact_mobile=wait.until(lambda x:x.find_element_by_xpath("//*[@id='iaddress_TEL_wrap']//dd//input"))
contact_mobile.send_keys('18141134488')
contact_other_info=wait.until(lambda x:x.find_element_by_xpath("//textarea"))
contact_other_info.send_keys('my wife')
contact_save_button=wait.until(lambda x:x.find_element_by_xpath("//span[.='确 定']"))
contact_save_button.click()
except TimeoutException, e:
#Catch TimeoutException exception
print traceback.print_exc()
except NoSuchElementException, e:
#Catch NoSuchElementException exception
print traceback.print_exc()
except Exception, e:
#Catch other exceptions
print traceback.print_exc()
Result: Success
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
Process finished with exit code 0
From the perspective of the main program, the action of adding contacts can also be encapsulated. Let’s encapsulate this part first. If there are any problems later, we can go back and deduce it. First do it according to this idea, and then do it again.
Step 3—Encapsulate the function of adding contacts
Create a new AddressBook python file under the PageObject package
CodeAddressBook.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
class AddressBook(object):
def __init__(self,driver):
self.driver=driver
def add_contact(self):
try:
wait = WebDriverWait(self.driver, 10, 0.2) # Display waiting
address_book_link = wait.until(lambda x: x.find_element_by_xpath("//div[text()='通讯录']"))
address_book_link.click()
# assert u"New Contact" in driver.page_source
add_contact_button = wait.until(lambda x: x.find_element_by_xpath("//span[text()='New Contact']"))
add_contact_button.click()
contact_name = wait.until(
lambda x: x.find_element_by_xpath("//a[@title='Edit detailed name']/preceding-sibling::div/input"))
contact_name.send_keys(u"徐凤钗")
contact_email = wait.until(lambda x: x.find_element_by_xpath("//*[@id='iaddress_MAIL_wrap']//input"))
contact_email.send_keys("[email protected]")
contact_is_star = wait.until(
lambda x: x.find_element_by_xpath("//span[text()='Set as star contact']/preceding-sibling::span/b"))
contact_is_star.click()
contact_mobile = wait.until(lambda x: x.find_element_by_xpath("//*[@id='iaddress_TEL_wrap']//dd//input"))
contact_mobile.send_keys('18141134488')
contact_other_info = wait.until(lambda x: x.find_element_by_xpath("//textarea"))
contact_other_info.send_keys('my wife')
contact_save_button = wait.until(lambda x: x.find_element_by_xpath("//span[.='确 定']"))
contact_save_button.click()
except TimeoutException, e:
#Catch TimeoutException exception
print traceback.print_exc()
except NoSuchElementException, e:
#Catch NoSuchElementException exception
print traceback.print_exc()
except Exception, e:
#Catch other exceptions
print traceback.print_exc()
if __name__=="__main__":
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
lp=LoginPage(driver)
lp.login()
ab=AddressBook(driver)
ab.add_contact()
Result: Success
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/PageObject/AddressBook.py
Process finished with exit code 0
At this point, the function of adding contacts is roughly encapsulated. Now call it in the main program.
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from PageObject.AddressBook import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
loginPage=LoginPage(driver)
addressBook=AddressBook(driver)
loginPage.login()
addressBook.add_contact()
Result: Success
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
Process finished with exit code 0
At this point, the two functions of the program - logging in and adding contacts - have been roughly encapsulated. You can directly call the function in the main program. Let's review the main functions of the program:
Log in to your email-->Open the contact page-->Click the Add Contact button-->Enter the contact information in the pop-up window-->Click the Save button. There is also the core of data-driven --> separation of data and programs
Next, I want to try to extract part of the data. Let’s see how to separate this data?
From the perspective of the program, the part that calls the data is in the two modules just encapsulated, Login_Page and AddressBook. Let’s see how the program uses the data.
Login_page:
AddressBook:
When encapsulating these two functions, no parameters are left out, and they are written directly in the function. This is obviously relatively low, so how to do it? Let's try to replace the data that needs to be called in the function with parameters, and then pass the parameters in when calling.
Let’s first modify the login function and see how to change it to use parameters. At this time, I suddenly became confused. With so many different data, there is no way to use parameters. . .
If you want to use parameter transfer, you need to split the login function. You can see that there are several parts of the login function that can be encapsulated. For example, find the function of the frame element, find the function of the username input box element, and then find the single In the function of the element, you can pass data to the parameters for search.
Step 4—Encapsulate the function of finding elements
Modify LoginPage.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
class LoginPage(object):
def __init__(self,driver):
self.driver=driver
self.wait = WebDriverWait(self.driver, 10, 0.2) # Show waiting
def getFrame(self,locateType,locateExpression):
frame=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//iframe[@id='x-URS-iframe']"
return frame
def getUserName(self,locateType,locateExpression):
userName=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//input[@placeholder='Email account or mobile phone number' and @name='email']"
return userName
def getPassword(self,locateType,locateExpression):
password=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//input[@placeholder='密码']"
return password
def getLoginButton(self,locateType,locateExpression):
loginButton=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//a[@id='dologin']"
return loginButton
if __name__=="__main__":
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http:\\mail.126.com")
lp=LoginPage(driver)
driver.switch_to.frame(lp.getFrame("xpath","//iframe[@id='x-URS-iframe']"))
time.sleep(2)
lp.getUserName("xpath","//input[@placeholder='email account or mobile phone number' and @name='email']").clear()
lp.getUserName("xpath","//input[@placeholder='email account or mobile phone number' and @name='email']").send_keys("xiaxiaoxu1987")
lp.getPassword("xpath","//input[@placeholder='密码']").send_keys("gloryroad")
lp.getLoginButton("xpath","//a[@id='dologin']").click()
driver.switch_to.default_content()
time.sleep(5)
assert u"退出" in driver.page_source, "no exist in page_source"
Result: Login successful
At this point, although the data is passed as a parameter to each function that finds elements, there is a problem.
When calling, you need to manually enter the data needed to find the element, which is more troublesome. If you enter a wrong letter, the element will not be found. How to do this?
It needs to be re-encapsulated, put the data in a file, and then encapsulate the method to read the data from the file. In this way, you need to create a configuration file to store the data, and then create a new python file to encapsulate the method of reading the configuration file.
Step 5—Put the data into the configuration file, and then encapsulate the method to read the data from the file
Create a new package called Conf under the project (used to store configuration-related files):
Create a new file under the Conf package: PageObjectRepository.ini
PageObjectRepository.ini:
[126mail_login]
login_page.frame=id>x-URS-iframe
login_page.username=xpath>//input[@name='email']
login_page.password=xpath>//input[@name='password']
login_page.loginbutton=id>dologin
[126mail_homepage]
home_page.addressbook=xpath>//div[text()='Address Book']
[126mail_addcontactspage]
addcontacts_page.createcontactsbtn=xpath>//span[text()='New Contact']
addcontacts_page.contactpersonname=xpath>//a[@title='Edit detailed name']/preceding-sibling::div/input
addcontacts_page.contactpersonemail=xpath>//*[@id='iaddress_MAIL_wrap']//input
addcontacts_page.starcontacts=xpath>//span[text()='Set as star contact']/preceding-sibling::span/b
addcontacts_page.contactpersonmobile=xpath>//*[@id='iaddress_TEL_wrap']//dd//input
addcontacts_page.contactpersoncomment=xpath>//textarea
addcontacts_page.savecontaceperson=xpath>//span[.='OK']
Create a new Util package under the project. This package is generally used to store tool classes, such as reading configuration files, finding element methods, excel operation classes, time classes, log methods and operations, screenshots, report templates, etc.
Create a new python file under the Util package: ParsePageObjectRepository.py to encapsulate the class for reading configuration files
#encoding=utf-8
#author-Xia Xiaoxu
from ConfigParser import ConfigParser
class ParsePageObjectRepositoryConfig(object):
def __init__(self,config_path):
self.cf=ConfigParser()#Generate parser
self.cf.read(config_path)
def getItemSection(self,sectionName):
print self.cf.items(sectionName)
return dict(self.cf.items(sectionName))
def getOptionValue(self,sectionName,optionName):#Return a dictionary
print self.cf.get(sectionName,optionName)
return self.cf.get(sectionName,optionName)
if __name__=='__main__':
pp=ParsePageObjectRepositoryConfig("D:\\test\\dataDrivenTestPractice1\\Conf\\PageObjectRepository.ini")
print pp.getItemSection("126mail_login")
print pp.getOptionValue("126mail_login","login_page.username")
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/ParsePageObjectRepository.py
[('login_page.frame', 'id>x-URS-iframe'), ('login_page.username', "xpath>//input[@name='email']"), ('login_page.password', "xpath>//input[@name='password']"), ('login_page.loginbutton', 'id>dologin')]
{'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
xpath>//input[@name='email']
xpath>//input[@name='email']
Process finished with exit code 0
You can see that when the read function is called in the test code, the path to the configuration file must be manually entered in the parameter. It is a long character. If the path is modified, it needs to be modified here. Since it is all encapsulated here, it is easy to modify it here. Be diligent and configure the path in a variable.
Generally, the variable storing the file path should be placed in the var.py file under the ProjectVar package. After that, the paths of all configuration files are maintained in the form of variables in this file. The relative path is used to obtain the path, which is more secure.
Step 6—Use variables to store the path to the configuration file
Create a new ProjectVar package under the project
Create a new python file of var.py under the ProjectVar package
var.py:
#encoding=utf-8
#author-Xia Xiaoxu
import us
#Get the absolute path of the directory where the project is located
project_path=os.path.dirname(os.path.dirname(__file__))
#Get the absolute path of the page object library file
page_object_repository_path=project_path.decode("utf-8")+u"/conf/PageObjectRepository.ini"
if __name__=='__main__':
#Test code
print "project_path:", project_path
print "page_object_repository_path:",page_object_repository_path
print os.path.exists(project_path)
print os.path.exists(page_object_repository_path)
result:
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/ProjectVar/var.py
project_path: D:/test/dataDrivenTestPractice1
page_object_repository_path: D:/test/dataDrivenTestPractice1/conf/PageObjectRepository.ini
True
True
Process finished with exit code 0
Now test the execution of reading the configuration file in ParsePageObjectRepository.py
ParsePageObjectRepository.py:
#encoding=utf-8
#author-Xia Xiaoxu
from ConfigParser import ConfigParser
from ProjectVar.var import page_object_repository_path#Newly added
class ParsePageObjectRepositoryConfig(object):
def __init__(self):#Removed the config_path parameter
self.cf=ConfigParser()#Generate parser
self.cf.read(page_object_repository_path)#Replace directly with variables
def getItemSection(self,sectionName):
print self.cf.items(sectionName)
return dict(self.cf.items(sectionName))
def getOptionValue(self,sectionName,optionName):#Return a dictionary
print self.cf.get(sectionName,optionName)
return self.cf.get(sectionName,optionName)
if __name__=='__main__':
pp=ParsePageObjectRepositoryConfig()#The address variable of the configuration file has been initialized in the constructor
print pp.getItemSection("126mail_login")
print pp.getOptionValue("126mail_login","login_page.username")
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/ParsePageObjectRepository.py
[('login_page.frame', 'id>x-URS-iframe'), ('login_page.username', "xpath>//input[@name='email']"), ('login_page.password', "xpath>//input[@name='password']"), ('login_page.loginbutton', 'id>dologin')]
{'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
xpath>//input[@name='email']
xpath>//input[@name='email']
Next, fix the calls in the login.py file and replace all the parameters that need to be entered manually with reading the configuration file.
Modify login.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from Util.ParsePageObjectRepository import *#新加
from ProjectVar.var import *# Newly added
class LoginPage(object):
def __init__(self,driver):
self.driver=driver
self.parse_config_file=ParsePageObjectRepositoryConfig()# Newly added, obtain configuration file information
self.login_page_items=self.parse_config_file.getItemSection("126mail_login")#新加
print "self.login_page_items:",self.login_page_items
self.wait = WebDriverWait(self.driver, 10, 0.2) # Show waiting
def getFrame(self):#Remove the parameters and process them inside
locateType,locateExpression=self.login_page_items['login_page.frame'].split('>')#id>x-URS-iframe
frame=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//iframe[@id='x-URS-iframe']"
return frame
def getUserName(self): #Remove the parameters and process them inside
locateType, locateExpression = self.login_page_items['login_page.username'].split('>')#xpath>//input[@name='email']
userName=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//input[@placeholder='Email account or mobile phone number' and @name='email']"
return userName
def getPassword(self): #Remove the parameters and process them inside
locateType, locateExpression = self.login_page_items['login_page.password'].split('>')#xpath>//input[@name='password']
password=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//input[@placeholder='密码']"
return password
def getLoginButton(self): #Remove the parameters and process them inside
locateType, locateExpression = self.login_page_items['login_page.loginbutton'].split('>') #id>dologin
loginButton=self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression))#"//a[@id='dologin']"
return loginButton
if __name__=="__main__":
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http:\\mail.126.com")
lp=LoginPage(driver)
driver.switch_to.frame(lp.getFrame())
time.sleep(2)
lp.getUserName().clear()
lp.getUserName().send_keys("xiaxiaoxu1987")
lp.getPassword().send_keys("gloryroad")
lp.getLoginButton().click()
driver.switch_to.default_content()
time.sleep(5)
assert u"退出" in driver.page_source, "no exist in page_source"
So far, we have encapsulated a lot of things and put the login data in the configuration file to read it. However, there is still a long way to go before perfection, but it doesn’t matter. As long as we have a clear idea, perfection is only a matter of time. Now let’s Let’s see if there are any problems with this framework that need to be optimized.
You can see that the following two points can be optimized:
1-In the method of obtaining login page elements in the login_page.py file, the following steps for finding elements can be encapsulated,
self.wait.until(lambda x: x.find_element(by=locateType,value=locateExpression)), there is no need to write this in every method of finding elements, and this step of finding elements is in the process of adding contacts to the account It will also be used in functions. It will be much more convenient to encapsulate the method of finding elements.
2-Secondly, when performing the login operation, we enter the username and password information manually. This can also be encapsulated. How to encapsulate it? Each method in the initial login.py file gets the login information. For the required elements, we manually call these methods when performing the login operation. After obtaining the elements, enter characters or click to complete the login operation. Then these steps can be performed in another file. Encapsulation, to put it bluntly, is to encapsulate the methods of obtaining each element in login_page to realize the login function. You don’t need to organize the scattered methods by yourself every time. Put it in a box. The name of the box is login. Every time Just execute this box, don't worry about the details inside, all packaging is the same.
Step 7—Encapsulate the function of finding elements and further encapsulate the login function
As you can see in the initial frame structure, the method of finding elements can be placed in the Util package
Create a new ObjectMap.py file in the Util package
ObjectMap.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium.webdriver.support.ui import WebDriverWait
#Get a single element object
def getElement(driver,locateType,locateExpression):
try:
element=WebDriverWait(driver,10).until(lambda x:x.find_element(by=locateType,value=locateExpression))
return element
except Exception,e:
raise e
#Get multiple identical page element objects and return them as a list
def getElements(driver,locateType,locatorExpression):
try:
elements=WebDriverWait(driver,5).until(lambda x:x.find_elements(by=locateType,value=locateExpression))
return elements
except Exception,e:
raise e
if __name__=="__main__":
#Test code
from selenium import webdriver
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://www.baidu.com')
searchBox=getElement(driver,"xpath","//input[@id='kw']")
driver.quit()
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/ObjectMap.py
Process finished with exit code 0
At this point, the method of finding elements is encapsulated. Let’s modify Login_Page.py to call the ObjectMap method to implement each method of finding elements, and encapsulate the login action.
Login_page.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from Util.ParsePageObjectRepository import *
from ProjectVar.var import *
from Util.ObjectMap import *
class LoginPage(object):
def __init__(self,driver):
self.driver=driver
self.parse_config_file=ParsePageObjectRepositoryConfig()
self.login_page_items=self.parse_config_file.getItemSection("126mail_login")
print "self.login_page_items:",self.login_page_items
self.wait = WebDriverWait(self.driver, 10, 0.2) # Show waiting
def getFrame(self):
locateType,locateExpression=self.login_page_items['login_page.frame'].split('>')#id>x-URS-iframe
frame=getElement(self.driver,locateType,locateExpression)#"//iframe[@id='x-URS-iframe']"
return frame
def getUserName(self):
locateType, locateExpression = self.login_page_items['login_page.username'].split('>')#xpath>//input[@name='email']
userName=getElement(self.driver,locateType,locateExpression)#"//input[@placeholder='Email account or mobile phone number' and @name='email']"
return userName
def getPassword(self):
locateType, locateExpression = self.login_page_items['login_page.password'].split('>')#xpath>//input[@name='password']
password=getElement(self.driver,locateType,locateExpression)#"//input[@placeholder='密码']"
return password
def getLoginButton(self):
locateType, locateExpression = self.login_page_items['login_page.loginbutton'].split('>') #id>dologin
loginButton=getElement(self.driver,locateType,locateExpression)#"//a[@id='dologin']"
return loginButton
def login(self):
self.driver.switch_to.frame(self.getFrame())
self.getUserName().clear()
self.getUserName().send_keys("xiaxiaoxu1987")
self.getPassword().send_keys("gloryroad")
self.getLoginButton().click()
if __name__=="__main__":
#Test code
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http:\\mail.126.com")
lp=LoginPage(driver)
lp.login()
Result: Can log in
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/PageObject/Login_Page.py
self.login_page_items: {'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
Process finished with exit code 0
Next, we move the login function package out of Login_page.py and store it as a separate file to separate the methods and calls. If there are changes in the future, just modify login_page.py.
Step 8—Encapsulate the login method separately
Create a new Action package under the project
Create a new login.py under the Action package
Login.py:
#encoding=utf-8
#author-Xia Xiaoxu
from PageObject.Login_Page import *
from selenium import webdriver
def login(driver,username,password):
lp=LoginPage(driver)
driver.switch_to.frame(lp.getFrame())
lp.getUserName().clear()
lp.getUserName().send_keys(username)
lp.getPassword().send_keys(password)
lp.getLoginButton().click()
if __name__=='__main__':
#Test code
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http://mail.126.com")
login(driver,"xiaxiaoxu1987","gloryroad")
Result: Login successful
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Action/login.py
self.login_page_items: {'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
Process finished with exit code 0
At this point, the login function is basically encapsulated, and the data and program are separated. Let's take a look at the encapsulation of adding contacts to realize the separation of data and program step by step. The operations of adding contacts have been put into AddressBook.py before. , but the situation is the same as the login_page.py at the beginning. The element search operation and configuration file data reading have not been done yet. Let’s start doing this.
Step 9—Encapsulate the add contact operation
Idea:
In AddressBook.py, encapsulate each element needed to add a contact page into a method, find the xpath data required for each element and put it in the configuration file, and then use a function to call the methods of these elements to implement input characters or click operations. Implement the function of adding contacts
AddressBook.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from Action.login import *
class AddressBook(object):
def __init__(self,driver):
self.driver=driver
self.parse_config_file=ParsePageObjectRepositoryConfig()
self.address_book_page=self.parse_config_file.getItemsFromSection("126mail_homepage")
print "self.address_book_page:",self.address_book_page
self.address_book_page_itmes=self.parse_config_file.getItemsFromSection("126mail_addcontactspage")
print "self.address_book_page_itmes:", self.address_book_page_itmes
def address_book_link(self):
locateType,locateExpression = self.address_book_page['home_page.addressbook'].split(">")
print locateType,locateExpression
return getElement(self.driver,"xpath","//div[text()='Address Book']")
def add_contact_button(self):
locateType,locateExpression=self.address_book_page_itmes['addcontacts_page.createcontactsbtn'].split(">")
print locateType,locateExpression
return getElement(self.driver,locateType,locateExpression)
def contact_name(self):
locateType,locateExpression=self.address_book_page_itmes['addcontacts_page.contactpersonname'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
def contact_email(self):
locateType, locateExpression = self.address_book_page_itmes['addcontacts_page.contactpersonemail'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
def contact_is_star(self):
locateType, locateExpression = self.address_book_page_itmes['addcontacts_page.starcontacts'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
def contact_mobile(self):
locateType, locateExpression = self.address_book_page_itmes['addcontacts_page.contactpersonmobile'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
def contact_other_info(self):
locateType, locateExpression = self.address_book_page_itmes['addcontacts_page.contactpersoncomment'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
def contact_save_button(self):
locateType, locateExpression = self.address_book_page_itmes['addcontacts_page.savecontaceperson'].split(">")
print locateType, locateExpression
return getElement(self.driver, locateType, locateExpression)
if __name__=="__main__":
driver = webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get("http://mail.126.com")
login(driver, "xiaxiaoxu1987", "gloryroad")
addressbook=AddressBook(driver)
time.sleep(5)
addressbook.address_book_link().click()
addressbook.add_contact_button().click()
addressbook.contact_name().send_keys("sam")
addressbook.contact_email().send_keys("[email protected]")
addressbook.contact_is_star().click()
addressbook.contact_mobile().send_keys("18142244444")
addressbook.contact_other_info().send_keys("myself")
addressbook.contact_save_button().click()
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/PageObject/AddressBook.py
self.login_page_items: {'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
self.address_book_page: {'home_page.addressbook': "xpath>//div[text()='\xe9\x80\x9a\xe8\xae\xaf\xe5\xbd\x95']"}
self.address_book_page_itmes: {'addcontacts_page.createcontactsbtn': "xpath>//span[text()='\xe6\x96\xb0\xe5\xbb\xba\xe8\x81\x94\xe7\xb3\xbb\xe4\xba\xba']", 'addcontacts_page.contactpersonmobile': "xpath>//*[@id='iaddress_TEL_wrap']//dd//input", 'addcontacts_page.contactpersonemail': "xpath>//*[@id='iaddress_MAIL_wrap']//input", 'addcontacts_page.contactpersoncomment': 'xpath>//textarea', 'addcontacts_page.contactpersonname': "xpath>//a[@title='\xe7\xbc\x96\xe8\xbe\x91\xe8\xaf\xa6\xe7\xbb\x86\xe5\xa7\x93\xe5\x90\x8d']/preceding-sibling::div/input", 'addcontacts_page.savecontaceperson': "xpath>//span[.='\xe7\xa1\xae \xe5\xae\x9a']", 'addcontacts_page.starcontacts': "xpath>//span[text()='\xe8\xae\xbe\xe4\xb8\xba\xe6\x98\x9f\xe6\xa0\x87\xe8\x81\x94\xe7\xb3\xbb\xe4\xba\xba']/preceding-sibling::span/b"}
xpath //div[text()='Address Book']
xpath //span[text()='New Contact']
xpath //a[@title='Edit detailed name']/preceding-sibling::div/input
xpath //*[@id='iaddress_MAIL_wrap']//input
xpath //span[text()='Set as star contact']/preceding-sibling::span/b
xpath //*[@id='iaddress_TEL_wrap']//dd//input
xpath //textarea
xpath //span[.='OK']
Now, we have encapsulated the operation of adding each element of the contact page and called it in the test code. Just like the encapsulation of the login function, we have encapsulated the operation of adding each element of the contact page in another file. , just call it directly in the main program, which is convenient for operation.
Step 10—Separately encapsulate the add contact function of addressBook
Create a new add_contact.py file under the Action package
add_contact.py:
#encoding=utf-8
#author-Xia Xiaoxu
from PageObject.AddressBook import *
from selenium import webdriver
def add_contact(driver):
driver.switch_to.default_content()
addressbook = AddressBook(driver)
addressbook.address_book_link().click()
addressbook.add_contact_button().click()
addressbook.contact_name().send_keys("sam")
addressbook.contact_email().send_keys("[email protected]")
addressbook.contact_is_star().click()
addressbook.contact_mobile().send_keys("18142244444")
addressbook.contact_other_info().send_keys("myself")
addressbook.contact_save_button().click()
if __name__=='__main__':
driver=webdriver.Firefox(executable_path="c:\\geckodriver")
driver.get("http://mail.126.com")
login(driver, "xiaxiaoxu1987", "gloryroad")
add_contact(driver)
result:
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Action/add_contact.py
self.login_page_items: {'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
Process finished with exit code 0
Further encapsulate, replace the input part with parameters
#encoding=utf-8
#author-Xia Xiaoxu
from PageObject.AddressBook import *
from selenium import webdriver
def add_contact(driver,name="",email="",is_star=True,mobile="",otherinfo=""):
driver.switch_to.default_content()
addressbook = AddressBook(driver)
addressbook.address_book_link().click()
addressbook.add_contact_button().click()
addressbook.contact_name().send_keys(name)
addressbook.contact_email().send_keys(email)
if is_star==True:
addressbook.contact_is_star().click()
addressbook.contact_mobile().send_keys(mobile)
addressbook.contact_other_info().send_keys(otherinfo)
addressbook.contact_save_button().click()
if __name__=='__main__':
driver=webdriver.Firefox(executable_path="c:\\geckodriver")
driver.get("http://mail.126.com")
login(driver, "xiaxiaoxu1987", "gloryroad")
add_contact(driver,"xiaxiaoxu","[email protected]",True,"13333334444","gloryroad")
driver.quit()
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Action/add_contact.py
self.login_page_items: {'login_page.loginbutton': 'id>dologin', 'login_page.username': "xpath>//input[@name='email']", 'login_page.frame': 'id>x-URS-iframe', 'login_page.password': "xpath>//input[@name='password']"}
Process finished with exit code 0
At this point, we have encapsulated most of the functions. Now let’s try calling these functions in the main program:
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from PageObject.AddressBook import *
from Action.add_contact import *
from Action.login import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
login(driver, "xiaxiaoxu1987", "gloryroad")
add_contact(driver,"xiaxiaoxu","[email protected]",True,"13333334444","gloryroad")
driver.quit()
Result: It works fine.
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
Process finished with exit code 0
Judging from the calls in the main program, there are still some data that need to be entered manually, such as user names, passwords, and contact information that need to be added. If you need to add multiple contacts, you need to enter it multiple times. Next step Let’s look at how to separate this part of the data from the program and save it in a file. This time we will put it in Excel, and then use the method of operating Excel to extract the data.
Step 11-Encapsulate excel operations
Create an excel file and put the 126 account information in the first sheet.
The second sheet contains contacts:
Create a new TestData package under the project and put the excel file under the package.
Excel.py:
#encoding=utf-8
#author-Xia Xiaoxu
# encoding=utf-8
from openpyxl import load_workbook
from openpyxl.styles import Border, Side, Font
import time
from ProjectVar.var import *
class parseExcel(object):
def __init__(self, excelPath):
self.excelPath = excelPath
self.workbook = load_workbook(excelPath) # 加载excel
self.sheet = self.workbook.active # Get the first sheet
self.font = Font(color=None)
self.colorDict = {"red": 'FFFF3030', "green": 'FF008B00'}
#Set the sheet object currently to be operated and use index to get the corresponding sheet
def set_sheet_by_index(self, sheet_index):
sheet_name = self.workbook.get_sheet_names()[sheet_index]
self.sheet = self.workbook.get_sheet_by_name(sheet_name)
return self.sheet
# Get the name of the current default sheet
def get_default_sheet(self):
return self.sheet.title
#Set the sheet object currently to be operated and use the sheet name to get the corresponding sheet
def set_sheet_by_name(self, sheet_name):
sheet = self.workbook.get_sheet_by_name(sheet_name)
self.sheet = sheet
return self.sheet
# Get the maximum number of rows in the default sheet
def get_max_row_no(self):
return self.sheet.max_row
# Get the maximum number of columns of the default sheet
def get_max_col_no(self):
return self.sheet.max_column
# Get the minimum (starting) row number of the default sheet
def get_min_row_no(self):
return self.sheet.min_row
# Get the minimum (starting) column number of the default sheet
def get_min_col_no(self):
return self.sheet.min_column
# Get all row objects of the default sheet,
def get_all_rows(self):
return list(self.sheet.iter_rows())
# return list(self.rows) can also be used
# Get all column objects in the default sheet
def get_all_cols(self):
return list(self.sheet.iter_cols())
# return list(self.sheet.columns) can also be used
# Get a column from the default sheet, the first column starts from 0
def get_single_col(self, col_no):
return self.get_all_cols()[col_no]
# Get a certain row from the default sheet, the first row starts from 0
def get_single_row(self, row_no):
return self.get_all_rows()[row_no]
# From the default sheet, obtain the specified cell through the row number and column number. Note that the row number and column number start from 1
def get_cell(self, row_no, col_no):
return self.sheet.cell(row=row_no, column=col_no)
# From the default sheet, obtain the contents of the specified cell through the row number and column number. Note that the row number and column number start from 1
def get_cell_content(self, row_no, col_no):
return self.sheet.cell(row=row_no, column=col_no).value
# From the default sheet, write the specified content into the specified cell through the row number and column number. Note that the row number and column number start from 1
# When calling this method, excel should not be open.
def write_cell_content(self, row_no, col_no, content, font=None):
self.sheet.cell(row=row_no, column=col_no).value = content
self.workbook.save(self.excelPath)
return self.sheet.cell(row=row_no, column=col_no).value
# From the default sheet, write the current date into the specified cell through the row number and column number. Note that the row number and column number start from 1
# When calling this method, excel should not be open.
def write_cell_current_time(self, row_no, col_no):
time1 = time.strftime("%Y-%m-%d %H:%M:%S")
self.sheet.cell(row=row_no, column=col_no).value = str(time1)
self.workbook.save(self.excelPath)
return self.sheet.cell(row=row_no, column=col_no).value
def save_excel_file(self):
self.workbook.save(self.excelPath)
if __name__ == '__main__':
#Test code
p = parseExcel(test_data_excel_path)
print u"Get default sheet:", p.get_default_sheet()
print u"Set sheet index to 1", p.set_sheet_by_index(1)
print u"Get default sheet:", p.get_default_sheet()
print u"Set sheet index to 0", p.set_sheet_by_index(0)
print u"Get default sheet:", p.get_default_sheet()
print u"Maximum number of rows:", p.get_max_row_no()
print u"Maximum number of columns:", p.get_max_col_no()
print u"Minimum starting row number:", p.get_min_row_no()
print u"Minimum starting number of columns:", p.get_min_col_no()
print u"All row objects:", p.get_all_rows()
print u"All column objects:", p.get_all_cols()
print u"Get a certain column (2):", p.get_single_col(2)
print u"Get a certain row(1):", p.get_single_row(1)
print u"Get row number and column number (2,2) cell:", p.get_cell(2, 2)
print u"Get the contents of the row number and column number cell (2, 2)", p.get_cell_content(2, 2)
print u"row and column number write content (11,11): 'xiaxiaoxu'", p.write_cell_content(11, 11, 'xiaxiaoxu') #
print u"The row number and column number are written into the current date (13,13):", p.write_cell_current_time(13, 13)
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/Excel.py
Get the default sheet: Contact
Set sheet index to 1 <Worksheet "\u8054\u7cfb\u4eba">
Get the default sheet: Contact
Set sheet index to 0 <Worksheet "126\u8d26\u53f7">
Get the default sheet: 126 account
Maximum number of rows: 3
Maximum number of columns: 6
Minimum number of starting lines: 1
Minimum number of starting columns: 1
所有行对象: [(<Cell u'126\u8d26\u53f7'.A1>, <Cell u'126\u8d26\u53f7'.B1>, <Cell u'126\u8d26\u53f7'.C1>, <Cell u'126\u8d26\u53f7'.D1>, <Cell u'126\u8d26\u53f7'.E1>, <Cell u'126\u8d26\u53f7'.F1>), (<Cell u'126\u8d26\u53f7'.A2>, <Cell u'126\u8d26\u53f7'.B2>, <Cell u'126\u8d26\u53f7'.C2>, <Cell u'126\u8d26\u53f7'.D2>, <Cell u'126\u8d26\u53f7'.E2>, <Cell u'126\u8d26\u53f7'.F2>), (<Cell u'126\u8d26\u53f7'.A3>, <Cell u'126\u8d26\u53f7'.B3>, <Cell u'126\u8d26\u53f7'.C3>, <Cell u'126\u8d26\u53f7'.D3>, <Cell u'126\u8d26\u53f7'.E3>, <Cell u'126\u8d26\u53f7'.F3>)]
所有列对象: [(<Cell u'126\u8d26\u53f7'.A1>, <Cell u'126\u8d26\u53f7'.A2>, <Cell u'126\u8d26\u53f7'.A3>), (<Cell u'126\u8d26\u53f7'.B1>, <Cell u'126\u8d26\u53f7'.B2>, <Cell u'126\u8d26\u53f7'.B3>), (<Cell u'126\u8d26\u53f7'.C1>, <Cell u'126\u8d26\u53f7'.C2>, <Cell u'126\u8d26\u53f7'.C3>), (<Cell u'126\u8d26\u53f7'.D1>, <Cell u'126\u8d26\u53f7'.D2>, <Cell u'126\u8d26\u53f7'.D3>), (<Cell u'126\u8d26\u53f7'.E1>, <Cell u'126\u8d26\u53f7'.E2>, <Cell u'126\u8d26\u53f7'.E3>), (<Cell u'126\u8d26\u53f7'.F1>, <Cell u'126\u8d26\u53f7'.F2>, <Cell u'126\u8d26\u53f7'.F3>)]
Get a certain column (2): (<Cell u'126\u8d26\u53f7'.C1>, <Cell u'126\u8d26\u53f7'.C2>, <Cell u'126\u8d26\u53f7'.C3>)
获取某一行(1): (<Cell u'126\u8d26\u53f7'.A2>, <Cell u'126\u8d26\u53f7'.B2>, <Cell u'126\u8d26\u53f7'.C2>, <Cell u'126\u8d26\u53f7'.D2>, <Cell u'126\u8d26\u53f7'.E2>, <Cell u'126\u8d26\u53f7'.F2>)
Get the row number and column number (2,2) cell: <Cell u'126\u8d26\u53f7'.B2>
Get the contents of row number and column number cells (2,2) xiaxiaoxu1987
Row number and column number write content (11,11): 'xiaxiaoxu' xiaxiaoxu
The row number and column number are written into the current date (13,13): 2018-07-10 21:28:14
Process finished with exit code 0
At this point, the excel operation has been encapsulated. Let’s try reading the data in the main program.
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from PageObject.AddressBook import *
from Action.add_contact import *
from Action.login import *
from ProjectVar.var import *
from Util.Excel import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
pe=parseExcel(test_data_excel_path)
pe.set_sheet_by_name(u"126 account")
print pe.get_default_sheet()
rows=pe.get_all_rows()[1:]
for id,row in enumerate(rows):
if row[4].value =='y':
username = row[1].value
password = row[2].value
print username, password
try:
login(driver,username,password)
pe.set_sheet_by_name(u"Contact")
print pe.get_default_sheet()
rows1=pe.get_all_rows()[1:]
print "rows1:",rows1
for id1,row in enumerate(rows1):
if row[7].value == 'y':
try:
#print row[1].value,row[2].value,row[3].value,row[4].value,row[5].value
#print "execute1"
add_contact(driver,row[1].value,row[2].value,row[3].value,row[4].value,row[5].value)
print "assert word:",row[6].value in driver.page_source
print row[6].value
pe.write_cell_content(id1+2,10,"pass")
except Exception,e:
print u"Exception message:",e.message
pe.write_cell_content(id1+2,10,"fail")
else:
pe.write_cell_content(id1+2,10,u"忽略")
continue
except Exception,e:
pe.set_sheet_by_name(u"126 account")
print u"Exception message:",e
pe.write_cell_content(id+2,5,"fail")
else:
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,6,u"忽略")
continue
Result: execution ok
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
126account
xiaxiaoxu1987 gloryroad
Contact person
rows1: [(<Cell u'\u8054\u7cfb\u4eba'.A2>, <Cell u'\u8054\u7cfb\u4eba'.B2>, <Cell u'\u8054\u7cfb\u4eba'.C2>, <Cell u'\u8054\u7cfb\u4eba'.D2>, <Cell u'\u8054\u7cfb\u4eba'.E2>, <Cell u'\u8054\u7cfb\u4eba'.F2>, <Cell u'\u8054\u7cfb\u4eba'.G2>, <Cell u'\u8054\u7cfb\u4eba'.H2>, <Cell u'\u8054\u7cfb\u4eba'.I2>, <Cell u'\u8054\u7cfb\u4eba'.J2>), (<Cell u'\u8054\u7cfb\u4eba'.A3>, <Cell u'\u8054\u7cfb\u4eba'.B3>, <Cell u'\u8054\u7cfb\u4eba'.C3>, <Cell u'\u8054\u7cfb\u4eba'.D3>, <Cell u'\u8054\u7cfb\u4eba'.E3>, <Cell u'\u8054\u7cfb\u4eba'.F3>, <Cell u'\u8054\u7cfb\u4eba'.G3>, <Cell u'\u8054\u7cfb\u4eba'.H3>, <Cell u'\u8054\u7cfb\u4eba'.I3>, <Cell u'\u8054\u7cfb\u4eba'.J3>), (<Cell u'\u8054\u7cfb\u4eba'.A4>, <Cell u'\u8054\u7cfb\u4eba'.B4>, <Cell u'\u8054\u7cfb\u4eba'.C4>, <Cell u'\u8054\u7cfb\u4eba'.D4>, <Cell u'\u8054\u7cfb\u4eba'.E4>, <Cell u'\u8054\u7cfb\u4eba'.F4>, <Cell u'\u8054\u7cfb\u4eba'.G4>, <Cell u'\u8054\u7cfb\u4eba'.H4>, <Cell u'\u8054\u7cfb\u4eba'.I4>, <Cell u'\u8054\u7cfb\u4eba'.J4>), (<Cell u'\u8054\u7cfb\u4eba'.A5>, <Cell u'\u8054\u7cfb\u4eba'.B5>, <Cell u'\u8054\u7cfb\u4eba'.C5>, <Cell u'\u8054\u7cfb\u4eba'.D5>, <Cell u'\u8054\u7cfb\u4eba'.E5>, <Cell u'\u8054\u7cfb\u4eba'.F5>, <Cell u'\u8054\u7cfb\u4eba'.G5>, <Cell u'\u8054\u7cfb\u4eba'.H5>, <Cell u'\u8054\u7cfb\u4eba'.I5>, <Cell u'\u8054\u7cfb\u4eba'.J5>)]
assert word: True
assert word: True
John Doe
Process finished with exit code 0
Excel results:
At this point, most of the functions of the framework have been encapsulated. Next, let’s build the log module so that the program can print logs when executing.
Step 12—Add the log module
Create a new Logger.conf log configuration file under the Conf package, which configures the logger, processor and formatter. The specific details of the log will be listed later.
#logger.conf
###############################################
[loggers]
keys=root,example01,example02
[logger_root]
level=DEBUG
handlers=hand01,hand02
[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0
[logger_example02]
handlers=hand01,hand03
qualname=example02
propagate=0
###############################################
[handlers]
keys=hand01,hand02,hand03
[handler_hand01]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stderr,)
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('DataDrivenFrameWork.log', 'a')
[handler_hand03]
class=handlers.RotatingFileHandler
level=INFO
formatter=form01
args=('DataDrivenFrameWork.log', 'a', 10*1024*1024, 5)
###############################################
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
[formatter_form02]
format=%(name)-12s: %(levelname)-8s %(message)s
datefmt=%Y-%m-%d %H:%M:%S
Create a new log.py file under the Util package to encapsulate the log operation
Log.py:
#encoding=utf-8
#author-Xia Xiaoxu
import logging
import logging.config
from ProjectVar.var import *
#Read the log configuration file
logging.config.fileConfig(project_path+"\\Conf\\Logger.conf")
#Select a log format
logger=logging.getLogger("example02")
def error(message):
#Print debug level information
logger.error(message)
def info(message):
#Print info level information
logger.info(message)
def warning(message):
#Print warning level information
logger.warning(message)
if __name__=="__main__":
#Test code
info("hi")
print "config file path:",project_path+"\\Conf\\Logger.conf"
error("world!")
warning("gloryroad!")
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/log.py
2018-07-11 21:32:12 log.py[line:19] INFO hi
config file path: D:\test\dataDrivenTestPractice1\Conf\Logger.conf
2018-07-11 21:32:12 log.py[line:15] ERROR world!
2018-07-11 21:32:12 log.py[line:23] WARNING gloryroad!
Process finished with exit code 0
The encapsulation of the log is now ok. Let’s modify the main program and call the log.
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from PageObject.AddressBook import *
from Action.add_contact import *
from Action.login import *
from ProjectVar.var import *
from Util.Excel import *
from Util.log import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
pe=parseExcel(test_data_excel_path)
pe.set_sheet_by_name(u"126 account")
print pe.get_default_sheet()
rows=pe.get_all_rows()[1:]
for id,row in enumerate(rows):
if row[4].value =='y':
username = row[1].value
password = row[2].value
print username, password
try:
login(driver,username,password)
pe.set_sheet_by_name(u"Contact")
print pe.get_default_sheet()
rows1=pe.get_all_rows()[1:]
print "rows1:",rows1
test_data_pass_flag=True#Result representation, used for final writing of excel results
for id1,row in enumerate(rows1):
if row[7].value == 'y':
try:
#print row[1].value,row[2].value,row[3].value,row[4].value,row[5].value
print "log0711",row[1],row[1].value
print "log0711",type(row[1].value)
#Perform the actual operation of adding contacts
add_contact(driver,row[1].value,row[2].value,row[3].value,row[4].value,row[5].value)
print "assert word:",row[6].value
assert row[6].value in driver.page_source
pe.write_cell_content(id1+2,10,"pass")#assert does not report an error, indicating that the assertion is successful
except Exception,e:
#print u"Exception message 01",e.message
pe.write_cell_content(id1+2,10,"fail")
test_data_pass_flag=False#The code reaches this point, indicating that there is an exception and the test failed.
info(u"Exception message 01" + e.message) #Output log
else:#The description identifier is 'n', ignore the data
pe.write_cell_content(id1+2,10,u"忽略")
continue
if test_data_pass_flag ==True:#If the flag is True, the result is successful
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,5,"成功")
else:#Indicates that the flag is False
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,5,"Failed")
except Exception,e:
pe.set_sheet_by_name(u"126 account")
#print u"Exception message 02:",e
pe.write_cell_content(id+2,6,"fail")
info(u"Exception message 02:"+e.message)#Output log
time.sleep(2)
driver.quit()
else:#Take this branch, indicating that the identifier is "n"
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,6,u"忽略")
continue
Result: Run OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
126account
xiaxiaoxu1987 gloryroad
2018-07-11 22:55:25 log.py[line:19] INFO login successfully
Contact person
rows1: [(<Cell u'\u8054\u7cfb\u4eba'.A2>, <Cell u'\u8054\u7cfb\u4eba'.B2>, <Cell u'\u8054\u7cfb\u4eba'.C2>, <Cell u'\u8054\u7cfb\u4eba'.D2>, <Cell u'\u8054\u7cfb\u4eba'.E2>, <Cell u'\u8054\u7cfb\u4eba'.F2>, <Cell u'\u8054\u7cfb\u4eba'.G2>, <Cell u'\u8054\u7cfb\u4eba'.H2>, <Cell u'\u8054\u7cfb\u4eba'.I2>, <Cell u'\u8054\u7cfb\u4eba'.J2>), (<Cell u'\u8054\u7cfb\u4eba'.A3>, <Cell u'\u8054\u7cfb\u4eba'.B3>, <Cell u'\u8054\u7cfb\u4eba'.C3>, <Cell u'\u8054\u7cfb\u4eba'.D3>, <Cell u'\u8054\u7cfb\u4eba'.E3>, <Cell u'\u8054\u7cfb\u4eba'.F3>, <Cell u'\u8054\u7cfb\u4eba'.G3>, <Cell u'\u8054\u7cfb\u4eba'.H3>, <Cell u'\u8054\u7cfb\u4eba'.I3>, <Cell u'\u8054\u7cfb\u4eba'.J3>), (<Cell u'\u8054\u7cfb\u4eba'.A4>, <Cell u'\u8054\u7cfb\u4eba'.B4>, <Cell u'\u8054\u7cfb\u4eba'.C4>, <Cell u'\u8054\u7cfb\u4eba'.D4>, <Cell u'\u8054\u7cfb\u4eba'.E4>, <Cell u'\u8054\u7cfb\u4eba'.F4>, <Cell u'\u8054\u7cfb\u4eba'.G4>, <Cell u'\u8054\u7cfb\u4eba'.H4>, <Cell u'\u8054\u7cfb\u4eba'.I4>, <Cell u'\u8054\u7cfb\u4eba'.J4>), (<Cell u'\u8054\u7cfb\u4eba'.A5>, <Cell u'\u8054\u7cfb\u4eba'.B5>, <Cell u'\u8054\u7cfb\u4eba'.C5>, <Cell u'\u8054\u7cfb\u4eba'.D5>, <Cell u'\u8054\u7cfb\u4eba'.E5>, <Cell u'\u8054\u7cfb\u4eba'.F5>, <Cell u'\u8054\u7cfb\u4eba'.G5>, <Cell u'\u8054\u7cfb\u4eba'.H5>, <Cell u'\u8054\u7cfb\u4eba'.I5>, <Cell u'\u8054\u7cfb\u4eba'.J5>)]
log0711 <Cell u'\u8054\u7cfb\u4eba'.B2> lily
log0711 <type 'unicode'>
assert word: [email protected]
Process finished with exit code 0
The login.py file can also create a log after executing the login action to indicate successful login.
At this point, the main program has implemented reading data, logging in to mailboxes, adding contacts, and writing test results to excel. The basic functions have been completed. Now let’s do one more thing and it will be close to perfect, which is the encapsulation of time. Call the time function in the program to write the specified time format.
Step 13—Encapsulation of common time operations
Create a new FormatTime.py under the Util package
FormatTime.py
#encoding=utf-8
#author-Xia Xiaoxu
#encoding=utf-8
import time
from datetime import timedelta,date
def date_time_chinese():
print u"returns the current time string,format for YYYY year mm month dd day HH hour MM minute SS second"
return time.strftime("%Y year %m month %d day %H hour %M minute %S second", time.localtime())
def date_chinese():
print u"returns the current time string, format for YYYY年mm月dd日"
return time.strftime("%Y year %m month %d day",time.localtime())
def time_chinese():
print u"returns the current time string,format for HH时 MM分 SS秒"
return time.strftime("%H hour %M minute %S second", time.localtime())
def date_time():
print "returns the current time string, format for YYYY-mm-dd HH:MM:SS"
return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())
def date_time_slash():
print "returns the current time string,format for YYYY/mm/dd HH:MM:SS"
return time.strftime("%Y/%m/%d %H:%M:%S",time.localtime())
def dates():
print "returns the current time string,format for YYYY-mm-dd"
return time.strftime("%Y-%m-%d",time.localtime())
def date_slash():
print "returns the current time string,format for YYYY/mm/dd"
return time.strftime("%Y/%m/%d",time.localtime())
def times():
print "returns the current time string, format for HH:MM:SS"
return time.strftime("%H:%M:%S",time.localtime())
def year():
print "returns the current time string,format for year"
return time.strftime("%Y",time.localtime())
def month():
print "returns the current time string,format for month"
return time.strftime("%m",time.localtime())
def day():
print "returns the current time string,format for day"
return time.strftime("%d",time.localtime())
def hour():
print "returns the current time string, format for Hour"
return time.strftime("%H",time.localtime())
def minute():
print "returns the current time string,format for minute"
return time.strftime("%M",time.localtime())
def seconds():
print "return the current time string,format for seconds"
return time.strftime("%S",time.localtime())
def str_to_tuple(stime):
print "returns the string variable into time tuples"
return time.strptime(stime,"%Y-%m-%d %H:%M:%S")
def add_date(day_num):
today=date.today()
print "returns the current date-%s and a time interval-%s" %(today,day_num)
times=today+timedelta(days=day_num)
return times
def sub_date(day_num):
today=date.today()
print "returns the current date-%s minus one time interval-%s" %(today,day_num)
times=today-timedelta(days=day_num)
return times
if __name__=='__main__':
#Test code
print date_time_chinese()
print date_chinese()
print time_chinese()
print date_time()
print date_time_slash()
print dates()
print date_slash()
print times()
print year()
print month()
print day()
print hour()
print minute()
print seconds()
time1=time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())
print str_to_tuple(time1)
print add_date(2)
print sub_date(2)
Result: ok, Chinese does not need to be transcoded in pycharm, but transcoding is required to run in notepad.
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/Util/FormatTime.py
returns the current time string,format for YYYY year mm month dd day HH hour MM minute SS second
July 11, 2018 23:01:36
returns the current time string, format for YYYY mm month dd day
July 11, 2018
returns the current time string, format for HH hours MM minutes SS seconds
23:01:36
returns the current time string, format for YYYY-mm-dd HH:MM:SS
2018-07-11 23:01:36
returns the current time string,format for YYYY/mm/dd HH:MM:SS
2018/07/11 23:01:36
returns the current time string,format for YYYY-mm-dd
2018-07-11
returns the current time string,format for YYYY/mm/dd
2018/07/11
returns the current time string, format for HH:MM:SS
23:01:36
returns the current time string,format for year
2018
returns the current time string,format for month
07
returns the current time string,format for day
11
returns the current time string, format for Hour
23
returns the current time string,format for minute
01
return the current time string,format for seconds
36
returns the string variable into time tuples
time.struct_time(tm_year=2018, tm_mon=7, tm_mday=11, tm_hour=23, tm_min=1, tm_sec=36, tm_wday=2, tm_yday=192, tm_isdst=-1)
returns the current date-2018-07-11 and a time interval-2
2018-07-13
returns the current date-2018-07-11 minus one time interval-2
2018-07-09
Process finished with exit code 0
Modify the main program, call the time function, and print the time
TestScript.py:
#encoding=utf-8
#author-Xia Xiaoxu
from selenium import webdriver
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import traceback
from PageObject.Login_Page import *
from PageObject.AddressBook import *
from Action.add_contact import *
from Action.login import *
from ProjectVar.var import *
from Util.Excel import *
from Util.log import *
from Util.FormatTime import *
driver=webdriver.Firefox(executable_path='c:\\geckodriver')
driver.get('http://mail.126.com')
pe=parseExcel(test_data_excel_path)
pe.set_sheet_by_name(u"126 account")
print pe.get_default_sheet()
rows=pe.get_all_rows()[1:]
for id,row in enumerate(rows):
if row[4].value =='y':
username = row[1].value
password = row[2].value
print username, password
try:
login(driver,username,password)
pe.set_sheet_by_name(u"Contact")
print pe.get_default_sheet()
rows1=pe.get_all_rows()[1:]
print "rows1:",rows1
test_data_pass_flag=True#Result representation, used for final writing of excel results
for id1,row in enumerate(rows1):
if row[7].value == 'y':
try:
#print row[1].value,row[2].value,row[3].value,row[4].value,row[5].value
print "log0711",row[1],row[1].value
print "log0711",type(row[1].value)
#Perform the actual operation of adding contacts
add_contact(driver,row[1].value,row[2].value,row[3].value,row[4].value,row[5].value)
pe.write_cell_content(id1 + 2, 9, date_time())
print "assert word:",row[6].value
assert row[6].value in driver.page_source
pe.write_cell_content(id1+2,10,"pass")#assert does not report an error, indicating that the assertion is successful
except Exception,e:
#print u"Exception message 01",e.message
pe.write_cell_content(id1+2,10,"fail")
pe.write_cell_content(id1+2,9,date_time())
test_data_pass_flag=False#The code reaches this point, indicating that there is an exception and the test failed.
info(u"Exception message 01" + e.message) # Output log
else:#The description identifier is 'n', ignore the data
pe.write_cell_content(id1+2,10,u"忽略")
pe.write_cell_content(id1 + 2, 9,date_time())
continue
if test_data_pass_flag ==True:#If the flag is True, the result is successful
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,6,"成功")
else:#Indicates that the flag is False
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,6,"Failed")
except Exception,e:
pe.set_sheet_by_name(u"126 account")
#print u"Exception message 02:",e
pe.write_cell_content(id+2,6,"fail")
info(u"Exception message 02:"+e.message)#Output log
time.sleep(2)
driver.quit()
else:#Take this branch, indicating that the identifier is "n"
pe.set_sheet_by_name(u"126 account")
pe.write_cell_content(id+2,6,u"忽略")
continue
Result: OK
C:\Python27\python.exe D:/test/dataDrivenTestPractice1/TestScript/TestScript.py
126account
xiaxiaoxu1987 gloryroad
2018-07-11 23:17:39 log.py[line:19] INFO login successfully
Contact person
rows1: [(<Cell u'\u8054\u7cfb\u4eba'.A2>, <Cell u'\u8054\u7cfb\u4eba'.B2>, <Cell u'\u8054\u7cfb\u4eba'.C2>, <Cell u'\u8054\u7cfb\u4eba'.D2>, <Cell u'\u8054\u7cfb\u4eba'.E2>, <Cell u'\u8054\u7cfb\u4eba'.F2>, <Cell u'\u8054\u7cfb\u4eba'.G2>, <Cell u'\u8054\u7cfb\u4eba'.H2>, <Cell u'\u8054\u7cfb\u4eba'.I2>, <Cell u'\u8054\u7cfb\u4eba'.J2>), (<Cell u'\u8054\u7cfb\u4eba'.A3>, <Cell u'\u8054\u7cfb\u4eba'.B3>, <Cell u'\u8054\u7cfb\u4eba'.C3>, <Cell u'\u8054\u7cfb\u4eba'.D3>, <Cell u'\u8054\u7cfb\u4eba'.E3>, <Cell u'\u8054\u7cfb\u4eba'.F3>, <Cell u'\u8054\u7cfb\u4eba'.G3>, <Cell u'\u8054\u7cfb\u4eba'.H3>, <Cell u'\u8054\u7cfb\u4eba'.I3>, <Cell u'\u8054\u7cfb\u4eba'.J3>), (<Cell u'\u8054\u7cfb\u4eba'.A4>, <Cell u'\u8054\u7cfb\u4eba'.B4>, <Cell u'\u8054\u7cfb\u4eba'.C4>, <Cell u'\u8054\u7cfb\u4eba'.D4>, <Cell u'\u8054\u7cfb\u4eba'.E4>, <Cell u'\u8054\u7cfb\u4eba'.F4>, <Cell u'\u8054\u7cfb\u4eba'.G4>, <Cell u'\u8054\u7cfb\u4eba'.H4>, <Cell u'\u8054\u7cfb\u4eba'.I4>, <Cell u'\u8054\u7cfb\u4eba'.J4>), (<Cell u'\u8054\u7cfb\u4eba'.A5>, <Cell u'\u8054\u7cfb\u4eba'.B5>, <Cell u'\u8054\u7cfb\u4eba'.C5>, <Cell u'\u8054\u7cfb\u4eba'.D5>, <Cell u'\u8054\u7cfb\u4eba'.E5>, <Cell u'\u8054\u7cfb\u4eba'.F5>, <Cell u'\u8054\u7cfb\u4eba'.G5>, <Cell u'\u8054\u7cfb\u4eba'.H5>, <Cell u'\u8054\u7cfb\u4eba'.I5>, <Cell u'\u8054\u7cfb\u4eba'.J5>)]
log0711 <Cell u'\u8054\u7cfb\u4eba'.B2> lily
log0711 <type 'unicode'>
returns the current time string, format for YYYY-mm-dd HH:MM:SS
assert word: [email protected]
returns the current time string, format for YYYY-mm-dd HH:MM:SS
returns the current time string, format for YYYY-mm-dd HH:MM:SS
returns the current time string, format for YYYY-mm-dd HH:MM:SS
Process finished with exit code 0
Excel writes the results:
Summarize:
Now we start by assembling scattered functions to implement a larger function, and then organize the larger function into a larger function. In the end, what we see in the main program is the combination of independent large functions. See It looks neat, simple, highly readable, and easy to maintain.
In this way, we transform the entire framework from a scattered list of codes to encapsulating functions into pieces. We separate data and programs, encapsulate and integrate a certain independent function. In this process, we become familiar with the steps of building a framework. After knowing the ins and outs and benefits of encapsulating it into several large blocks, when you build the test framework later, you can just follow these large blocks. It will be relatively easier to accept this idea and logic and its necessity. nature, fundamentally understand why such a test framework should be built and the principles of this test framework, and then draw inferences and extend it to other types of test frameworks.
More hands-on practice~
The following is the data-driven framework structure:
Data-driven framework structure:
Action:
encapsulated function of operating elements, such as login and adding contacts. . .
conf:
Log configuration file
Positioning element configuration file
Database configuration file
PageObject:
A page is a class, and the method of the class can obtain related elements on the page
ProjectVar:
Project path
Project-related global variables
TestData: (file or excel)
test case
test data
TestScript:
The main program that runs the test framework: the entrance, which mainly reads the test data files
and records the test results.
Util-Tool Class
Function:
Read configuration file
excel tool class
time class
method to find elements method
to read positioning element configuration file
log method
log operation
screenshot
report template
If you need the source code of the framework, you can leave your email in the comment area~
Finally, I will share with you the documents and learning materials that I have accumulated and truthful. If necessary, you can just pick them up. The above content should
be the most comprehensive and complete preparation warehouse for software testing friends. In order to better organize it For each module, I also referred to many high-quality blog posts and projects on the Internet, trying not to miss any knowledge point. Many friends relied on these contents to review and got offers from major manufacturers such as BATJ. This warehouse has also helped a lot. As a learner of software testing, I hope it can also help you.
Follow my WeChat official account below to get it for free! ↓ ↓ ↓ ↓ ↓