目录
1. PO模式
继上次代码重构后,我们要继续学习,将我们的代码转换为PO模式。
所谓的PO模式即为:Page Object Model,强调将页面当作对象。
其优点为:
- 页面分层:页面元素和业务的逻辑进行区分
- 方便复用对象
- 每个页面都是一个独立的测试用例
- 自动化变的更容易
其分层为:
base层 --> 基础的内容
common层 --> 读取文件、日期处理、公共
data层 --> 数据的文件
logs层 --> 日志
PO层 --> 页面的业务代码
testcase层 --> 测试层用来存放测试代码
reports层 --> 用来存放测试报告
config ---> 配置文件
2. 目录结构
开发后代码目录结构为:
├─xcPO
│ │ __init__.py
│ │
│ ├─base
│ │ base.py
│ │ __init__.py
│ │
│ ├─common
│ │ function.py
│ │ __init__.py
│ │
│ ├─data
│ │ data.yaml
│ │ __init__.py
│ │
│ ├─po
│ │ filter_tickets_page.py
│ │ login_page.py
│ │ order_page.py
│ │ search_trains_page.py
│ │ __init__.py
│ │
│ ├─reports
│ │ __init__.py
│ │
│ └─testcase
│ │ cookies.txt
│ │ test_book_tickets.py
│ └─__init__.py
3. 代码
(1). base层——base.py
# base.py
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from time import sleep
class Base():
def __init__(self, driver):
self.driver = driver # type:WebDriver
# 捕获异常方法【用于判断某元素是否存在】
def isElementPresent(self, element):
try:
self.driver.find_element(By.XPATH, element)
return True
except Exception:
return False
# 根据XPATH定位元素
def byXpath(self, element):
return self.driver.find_element(By.XPATH, element)
# 获取driver
def getDriver(self):
return self.driver
# 基础浏览器操作
def openUrl(self, url):
self.driver.get(url)
# 最大化页面
self.driver.maximize_window()
# 关闭浏览器,释放资源
def close(self):
print("关闭浏览器并释放资源")
self.driver.quit()
# 滚动加载数据
def loadDataByScroll(self):
# 滚动加载数据
last_count = 0
# 开启循环,确保获取到所有数据
while True:
# 获取当前卡片数量
cards = self.driver.find_elements(By.XPATH, "//div[@class='card-white list-item']")
current_count = len(cards)
# 如果卡片数量没有变化,停止滚动
if current_count == last_count:
break
# 滚动到底部
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
last_count = current_count
# 等待新数据加载
sleep(2) # 根据加载速度调整等待时间
# 将页面滚动到顶部,方便观察
self.driver.execute_script("window.scrollTo(0, 0);")
(2). common层——function.py
# function.py
from ruamel.yaml import YAML
# 读取数据
def readYaml():
yaml = YAML()
# 保留引号和注释
yaml.preserve_quotes = True
# 获取配置文件中的数据
with open("../data/data.yaml", 'r', encoding='utf-8') as file:
data = yaml.load(file)
return data
def writeYaml(data):
yaml = YAML()
# 保留引号和注释
yaml.preserve_quotes = True
with open('../data/data.yaml', 'w', encoding='utf-8') as f:
yaml.dump(data, f)
(3). data层——data.yaml
# data.yaml
cookiesFilePath: "./cookies.txt" # cookies本地存储路径
# 输入
inputs:
- trainInfo:
departure: "开封" # 出发站
destination: "郑州" # 到达站
date: "2024-10-10" # 日期
seatTypes:
- "硬座"
- "二等座"
- "商务座"
passengers:
- name: "张三"
ID: "410201200010010125"
tel: "13579325643"
type: "成人"
- name: "李四"
ID: "410201200010010125"
tel: "13579325644"
type: "学生"
filter:
departTime: "15:00" # 预期出发时间,最早
arrivalTime: "18:00" # 预期抵达时间,最晚
onlyAvailable: true # 仅显示有票车次
trainTypes: # 车型
- type: "高铁(G/C)"
select: false
- type: "动车(D)"
select: true
- type: "普通(Z/T/K)"
select: true
- type: "其他(L/Y)"
select: true
- trainInfo:
departure: "郑州" # 出发站
destination: "开封" # 到达站
date: "2024-10-10" # 日期
seatTypes:
- "硬座"
- "二等座"
- "商务座"
passengers:
- name: "张三"
ID: "410201200010010125"
tel: "13579325643"
type: "成人"
filter:
departTime: "15:00" # 预期出发时间,最早
arrivalTime: "18:00" # 预期抵达时间,最晚
onlyAvailable: true # 仅显示有票车次
trainTypes: # 车型
- type: "高铁(G/C)"
select: false
- type: "动车(D)"
select: true
- type: "普通(Z/T/K)"
select: true
- type: "其他(L/Y)"
select: true
seats: # 坐席
- type: "硬座"
available: true
- type: "硬卧"
available: true
- type: "软卧"
available: true
- type: "无座"
available: true
- type: "高级软卧"
available: false
- type: "二等座"
available: true
- type: "二等卧"
available: true
- type: "一等卧"
available: true
- type: "一等座"
available: true
- type: "商务座"
available: true
- type: "优选一等座"
available: false
(4). po层——login_page.py
# login_page.py
import os
import json
from time import sleep
from selenium.webdriver.common.by import By
from xctest.xcPO.base.base import Base
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class loginPage(Base):
def getLoginBtn(self):
return self.byXpath("//button[@class='tl_nfes_home_header_login_not_frb4a']")
def getQRLoginBtn(self):
return self.driver.find_element(By.PARTIAL_LINK_TEXT, "扫码登录")
def getRefreshBtn(self):
return self.byXpath("//a[text()='刷新']")
def checkCookiesFile(self, cookiesFilePath):
return os.path.exists(cookiesFilePath) and os.path.getsize(cookiesFilePath) > 0
def injectCookies(self, cookiesFilePath):
# 注入cookies
with open(cookiesFilePath, 'r') as file:
cookies = json.load(file)
for cookie in cookies:
# 添加 cookie 之前,删除 'expiry' 属性,防止不兼容问题
if 'expiry' in cookie:
del cookie['expiry']
self.driver.add_cookie(cookie)
def handleManualLogin(self, cookiesFilePath):
# 获取页面元素
loginBtn = self.getLoginBtn()
# 点击登录按钮
loginBtn.click()
sleep(1)
# 隐式等待
# self.driver.implicitly_wait(10)
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, "扫码登录")))
# 切换登录方式为:扫码登录
QRLoginBtn = self.getQRLoginBtn()
QRLoginBtn.click()
# 循环等待扫码完成
while True:
print("===========开始循环===========")
if self.isElementPresent("//p[text()='二维码已失效']"):
print("=========二维码已过期=========")
sleep(3)
refreshBtn = self.getRefreshBtn()
refreshBtn.click()
sleep(3)
print("=========二维码已刷新=========")
elif self.isElementPresent("//*[@id='label-departStation']"):
print("===========登录成功===========")
break
else:
print("=========等待扫描QR码=========")
sleep(10)
def saveCookies(self, cookiesFilePath):
cookies = self.driver.get_cookies()
# 保存cookies到本地
with open(cookiesFilePath, 'w') as file:
json.dump(cookies, file)
print("====cookies写入完成====")
def login(self, cookiesFilePath):
# 打开URL
# url = "https://trains.ctrip.com/"
# self.openUrl(url)
# 判断cookies.txt是否存在及检测cookies.txt内是否有内容
if self.checkCookiesFile(cookiesFilePath):
print("将使用Cookies实现自动登录~")
self.injectCookies(cookiesFilePath)
else:
print("请手动登录~")
self.handleManualLogin(cookiesFilePath)
self.saveCookies(cookiesFilePath)
sleep(3)
print("浏览器刷新~")
self.driver.refresh()
sleep(3)
(5). po层——search_trains_page.py
# search_trains_page.py
from time import sleep
from xctest.xcPO.base.base import Base
from selenium.webdriver.common.keys import Keys
class searchTrainsPage(Base):
def getDepartureStation(self):
return self.byXpath("//*[@id='label-departStation']")
def getArrivalStation(self):
return self.byXpath("//*[@id='label-arriveStation']")
def getDatePicker(self):
return self.byXpath("//*[@id='label-departDate']")
def getSearchBtn(self):
return self.byXpath("//*[text()='搜索']")
def delContent(self, element):
element.send_keys(Keys.CONTROL, 'a')
element.send_keys(Keys.DELETE)
def enableDatePickerStyle(self):
# 使用 JavaScript 添加日期控件input样式
js_code = """
var styles = '.assist-block-dom, .assist-flex-dom, .assist-ib-dom { display: block !important; }';
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
"""
self.driver.execute_script(js_code)
def searchTrain(self, departure, destination, date):
# 获取页面元素
departureStation = self.getDepartureStation()
arrivalStation = self.getArrivalStation()
datePicker = self.getDatePicker()
searchBtn = self.getSearchBtn()
# 确保日期控件可输入
self.enableDatePickerStyle()
sleep(1)
# 清除原有内容
self.delContent(departureStation)
self.delContent(arrivalStation)
self.delContent(datePicker)
# 输入出发站、到达站和日期
departureStation.send_keys(f"{departure}")
arrivalStation.send_keys(f"{destination}")
datePicker.send_keys(f"{date}")
# 点击搜索按钮
self.driver.execute_script("arguments[0].click();", searchBtn)
# 隐式等待
self.driver.implicitly_wait(5)
print(f"本次出发站为:{departure},到达站为:{destination}")
(6). po层——filter_tickets_page.py
# filter_tickets_page.py
from time import sleep
from xctest.xcPO.base.base import Base
from selenium.webdriver.common.keys import Keys
class searchTrainsPage(Base):
def getDepartureStation(self):
return self.byXpath("//*[@id='label-departStation']")
def getArrivalStation(self):
return self.byXpath("//*[@id='label-arriveStation']")
def getDatePicker(self):
return self.byXpath("//*[@id='label-departDate']")
def getSearchBtn(self):
return self.byXpath("//*[text()='搜索']")
def delContent(self, element):
element.send_keys(Keys.CONTROL, 'a')
element.send_keys(Keys.DELETE)
def enableDatePickerStyle(self):
# 使用 JavaScript 添加日期控件input样式
js_code = """
var styles = '.assist-block-dom, .assist-flex-dom, .assist-ib-dom { display: block !important; }';
var styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
"""
self.driver.execute_script(js_code)
def searchTrain(self, departure, destination, date):
# 获取页面元素
departureStation = self.getDepartureStation()
arrivalStation = self.getArrivalStation()
datePicker = self.getDatePicker()
searchBtn = self.getSearchBtn()
# 确保日期控件可输入
self.enableDatePickerStyle()
sleep(1)
# 清除原有内容
self.delContent(departureStation)
self.delContent(arrivalStation)
self.delContent(datePicker)
# 输入出发站、到达站和日期
departureStation.send_keys(f"{departure}")
arrivalStation.send_keys(f"{destination}")
datePicker.send_keys(f"{date}")
# 点击搜索按钮
self.driver.execute_script("arguments[0].click();", searchBtn)
# 隐式等待
self.driver.implicitly_wait(5)
print(f"本次出发站为:{departure},到达站为:{destination}")
(7). po层——order_page.py
# order_page.py
from time import sleep
from xctest.xcPO.base.base import Base
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class orderPage(Base):
def getAddPassengerBtn(self):
return self.byXpath("//button[text()='新增乘客']")
def getPassengerType(self, type):
return self.byXpath(f"//div[p[text()='{type}']]")
def getNameInput(self):
return self.byXpath("//input[contains(@class, 'input-txt') and contains(@class, 'focus-cNName')]")
def getIDInput(self):
return self.byXpath("//input[contains(@class, 'input-txt') and contains(@class, 'focus-identityNo')]")
def getTelInput(self):
return self.byXpath("//input[contains(@class, 'input-txt') and contains(@class, 'focus-mobilePhone')]")
def getAddBtn(self):
return self.byXpath("//button[text()='确认添加']")
def inputToElement(self, element, content):
element.send_keys(f"{content}")
def getPassengerCard(self, name):
return self.byXpath(f"//span[text()='{name}']/ancestor::div[2][@class='PassengerCell_innerContainer__PPhbr']")
def getConfirmBtn(self):
return self.byXpath("//div[contains(@class, 'passengerList_bottomButton__VF11U')]")
def addPassengers(self, passengers):
nameList = []
for passenger in passengers:
nameList.append(passenger['name'])
if not self.isElementPresent(f"//li[.//div[@class='name' and text()='{passenger['name']}']]"):
# 获取新增乘客按钮、乘客类型、姓名、身份证号和手机号元素
addPassengerBtn = self.getAddPassengerBtn()
passengerType = self.getPassengerType(passenger['type'])
nameInput = self.getNameInput()
IDInput = self.getIDInput()
telInput = self.getTelInput()
# 点击新增游客并选择乘客类型
addPassengerBtn.click()
passengerType.click()
# 输入姓名和身份证号
self.inputToElement(nameInput, passenger['name'])
self.inputToElement(IDInput, passenger['ID'])
self.inputToElement(telInput, passenger['tel'])
# 点击添加按钮
addBtn = self.getAddBtn()
addBtn.click()
sleep(1)
self.selectPassengers(nameList)
def selectPassengers(self, nameList):
self.driver.find_element(By.XPATH, "//button[contains(@class, 'btn-blue') and contains(@class, 'btn-add')]").click()
# 显式等待iframe加载完成
WebDriverWait(self.driver, 5).until(EC.presence_of_element_located((By.XPATH, "//iframe[@id='picker-iframe']")))
sleep(2)
# 切换frame
iframe = self.byXpath("//iframe[@id='picker-iframe']")
self.driver.switch_to.frame(iframe)
# 添加乘客信息
for name in nameList:
passengerCard = self.getPassengerCard(name)
passengerCard.click()
sleep(1)
confirmBtn = self.getConfirmBtn()
# driver.execute_script("arguments[0].click()", confirmBtn)
confirmBtn.click()
sleep(1)
# 切换回上一个页面
self.driver.switch_to.parent_frame()
(8). testcase层——test_book_tickets.py
# test_book_tickets.py
from xctest.xcPO.po.login_page import loginPage
from xctest.xcPO.po.search_trains_page import searchTrainsPage
from xctest.xcPO.po.filter_tickets_page import filterTicketsPage
from xctest.xcPO.po.order_page import orderPage
from selenium import webdriver
from xctest.xcPO.common.function import readYaml
import pytest
data = readYaml()
class TestBookTickets():
def setup_method(self, method):
self.driver = webdriver.Chrome()
url = "https://trains.ctrip.com/"
self.driver.get(url)
self.driver.maximize_window()
param_data = [(data['cookiesFilePath'], row, data) for row in data['inputs']]
@pytest.mark.parametrize(["cookiesFilePath", "row", "data"], param_data)
def test_01(self, cookiesFilePath, row, data):
login = loginPage(self.driver)
login.login(cookiesFilePath)
trains = searchTrainsPage(self.driver)
trains.searchTrain(row['trainInfo']['departure'], row['trainInfo']['destination'], row['trainInfo']['date'])
tickets = filterTicketsPage(self.driver)
tickets.checkSeats(data['seats'], data)
tickets.filterTickets(row['filter']['onlyAvailable'], row['filter']['trainTypes'], row['filter']['departTime'], row['filter']['arrivalTime'], data['seats'], row['trainInfo']['seatTypes'])
order = orderPage(self.driver)
order.addPassengers(row['passengers'])
def teardown_method(self, method):
self.driver.quit()
if __name__ == '__main__':
pytest.main(["-s", "test_book_tickets.py"])
3. 总结
理解代码具体内容、逻辑以及开发思路请根据以下顺序自行查看:
- Selenium处理携程日期组件的方法
- 【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试
- 【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——代码重构篇
本篇
【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——PO模式篇
侵权必删声明
本资料部分内容来源于互联网及公开渠道,仅供学习和交流使用,版权归原作者所有。若涉及版权问题,敬请原作者联系我,我将立即处理或删除相关内容。感谢您的理解与支持。