首页 > 编程语言 >【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——PO模式篇

【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——PO模式篇

时间:2024-10-09 19:51:50浏览次数:3  
标签:self py driver 测试 自动化 import type 携程网 def

目录

1. PO模式

继上次代码重构后,我们要继续学习,将我们的代码转换为PO模式。
所谓的PO模式即为:Page Object Model,强调将页面当作对象。
其优点为:

  1. 页面分层:页面元素和业务的逻辑进行区分
  2. 方便复用对象
  3. 每个页面都是一个独立的测试用例
  4. 自动化变的更容易

其分层为:

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. 总结

理解代码具体内容、逻辑以及开发思路请根据以下顺序自行查看:

  1. Selenium处理携程日期组件的方法
  2. 【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试
  3. 【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——代码重构篇
  4. 本篇【Web自动化测试】Python + Selenium实现携程网火车票订购的自动化测试——PO模式篇

侵权必删声明
本资料部分内容来源于互联网及公开渠道,仅供学习和交流使用,版权归原作者所有。若涉及版权问题,敬请原作者联系我,我将立即处理或删除相关内容。感谢您的理解与支持。

标签:self,py,driver,测试,自动化,import,type,携程网,def
From: https://blog.csdn.net/weixin_51055612/article/details/142794722

相关文章

  • AI预测福彩3D采取888=3策略+和值012路或胆码测试10月9日新模型预测第105弹
            经过100多期的测试,当然有很多彩友也一直在观察我每天发的预测结果,得到了一个非常有价值的信息,那就是9码定位的命中率非常高,100多期一共只错了12次,这给喜欢打私房菜的朋友提供了极高价值的预测结果~当然了,大部分菜友还是走的正常渠道,因此,得想办法进行缩水,尽可能......
  • iPhone 16 Pro上百款充电头充电功率测试
    一、苹果原装充电器测试截图:以下所有测试均为“充电头网实验室”评测,且测试手机为最新的iPhone16Pro手机,充电线均为iPhone16Pro最新的原装编织充电线,所以只有充电头是不同的,排除了其他因素的干扰。视频网站来自于B站:https://www.bilibili.com/video/BV1XixLe9Eaq/?vd_sou......
  • webapi测试例子
     1.修改WebApiConfig.cs中路由路径  问题:webapi的默认路由并不需要指定action的名称(WebApi的默认路由是通过http的方法get/post/put/delete去匹配对应的action),        但默认路由模板无法满足针对一种资源一种请求方式的多种操作。  解决:打开App_Sta......
  • 使用python对交换机进行排障自动化运维(锐捷)
    importglobimporttelnetlibimportrefromdatetimeimportdatetimefromtimeimportsleepimportpandasaspdimportosimporttimefrommatplotlibimportpyplotasplt#Telnet连接函数defconnect_telnet(hostname,username,password):  try:  ......
  • 测试要不要转岗产品经理?
    星球有同学问了这样一个问题:在公司有机会转岗产品经理,目前是软件测试岗位,要不要转产品经理?老实说,测试转开发或者开发转测试的案例有很多,转岗后做的好的也不少,毕竟都是技术岗位,底层技术都是通用的。但测试转产品岗位确实比较少见,且成功的案例更是寥寥,因为这两者背后最大的差异是......
  • 智驾仿真测试实战之自动泊车HiL仿真测试
    1.引言 汽车进入智能化时代,自动泊车功能已成为标配。在研发测试阶段,实车测试面临测试场景覆盖度不足、效率低下和成本高昂等挑战。为解决这些问题,本文提出一种自动泊车HiL仿真测试系统方案,可大幅度提升测试效率及测试场景覆盖度、缩短测试周期、加速产品迭代升级。 2.自动泊......
  • 性能测试的类型有哪些
    目录1.基准测试2.负载测试3.压力测试4.峰值测试5.并发测试6.容积测试7.稳定性测试8.可扩展性测试9.配置测试性能测试是为测量或评估被测软件系统与性能效率相关的特性而实施的一类测试,它关注被测系统在不同负载下的各种性能效率。软件系统的性能效率相关特性的覆盖......
  • 高带宽示波器在信号测试分析中的优势和主要应用场景
    最近,普源精电推出了一款13GHz带宽的示波器DS81304,。有些小伙伴会好奇,为什么普源示波器的带宽会从5GHz跳到13GHz,为什么不是到10GHz或者15GHz呢?13GHz的示波器又能干些什么呢?下面讲为大家介绍,为什么DS81304设计为13GHz带宽,以及DS81304相比5GHz带宽的DS70504又能有什么特点。为......
  • 【星闪开发连载】SLE_UUID_Server和SLE_UUID_Client程序测试
    引言前一篇博文介绍了SLE_UUID_Server和SLE_UUID_Client程序的基本结构,这篇介绍如何进行测试,从而实现两块星闪开发板之间的连接。服务器的构建在sdk根目录下(即src目录)打开集成终端台,执行python build.py-cws63-liteos-appmenuconfig命令,会出现选择弹窗。menuconfig这......
  • 软件测试与测试阶段
    软件测试概述    软件测试是使用人工或自动的手段来运行或测定某个软件系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。    软件测试的目的就是确保软件的质量、确认软件以正确的方式做了用户所期望的事情,所以软件测试工......