测试学习记录,仅供参考!
项目实战(开源电商商城系统项目)
注册页面
登录页面场景完成之后,开始编写"注册页面"实现场景;

页面类
页面元素
1、在项目根目录 pageObject 软件包 register_page 目录下新建名称为 register_page.py 的 Python 文件;
把"注册页"的定位元素放到页面类中去管理;做 web 自动化主要的工作量就是定位元素(比较费时间);
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage
# 注册页面类
class RegisterPage(BasePage):
# url
url = 'http://localhost:8088/ecshop/user.php?act=register'
# 用户名
username = (By.NAME, 'username')
# email
email = (By.NAME, 'email')
# 密码
password = (By.NAME, 'password')
# 确认密码
confirm_password = (By.ID, 'conform_password')
# MSN
msn = (By.NAME, 'extend_field1')
# QQ
qq = (By.NAME, 'extend_field2')
# 办公电话
office_phone = (By.NAME, 'extend_field3')
# 家庭电话
family_phone = (By.NAME, 'extend_field4')
# 手机
phone = (By.NAME, 'extend_field5')
# 密码提示问题
selects = (By.NAME, 'sel_question')
# 密码问题答案
password_answer = (By.NAME, 'passwd_answer')
# 立即注册按钮
submit_button = (By.NAME, 'Submit')
页面操作
2、页面类元素完成之后开始页面操作;
若是不怕搞混淆了的话,定位表达式的变量名和输入传参数据名一样亦可;
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage
# 注册页面类
class RegisterPage(BasePage):
# url
url = 'http://localhost:8088/ecshop/user.php?act=register'
# 用户名
username = (By.NAME, 'username')
# email
email = (By.NAME, 'email')
# 密码
password = (By.NAME, 'password')
# 确认密码
confirm_password = (By.ID, 'conform_password')
# MSN
msn = (By.NAME, 'extend_field1')
# QQ
qq = (By.NAME, 'extend_field2')
# 办公电话
office_phone = (By.NAME, 'extend_field3')
# 家庭电话
family_phone = (By.NAME, 'extend_field4')
# 手机
phone = (By.NAME, 'extend_field5')
# 密码提示问题
selects = (By.NAME, 'sel_question')
# 密码问题答案
password_answer = (By.NAME, 'passwd_answer')
# 立即注册按钮
submit_button = (By.NAME, 'Submit')
# 断言结果定位
assert_result = (By.XPATH, '/html/body/div[7]/div[2]/div/div/div/font')
# 断言用户已存在的结果
assert_user_exist = (By.XPATH, '//*[@id="username_notice"]')
# 注册操作--若不怕搞混淆,定位表达式的变量名和输入传参数据名一样也行
def click_register(self, username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer):
# 打开被测页面
self.open_url(self.url)
# 依次输入内容
self.send_keys(self.username, username)
self.send_keys(self.email, email)
self.send_keys(self.password, password)
self.send_keys(self.confirm_password, confirm_password)
self.send_keys(self.msn, msn)
self.send_keys(self.qq, qq)
self.send_keys(self.office_phone, office_phone)
self.send_keys(self.family_phone, family_phone)
self.send_keys(self.phone, phone)
self.send_keys(self.password_answer, password_answer)
# 点击立即注册按钮
self.click(self.submit_button)
封装方法
3、此时可以发现还剩下一个"下拉选择框"操作,在基类公共方法中亦没有封装此方法;
优化项目根目录 util_tools 软件包下 basePage.py 文件,再次封装 下拉菜单选择 方法;
导入下拉选择 from selenium.webdriver.support.ui import Select 类;
# 导包
from pyxnat.core.uriutil import file_path
from selenium import webdriver
from selenium.common import NoSuchElementException
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from config import setting
from util_tools.logs_util.recordlog import logs
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import pytesseract
from PIL import Image
from selenium.webdriver.support.ui import Select
class BasePage(object):
def __init__(self, driver):
self.__driver = driver
self.__wait = WebDriverWait(self.__driver, setting.WAIT_TIME)
def window_max(self):
self.__driver.maximize_window()
def window_full(self):
self.__driver.fullscreen_window()
def screenshot(self):
self.__driver.get_screenshot_as_png()
def open_url(self, url):
self.__driver.get(url)
@property
def current_url(self):
return self.__driver.current_url
@property
def title(self):
return self.__driver.title
def refresh(self):
self.__driver.refresh()
@property
def switch_to(self):
return self.__driver.switch_to
def iframe(self, frame):
self.switch_to.frame(frame)
def exit_iframe(self):
self.switch_to.default_content()
@property
def alert(self):
return self.__wait.until(ec.alert_is_present())
def alert_confirm(self):
self.alert.accept()
def alert_cancel(self):
self.alert.dismiss()
def location_element(self, by, value):
try:
element = self.__wait.until(ec.presence_of_element_located((by, value)))
logs.info(f"找到元素:{by}={value}")
return element
except Exception as e:
logs.error(f"未找到元素:{by}={value}")
raise e
def location_elements(self, by, value):
try:
self.__wait.until(ec.presence_of_all_elements_located((by, value)))
elements = self.__driver.find_elements(by, value)
logs.info(f"找到元素列表:{by}={value}")
return elements
except Exception as e:
logs.error(f"未找到元素列表:{by}={value}")
raise e
def click(self, locator: tuple, force=False):
try:
element = self.location_element(*locator)
if not force:
self.__driver.execute_script("arguments[0].click()", element)
else:
self.__driver.execute_script("arguments[0].click({force:true})", element)
logs.info(f"元素被点击:{locator}")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def send_keys(self, locator: tuple, data):
try:
element = self.location_element(*locator)
element.send_keys(data)
logs.info(f"元素被输入内容:{locator},输入的内容为:{data}")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def selects(self, locator: tuple, index):
try:
select = Select(self.location_element(*locator))
select.select_by_index(index)
logs.info(f'选择第{index}个数据')
except NoSuchElementException as e:
logs.error(f'元素无法定位:{e}')
raise e
def enter(self):
try:
ActionChains(self.__driver).send_keys(Keys.ENTER).perform()
logs.info("按下回车键")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def right_click(self, locator: tuple):
try:
element = self.location_element(*locator)
ActionChains(driver).context_click(element).perform()
logs.info("执行鼠标右键点击操作")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def double_click(self, locator: tuple):
try:
element = self.location_element(*locator)
ActionChains(driver).double_click(element).perform()
logs.info("执行鼠标双击操作")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def screenshots(self, image_name):
import os
current_time = datetime.now().strftime("%Y%m%d%H%M%S")
file_name = f"{image_name}-{current_time}.png"
file_path = os.path.join(setting.FILE_PATH.get('screenshot'), file_name)
self.__driver.get_screenshot_as_file(file_path)
def clear(self, locator: tuple):
try:
element = self.location_element(*locator)
element.clear()
logs.info("清空文本")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def ocr_captcha(self, locator: tuple):
captcha_element = self.location_element(*locator)
captcha_path = setting.FILE_PATH['screenshot'] + '/captcha.png'
captcha_element.screenshot(captcha_path)
captcha_image = Image.open(captcha_path)
try:
captcha_text = pytesseract.image_to_string(captcha_image)
logs.info(f"识别到的验证码为:{captcha_text}")
return captcha_text
except pytesseract.pytesseract.TesseractNotFoundError:
logs.error("找不到tesseract,这是因为pytesseract模块依赖于TesseractOCR引擎来进行图像识别!")
def assert_is_element_present(self, locator: tuple):
try:
element = self.__driver.find_element(*locator)
assert element.is_displayed(), '元素不存在'
except NoSuchElementException as e:
logs.error(f'元素未找到:{e}')
raise AssertionError('元素不存在')
def assert_title(self, expect_title):
assert expect_title in self.title
4、回到注册页面,完善注册页面相关的操作
from selenium.webdriver.common.by import By
from util_tools.basePage import BasePage
# 注册页面类
class RegisterPage(BasePage):
# url
url = 'http://localhost:8088/ecshop/user.php?act=register'
# 用户名
username = (By.NAME, 'username')
# email
email = (By.NAME, 'email')
# 密码
password = (By.NAME, 'password')
# 确认密码
confirm_password = (By.ID, 'conform_password')
# MSN
msn = (By.NAME, 'extend_field1')
# QQ
qq = (By.NAME, 'extend_field2')
# 办公电话
office_phone = (By.NAME, 'extend_field3')
# 家庭电话
family_phone = (By.NAME, 'extend_field4')
# 手机
phone = (By.NAME, 'extend_field5')
# 密码提示问题
select = (By.NAME, 'sel_question')
# 密码问题答案
password_answer = (By.NAME, 'passwd_answer')
# 立即注册按钮
submit_button = (By.NAME, 'Submit')
# 断言结果定位
assert_result = (By.XPATH, '/html/body/div[7]/div[2]/div/div/div/font')
# 断言用户已存在的结果
assert_user_exist = (By.XPATH, '//*[@id="username_notice"]')
# 注册操作--若不怕搞混淆,定位表达式的变量名和输入传参数据名一样也行
def click_register(self, username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer):
# 打开被测页面
self.open_url(self.url)
# 依次输入内容
self.send_keys(self.username, username)
self.send_keys(self.email, email)
self.send_keys(self.password, password)
self.send_keys(self.confirm_password, confirm_password)
self.send_keys(self.msn, msn)
self.send_keys(self.qq, qq)
self.send_keys(self.office_phone, office_phone)
self.send_keys(self.family_phone, family_phone)
self.send_keys(self.phone, phone)
# 下拉菜单选择--若不固定索引值请在上面方法中自行加个参数
self.selects(self.select, 3)
self.send_keys(self.password_answer, password_answer)
# 点击立即注册按钮
self.click(self.submit_button)
测试类
5、 在项目根目录 testcase 软件包 register 目录下新建名称为 test_register.py 的 Python 文件;
用于管理注册页的测试用例;
1)、第一步,导入页面类;
2)、定义测试类;(可以参考登录)
3)、编写注册的测试用例;
from pageObject.register_page.register_page import RegisterPage
class TestRegister:
# 注册成功场景
def test_register_success(self):
pass
# 注册失败场景
def test_register_failed(self):
pass
参数化
自行选择使用参数化处理数据文件驱动方式;使用 json 的话参数比较多,建议使用 Yaml、Excel 文件;
6、在 data 文件目录下新建一个名称为 register_success.yaml 的 Yaml 文件(注册成功场景)
注意符合格式要求
- test01, 01@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
7、完善注册成功场景的测试用例;
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml
class TestRegister:
# 注册成功场景
@pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
def test_register_success(self, get_driver, data):
# 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
register_page = RegisterPage(get_driver)
# 页面类实例化之后开始进行解包数据
username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer = data
# 使用页面类对象 register_page 去调用相关操作 click_register
register_page.click_register(username, email, password, confirm_password, msn, qq, office_phone, family_phone, phone, password_answer)
# 添加等待时间查看效果
sleep(3)
8、运行查看结果是能够成功的,但是有很多的参数,在一行显示就很长,若是有几十上百个参数,很多这种场景的话,都通过这样解包的方式就比较麻烦,需要优化;
直接使用 *data 去接收多个参数,但是有一个前提是:data 的元素数量(读取文件中以逗号分隔的每一组参数为一个)务必与方法(页面类中的方法)里面的入参的参数数量是一致的,才能使用" *data";
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml
class TestRegister:
# 注册成功场景
@pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
def test_register_success(self, get_driver, data):
# 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
register_page = RegisterPage(get_driver)
# 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
register_page.click_register(*data)
# 添加等待时间查看效果
sleep(3)
# 断言结果
register_page.assert_is_element_present(register_page.assert_result)
9、编写注册失败场景的测试用例代码; 在 data 文件目录下新建一个名称为 register_failed.yaml 的 Yaml 文件(注册失败场景),直接使用刚刚注册成功场景的数据,因为执行注册成功场景后,test01 用户已经存在了,此时再去测试"去注册已经存在的用户是否还能继续注册";
注册成功场景数据
- test02, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
注册失败场景数据
- test02, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
10、完善测试用例;
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml
class TestRegister:
# 注册成功场景
@pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
def test_register_success(self, get_driver, data):
# 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
register_page = RegisterPage(get_driver)
# 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
register_page.click_register(*data)
# 添加等待时间查看效果
sleep(3)
# 断言结果
register_page.assert_is_element_present(register_page.assert_result)
# 注册失败场景
@pytest.mark.parametrize('data', read_yaml('./data/register_failed.yaml'))
def test_register_failed(self, get_driver, data):
register_page = RegisterPage(get_driver)
register_page.click_register(*data)
sleep(3)
register_page.assert_is_element_present(register_page.assert_user_exist)
11、运行主函数 run.py 文件,可以发现注册成功和注册失败场景均能通过测试;但是当测试数据有多组场景的情况下,断言结果是不同的,每一组数据都去写一种断言结果是比较麻烦的,每一个测试用例的断言结果并不是通用的;优化断言结果通用(想个办法把断言结果写成通用的)
封装方法
12、优化项目根目录 util_tools 软件包下 basePage.py 文件,在公共函数中去写一个 判断元素不存在 的方法;
# 导包
from pyxnat.core.uriutil import file_path
from selenium import webdriver
from selenium.common import NoSuchElementException, TimeoutException
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from config import setting
from util_tools.logs_util.recordlog import logs
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import pytesseract
from PIL import Image
from selenium.webdriver.support.ui import Select
class BasePage(object):
def __init__(self, driver):
self.__driver = driver
self.__wait = WebDriverWait(self.__driver, setting.WAIT_TIME)
def window_max(self):
self.__driver.maximize_window()
def window_full(self):
self.__driver.fullscreen_window()
def screenshot(self):
self.__driver.get_screenshot_as_png()
def open_url(self, url):
self.__driver.get(url)
@property
def current_url(self):
return self.__driver.current_url
@property
def title(self):
return self.__driver.title
def refresh(self):
self.__driver.refresh()
@property
def switch_to(self):
return self.__driver.switch_to
def iframe(self, frame):
self.switch_to.frame(frame)
def exit_iframe(self):
self.switch_to.default_content()
@property
def alert(self):
return self.__wait.until(ec.alert_is_present())
def alert_confirm(self):
self.alert.accept()
def alert_cancel(self):
self.alert.dismiss()
def location_element(self, by, value):
try:
element = self.__wait.until(ec.presence_of_element_located((by, value)))
logs.info(f"找到元素:{by}={value}")
return element
except Exception as e:
logs.error(f"未找到元素:{by}={value}")
raise e
def location_elements(self, by, value):
try:
self.__wait.until(ec.presence_of_all_elements_located((by, value)))
elements = self.__driver.find_elements(by, value)
logs.info(f"找到元素列表:{by}={value}")
return elements
except Exception as e:
logs.error(f"未找到元素列表:{by}={value}")
raise e
def click(self, locator: tuple, force=False):
try:
element = self.location_element(*locator)
if not force:
self.__driver.execute_script("arguments[0].click()", element)
else:
self.__driver.execute_script("arguments[0].click({force:true})", element)
logs.info(f"元素被点击:{locator}")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def send_keys(self, locator: tuple, data):
try:
element = self.location_element(*locator)
element.send_keys(data)
logs.info(f"元素被输入内容:{locator},输入的内容为:{data}")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def selects(self, locator: tuple, index):
try:
select = Select(self.location_element(*locator))
select.select_by_index(index)
logs.info(f'选择第{index}个数据')
except NoSuchElementException as e:
logs.error(f'元素无法定位:{e}')
raise e
def enter(self):
try:
ActionChains(self.__driver).send_keys(Keys.ENTER).perform()
logs.info("按下回车键")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def right_click(self, locator: tuple):
try:
element = self.location_element(*locator)
ActionChains(driver).context_click(element).perform()
logs.info("执行鼠标右键点击操作")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def double_click(self, locator: tuple):
try:
element = self.location_element(*locator)
ActionChains(driver).double_click(element).perform()
logs.info("执行鼠标双击操作")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def screenshots(self, image_name):
import os
current_time = datetime.now().strftime("%Y%m%d%H%M%S")
file_name = f"{image_name}-{current_time}.png"
file_path = os.path.join(setting.FILE_PATH.get('screenshot'), file_name)
self.__driver.get_screenshot_as_file(file_path)
def clear(self, locator: tuple):
try:
element = self.location_element(*locator)
element.clear()
logs.info("清空文本")
except NoSuchElementException as e:
logs.error(f"元素无法定位:{e}")
raise e
def ocr_captcha(self, locator: tuple):
captcha_element = self.location_element(*locator)
captcha_path = setting.FILE_PATH['screenshot'] + '/captcha.png'
captcha_element.screenshot(captcha_path)
captcha_image = Image.open(captcha_path)
try:
captcha_text = pytesseract.image_to_string(captcha_image)
logs.info(f"识别到的验证码为:{captcha_text}")
return captcha_text
except pytesseract.pytesseract.TesseractNotFoundError:
logs.error("找不到tesseract,这是因为pytesseract模块依赖于TesseractOCR引擎来进行图像识别!")
def assert_is_element_present(self, locator: tuple):
try:
element = self.__driver.find_element(*locator)
assert element.is_displayed(), '元素不存在'
except NoSuchElementException as e:
logs.error(f'元素未找到:{e}')
raise AssertionError('元素不存在')
def assert_element_not_visible(self, locator: tuple):
try:
self.__wait.until(ec.invisibility_of_element_located(locator))
except TimeoutException:
logs.error('元素可见')
def assert_title(self, expect_title):
assert expect_title in self.title
改造通用断言结果
13、改造断言结果
# 导包
from pageObject.register_page.register_page import RegisterPage
import pytest
from time import sleep
from util_tools.handle_data.readYaml import read_yaml
class TestRegister:
# 注册成功场景
@pytest.mark.parametrize('data', read_yaml('./data/register_success.yaml'))
def test_register_success(self, get_driver, data):
# 调用页面类相关的操作--直接调用RegisterPage()把浏览器对象get_driver传给它
register_page = RegisterPage(get_driver)
# 如果使用 *data 解包数据,data的元素数量必须跟click_register方法的入参个数保持一致
register_page.click_register(*data)
# 添加等待时间查看效果
sleep(3)
# 断言结果
register_page.assert_is_element_present(register_page.assert_result)
# 注册失败场景
@pytest.mark.parametrize('data', read_yaml('./data/register_failed.yaml'))
def test_register_failed(self, get_driver, data):
register_page = RegisterPage(get_driver)
register_page.click_register(*data)
sleep(3)
register_page.assert_element_not_visible(register_page.assert_result)
14、更改测试数据;
注册成功场景数据;(自行设置)
用户名、email 都不存在,可以注册成功;
- test03, 03@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
注册失败场景数据;(自行设置)
用户名已存在;
email 已存在;
- test01, 09@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
- test09, 02@126.com, 123456, 123456, 22@qq.com, 123456, 123, 456, 789, 注册测试
15、运行主函数 run.py 文件,可以发现注册成功和注册失败 3 条测试用例均通过测试;
16、可以使用随机函数等(自行学习了解)
未完待续。。。