Web UI 自动化的分层设计思想
项目目录结构
│ conftest.py
│ run.py
│ setting.py
│
├─cases
│ │ test_attendance.py
│ │ test_login.py
│ │ __init__.py
│ │
├─common
│ │ basepage.py
│ │ __init__.py
├─data
│ │ login_data.py
│ │ __init__.py
├─pages
│ │ course_page.py
│ │ home_page.py
│ │ login_page.py
│ │ __init__.py
├─report
│ 040e1a67-05be-48d2-ac48-d9c5d564fc3a-container.json
│ 05615248-db84-44a5-ae2a-0bb4faf7096b-container.json
└─
基础方法层 common
basepage.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/22 12:48
# @Author : shisuiyi
# @File : basepage.py
# @Software: win10 Tensorflow1.13.1 python3.9
import time
import allure
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class BasePage:
"""储存浏览器,页面的通用操作"""
def __init__(self, driver):
self.driver = driver
def goto(self, url):
self.driver.get(url)
def reload(self):
"""刷新"""
self.driver.refresh()
def wait_element_clickable(self, locator, timeout=10):
"""等待元素可以被点击"""
wait = WebDriverWait(self.driver, timeout=timeout, poll_frequency=0.2)
el = wait.until(EC.element_to_be_clickable(locator))
return el
def wait_element_visible(self, locator, timeout=10):
"""等待元素可见"""
wait = WebDriverWait(self.driver, timeout=timeout, poll_frequency=0.2)
el = wait.until(EC.visibility_of_element_located(locator))
return el
def wait_title_is(self, title, timeout=10):
"""等待 title 等于"""
wait = WebDriverWait(self.driver, timeout=timeout, poll_frequency=0.2)
# EC.title_is(title) 内置的显性等待,页面的标题是否等于
return wait.until(EC.title_is(title))
def wait_url_contains(self, url, timeout=10):
"""等待 url包含"""
wait = WebDriverWait(self.driver, timeout=timeout, poll_frequency=0.2)
return wait.until(EC.url_contains(url))
def get_element(self, locator):
"""查找元素"""
el = self.driver.find_element(*locator)
return el
def get_elements(self, locator):
"""查找多个元素"""
elements = self.driver.find_elements(*locator)
time.sleep(1)
return elements
def write(self, locator, value):
"""输入操作"""
el = self.driver.find_element(*locator)
el.send_keys(value)
return self # return self返回的是类的实例。
def click(self, locator):
"""鼠标点击。方法2"""
el = self.wait_element_clickable(locator)
# el = self.driver.find_element(*locator)
action = ActionChains(self.driver)
action.click(el).perform()
def double_click(self, locator):
el = self.wait_element_clickable(locator)
# el = self.driver.find_element(*locator)
action = ActionChains(self.driver)
action.double_click(el).perform()
def context_click(self, locator):
el = self.driver.find_element(*locator)
action = ActionChains(self.driver)
action.context_click(el).perform()
def click_and_hold(self, locator):
"""长按"""
try:
el = self.driver.find_element(*locator)
action = ActionChains(self.driver)
action.click_and_hold(el).perform()
except Exception as e:
print('[ERROR]-长按页面元素{}失败,原因:{}'.format(locator, e))
def clear_element(self, locator):
"""元素清空"""
self.wait_element_clickable(locator).clear()
return self
def select(self, locator1, locator2):
"""下拉选择"""
self.click(locator1)
self.click(locator2)
def enter(self):
"""回车"""
action = ActionChains(self.driver)
action.send_keys(Keys.ENTER).perform()
def send_file(self, locator, file_path):
"""发送文件"""
el = self.wait_element_visible(locator)
el.send_keys(file_path)
def move_to(self, locator):
"""鼠标悬停, locator = ('xpath', 'value')"""
el = self.driver.find_element(*locator)
action = ActionChains(self.driver)
action.move_to_element(el).perform()
def drag_and_drop(self, locator_start, locator_end):
start_el = self.wait_element_clickable(locator_start)
end_el = self.wait_element_clickable(locator_end)
action = ActionChains(self.driver)
action.drag_and_drop(start_el, end_el).perform()
def switch_to_iframe(self, iframe_reference, timeout=30):
"""iframe切换"""
# self.driver.switch_to.frame(iframe_reference)
wait = WebDriverWait(self.driver, timeout=timeout, poll_frequency=0.2)
wait.until(EC.frame_to_be_available_and_switch_to_it(iframe_reference))
# frame_to_be_available_and_switch_to_it 此方法会判断iframe是否可用,并且会自动切换到iframe中。
def allure_screenshot(self, name=None):
"""截图"""
f = self.driver.get_screenshot_as_png()
return allure.attach(f,
name=name,
attachment_type=allure.attachment_type.PNG)
def script(self, src):
"""
定义script方法,用于执行js脚本
"""
self.driver.execute_script(src)
def switch_window(self, n):
"""窗口切换"""
# WebDriver对象有window_handles 属性,这是一个列表对象, 里面包括了当前浏览器里面所有的窗口句柄。
self.driver.switch_to.window(self.driver.window_handles[n])
def assert_element_attribute_equal(self, locator, attr_name, expected):
"""断言元素的text文本等于"""
el = self.wait_element_visible(locator)
actual = el.get_attribute(attr_name)
print("文本", actual)
print(expected)
assert actual == expected
元素定位层Pages
course_page.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/25 20:19
# @Author : shisuiyi
# @File : course_page.py
# @Software: win10 Tensorflow1.13.1 python3.9
from common.basepage import BasePage
from time import sleep
class CoursePage(BasePage):
# 元素定位
# 考勤
attendance_locator = ('xpath', '//span[text()="考勤"]')
# iframe
iframe_locator = ('id', 'layui-layer-content1')
# 新建考勤
new_attendance_locator = ('link text', '新建考勤')
# 数字考勤
number_attendance_locator = ('xpath', '//span[@class="lable-title" and text()="数字考勤"]')
# 开始考勤
start_attendance_locator = ('xpath', '//a[text()="开始考勤"]')
# 获取考勤码
attendance_code_locator = ('xpath', '//div[@class="number-box"]/span')
# 考勤人数
student_number_locator = ('xpath', '//i[@class="ing"]')
# 结束考勤
finish_attendance_locator = ('xpath', '//a[text()="结束"]')
# 确认结束
confirm_finish_attendance_locator = ('xpath', '(//a[text()="结束"])[5]')
# 学生端立即签到
right_sign_locator = ('link text', '立即签到')
# 输入签到码
sign_code_locator = ('id', 'phoneVer_modalAuthInput')
def add_attendance(self):
"""
1,课程详情页, 点击 “考勤” //span[text()="考勤"]/..
2, 切换 iframe , id = layui-layer-content1
3, 点击 “新建考勤” link text 新建考勤
4, 点击 数字考勤 , //span[@class="lable-title" and text()="数字考勤"]
5, 点击 “开始考勤” , //a[text()="开始考勤"]
6, 获取考勤码, find_elements //div[@class="number-box"]/span ,
el = [], el.text
"""
self.click(self.attendance_locator)
sleep(3)
# 切换 iframe
self.switch_to_iframe(self.iframe_locator)
# 点击新建考勤
self.click(self.new_attendance_locator)
# 数字考勤
self.click(self.number_attendance_locator)
# 开始考勤
self.click(self.start_attendance_locator)
# 获取考勤码的四个元素
self.wait_element_visible(self.attendance_code_locator)
number_elements = self.get_elements(self.attendance_code_locator)
# numbers = []
numbers = [el.text for el in number_elements]
code = ''.join(numbers)
return code
def student_sign(self, code):
"""
1,立即签到 link text 立即签到
2,输入签到码 id phoneVer_modalAuthInput code
"""
self.click(self.right_sign_locator)
self.write(self.sign_code_locator, code)
def get_student_number(self):
"""获取考勤人数"""
el = self.get_element(self.student_number_locator)
return el.text
def end_attendance(self):
"""
# 老师端
1,结束 //a[text()="结束"]
2,再点击确认结束 (//a[text()="结束"])[5]
"""
self.click(self.finish_attendance_locator)
self.click(self.confirm_finish_attendance_locator)
home_page.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/21 20:43
# @Author : shisuiyi
# @File : home_page.py
# @Software: win10 Tensorflow1.13.1 python3.9
from common.basepage import BasePage
class HomePage(BasePage):
# 头像元素
avatar_locator = ('xpath', "//img[@class='avatar']")
def get_username(self, expected):
"""获取用户名称"""
self.assert_element_attribute_equal(self.avatar_locator, 'alt', expected)
def enter_course_page(self, course_name):
"""选择进入的课程"""
self.click(("link text", course_name))
login_page.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/20 20:34
# @Author : shisuiyi
# @File : login_page.py
# @Software: win10 Tensorflow1.13.1 python3.9
from selenium.webdriver.common.by import By
import setting
from common.basepage import BasePage
class LoginPage(BasePage):
"""登录页面"""
# 通过类属性获取元素的定位方式与元素定位表达式,便于修改与管理。
url = setting.host + '/User/login.html'
mobile_locator = (By.XPATH, '//input[@name="account"]')
password_locator = (By.XPATH, '//input[@name="pass"]')
login_btn_locator = (By.XPATH, '//a[@class="btn-btn" and text()="登录"]')
error_msg = (By.XPATH, '//p[@class="error-tips"]')
def login(self, *args):
"""登录"""
# 输入用户户名
# self.driver.find_element(By.XPATH, '//input[@name="account"]').send_keys(args[0])
self.write(self.mobile_locator, args[0])
# 输入密码
# self.driver.find_element(By.XPATH, '//input[@name="pass"]').send_keys(args[1])
self.write(self.password_locator, args[1])
# 点击登录
# self.driver.find_element(By.XPATH, '//a[@class="btn-btn" and text()="登录"]').click()
self.click(self.login_btn_locator)
def logout(self):
"""退出登录"""
pass
def forget_password(self):
"""忘记密码"""
pass
def get_error_msg(self):
"""获取登录失败的错误信息"""
return self.get_element(self.error_msg).text
测试数据层
login_data.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/21 20:58
# @Author : shisuiyi
# @File : login_data.py
# @Software: win10 Tensorflow1.13.1 python3.9
data_error = [['123', 'abc', '密码有效长度是6到30个字符'],
['123', '12345678', '用户名或密码无效'],
['188****7463', '1234568', '密码错误']]
data_success = [('188****7463', '********205f', '师语'), ]
测试用例层cases
test_login.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/13 19:46
# @Author : shisuiyi
# @File : test_login.py
# @Software: win10 Tensorflow1.13.1 python3.9
import pytest
from data.login_data import data_error, data_success
from pages.login_page import LoginPage
from pages.home_page import HomePage
class TestLogin(object):
"""测试登录功能"""
@pytest.mark.parametrize('username,password,expected', data_error, ids=['密码过短', '用户名或密码无效', '密码错误'])
def test_login_error(self, username, password, expected, driver):
login_page = LoginPage(driver)
login_page.goto(url=LoginPage.url)
login_page.login(username, password)
actual = login_page.get_error_msg()
assert expected in actual
@pytest.mark.parametrize('username,password,expected', data_success, ids=['登录成功'])
def test_login_success(self, username, password, expected, driver):
login_page = LoginPage(driver)
login_page.goto(url=LoginPage.url)
login_page.login(username, password)
HomePage(driver).get_username(expected)
test_attendance.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/24 20:43
# @Author : shisuiyi
# @File : test_attendance.py
# @Software: win10 Tensorflow1.13.1 python3.9
"""
1, 不要着急写代码
2, 梳理测试的步骤(手工测试)
3, 准备测试数据, 在哪个页面的操作, 元素定位表达式, 用户名,密码。
前置,后置的
4, 代码
"""
import time
import pytest
from pages.course_page import CoursePage
from pages.home_page import HomePage
@pytest.mark.attendance
class TestAttendance:
def test_attendance(self, teacher_driver, student_driver):
"""
老师: 学生:
1、老师访问登录页面,完成登录操作 https://v4.ketangpai.com/User/login.html
2、首页, 点击课程详情, 进入课程详情页, a python九歌
3,课程详情页, 点击 “考勤” //span[text()="考勤"]/..
4, 切换 iframe , id = layui-layer-content1
5, 点击 “新建考勤” link text 新建考勤
6, 点击 数字考勤 , //span[@class="lable-title" and text()="数字考勤"]
7, 点击 “开始考勤” , //a[text()="开始考勤"]
8, 获取考勤码, find_elements //div[@class="number-box"]/span ,
el = [], el.text
# 学生端
1, 学生登录
2, 进入课程页面 a python九歌
3, 立即签到 link text 立即签到
4, 输入签到码 id phoneVer_modalAuthInput code
# 老师端
//i[@class="ing"] text = 1
结束 //a[text()="结束"]
再点击确认结束 (//a[text()="结束"])[5]
:return:
"""
time.sleep(2)
# 老师登录加入课程页面,选择课程python九歌
HomePage(teacher_driver).enter_course_page('python九歌')
teacher_course_page = CoursePage(teacher_driver)
code = teacher_course_page.add_attendance()
# 学生进入课程页面,选择课程python九歌
time.sleep(2)
HomePage(student_driver).enter_course_page('python九歌')
student_course_page = CoursePage(student_driver)
student_course_page.student_sign(code)
# 强制等待,学生签到之后,消息队列(任务)
time.sleep(4)
actual = teacher_course_page.get_student_number()
assert actual == '1'
# 老师结束考勤
teacher_course_page.end_attendance()
测试脚手架
conftest.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/15 14:14
# @Author : shisuiyi
# @File : conftest.py
# @Software: win10 Tensorflow1.13.1 python3.9
import pytest
from selenium import webdriver
from pages.login_page import LoginPage
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上,
为解决使用 pytest.mark.parametrize 参数化的时候,加 ids 参数用例描述有中文时,
在控制台输出会显示unicode编码,中文不能正常显示。
:return:
"""
for item in items:
item.name = item.name.encode("utf-8").decode("unicode_escape")
print(item.nodeid)
item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
def get_driver():
"""获取浏览器"""
driver = webdriver.Chrome()
# 养成设置隐性
driver.implicitly_wait(8)
# 窗口最大化
driver.maximize_window()
return driver
@pytest.fixture(scope='session')
def driver():
"""管理浏览器的夹具"""
d = get_driver()
yield d
d.quit()
@pytest.fixture(scope='session')
def teacher_driver():
"""老师的浏览器"""
d = get_driver()
LoginPage(d).goto(url=LoginPage.url)
LoginPage(d).login('*******.com', 'ad****6')
yield d
d.quit()
@pytest.fixture(scope='session')
def student_driver():
"""学生的浏览器"""
d = get_driver()
LoginPage(d).goto(url=LoginPage.url)
LoginPage(d).login('*******.com', 'ad****6')
yield d
d.quit()
运行测试
run.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/1/12 20:41
# @Author : shisuiyi
# @File : run.py
# @Software: win10 Tensorflow1.13.1 python3.9
import pytest
pytest.main(['--alluredir=report'])
评论