软件测试大赛web测试-python【备赛3】
24年预选赛-12306
测试需求
1. 单程车票查询
- 需求1(R001): 用户进入单程车票查询页面,输入有效出发地、目的地和出发日后,系统应能正确显示符合条件的车次列表。(用例数不超过10条)
输入数据要求:
- 出发地:仅考虑以下2个城市(北京、上海);
- 目的地:仅考虑以下2个城市(广州、成都);
- 出发日:根据预售期确定有效等价类并进行边界值分析;
- 需求2(R002): 用户进入单程车票查询页面,用户输入合法出发地、合法目的地以及无效出发日时,系统应显示相应的错误提示信息。(用例数不超过3条)
输入数据要求:
- 出发地:输入任意合法出发地;
- 目的地:输入任意合法目的地;
- 出发日:根据预售期确定无效等价类并进行边界值分析;
- 需求3(R003): 用户进入单程车票查询页面,出发地输入北京、目的地输入上海和出发日期输入2024-10-30,系统显示符合条件的车次列表,在此基础上对查询结果进行筛选,筛选应能根据用户选择的车次类型、出发车站、到达车站和车次席别正确筛选结果。通过鼠标点击的方式依次对车次类型、出发车站、到达车站和车次席别选中第一个可筛选项并取消选中的方式来验证。
2. 往返车票查询
- 需求4(R004):用户进入往返车票查询页面,输入有效出发地、目的地、出发日、返程日后,系统应能正确显示符合条件的车次列表。(用例数不超过4条)
- 出发地:输入任意合法出发地;
- 目的地:输入任意合法目的地;
- 出发日:输入2024-11-01;
- 返程日:根据可选择的返程日确定有效等价类并进行边界值分析;
- 需求5(R005):用户进入往返车票查询页面,用户输入有效的信息(出发地、目的地、出发日)和无效的返程日,系统应显示相应的错误提示信息。(用例数不超过3条)
- 出发地:输入任意合法出发地;
- 目的地:输入任意合法目的地;
- 出发日:输入2024-11-02;
- 返程日:根据可选择的返程日确定无效等价类并进行边界值分析;
- 需求6(R006): 用户进入往返车票查询页面,使用车站选择控件的方式选择出发地(FGHIJ->福州南),同样使用车站选择控件选择目的地(热门->厦门),出发日输入2024-10-30,返程日输入2024-11-06,选择学生票进行查询。查询出结果后,在Tab中日期选择11-03周日,然后车次类型选择最后一个复选框,出发车站和到达车站选择全部,同时最后勾选显示折扣车次,截图查询结果。
3. 中转车票查询
- 需求7(R007): 用户进入中转车票查询页面,输入有效出发地、目的地和乘车日期,同时不指定换乘站后进行查询,系统应能正确显示符合条件的中转换乘车次列表。(用例数不超过10条)
- 出发地:仅考虑以下3个城市(哈尔滨、长春、安吉);
- 目的地:仅考虑以下3个城市(济南、西安、拉萨);
- 出发日:比赛当天日期的后一天;
- 需求8(R008): 用户进入中转车票查询页面,输入有效出发地、目的地和乘车日期后和无效换乘站,系统应显示相应的错误提示信息。(用例数不超过4条)
- 出发地:输入任意合法出发地;
- 目的地:输入任意合法目的地;
- 出发日:比赛当天日期的后一天;
- 换乘站:(为空、特殊字符、无效的换乘站);
- 需求9(R009) :用户进入中转车票查询页面,输入有效出发地、目的地和乘车日期后,勾选指定换乘站并输入的换乘站点,系统应能显示通过指定换乘站的中转方案。(用例数不超过4条)
- 出发地:仅考虑以下1个城市(哈尔滨);
- 目的地:仅考虑以下1个城市(南京);
- 出发日:比赛当天日期的后一天;
- 换乘站:仅考虑以下3个城市(济南、安阳、周口);
4. 计次/定期车票查询
- 需求10(R010): 用户进入计次·定期车票查询页面,输入有效出发地和目的地后,系统应能正确显示符合条件的计次票和定期票列表。(用例数不超过5条,选手自由发挥)
- 需求11(R011): 用户进入计次·定期车票查询页面,输入无效出发地和目的地后,系统应显示“未查到相关产品”的提示信息。(用例数不超过5条,选手自由发挥)
注意事项
1、为了提高代码的可读性和可维护性,应优先使用带有语意的定位器,请按照以下优先级使用定位器:ID > NAME >CLASSNAME > LINKTEXT > CSS Selector> XPATH;当出现超链接的时候,需将LINKTEXT优先级作为第一位。
2、对于计次·定期车票查询的需求10、需求11,不再对输入数据做要求,选手自由发挥,但用例数不得超过5条。
3、在设计需求10、需求11的自动化脚本时,如遇到登录操作,请使用扫码登录的方式。
代码:
import os
from datetime import datetime
import pytest
from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from time import sleep
@pytest.fixture(scope="function")
def driver():
service = Service(
# 提交最终代码脚本时,请将驱动路径换回官方路径"C:\\Users\\86153\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe"
executable_path="C:\\Users\\86153\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe")
driver = webdriver.Chrome(service=service)
driver.get("https://www.12306.cn/index/index.html")
driver.maximize_window()
yield driver
driver.quit()
class Test12306:
# test-code-start
def test_12306_R001(self, driver):
# 进入单程页面
# 1. 定位主菜单“车票”(超链接)
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
# 2. 悬停(触发子菜单显示)
ActionChains(driver).move_to_element(ticket_menu).perform() # 悬停
sleep(0.5)
# 3. 点击子菜单“单程”(也是超链接)
driver.find_element(By.LINK_TEXT, "单程").click() # 点击“单程”
sleep(0.5)
test_cases = [
("北京", "广州", "2025-10-31", "12306_R001_001.png"),
("北京", "成都", "2025-11-07", "12306_R001_002.png"),
("上海", "广州", "2025-11-14", "12306_R001_003.png"),
("上海", "成都", "2025-10-31", "12306_R001_004.png"),
("北京", "广州", "2025-11-14", "12306_R001_005.png"),
("北京", "成都", "2025-10-31", "12306_R001_006.png"),
("上海", "广州", "2025-11-07", "12306_R001_007.png"),
("上海", "成都", "2025-11-14", "12306_R001_008.png"),
("北京", "成都", "2025-11-14", "12306_R001_009.png"),
]
for from_city, to_city, date, filename in test_cases:
# 清空并输入出发地
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER) # 确认选择
# 同样操作目的地
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
# 输入日期
date_input = driver.find_element(By.ID, "train_date")
date_input.clear()
date_input.send_keys(date)
# 点击查询
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, filename)
# 重新进入首页,避免状态残留(重要!)
driver.get("https://www.12306.cn/index/index.html")
# 重新进入单程页面(下一轮循环)
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "单程").click()
sleep(0.5)
def test_12306_R002(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "单程").click()
sleep(0.5)
test_cases = [
("郑州东", "上海", "2025-10-25", "12306_R002_001.png"),
("郑州东", "上海", "2025-11-16", "12306_R002_002.png"),
]
for from_city, to_city, date, filename in test_cases:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
date_input = driver.find_element(By.ID, "train_date")
date_input.clear()
date_input.send_keys(date)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, filename)
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "单程").click()
sleep(0.5)
def test_12306_R003(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "单程").click()
sleep(0.5)
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys("北京")
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys("上海")
sleep(0.3)
to_input.send_keys(Keys.ENTER)
date_input = driver.find_element(By.ID, "train_date")
date_input.clear()
date_input.send_keys("2025-10-31") # 按需求文档
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(3)
# 筛选:依次点击第一个可选项并取消
try:
# 查询后,依次点击并取消筛选项
checkboxes = [
driver.find_element(By.NAME, "cc_type"), # 车次类型
driver.find_element(By.NAME, "cc_from_station"), # 出发车站
driver.find_element(By.NAME, "cc_to_station"), # 到达车站
driver.find_element(By.NAME, "cc_seat_type"), # 席别
]
for cb in checkboxes:
cb.click() # 选中第一个
sleep(0.5)
cb.click() # 取消
sleep(0.5)
except Exception:
pass # 若筛选项未加载,跳过不影响截图
self.take_screenshot(driver, "12306_R003_001.png")
def test_12306_R004(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "往返").click()
sleep(0.5)
test_cases = [
("郑州东", "上海", "2025-11-01", "2025-11-01", "12306_R004_001.png"),
("郑州东", "上海", "2025-11-01", "2025-11-08", "12306_R004_002.png"),
("郑州东", "上海", "2025-11-01", "2025-11-15", "12306_R004_003.png"),
]
for from_city, to_city, depart, return_date, filename in test_cases:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
depart_input = driver.find_element(By.ID, "train_date")
depart_input.clear()
depart_input.send_keys(depart)
return_input = driver.find_element(By.ID, "back_train_date")
return_input.clear()
return_input.send_keys(return_date)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, filename)
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "往返").click()
sleep(0.5)
def test_12306_R005(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "往返").click()
sleep(0.5)
test_cases = [
("郑州东", "上海", "2025-11-02", "2025-11-01", "12306_R005_001.png"),
("郑州东", "上海", "2025-11-02", "2025-11-17", "12306_R005_002.png"),
]
for from_city, to_city, depart, return_date, filename in test_cases:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
depart_input = driver.find_element(By.ID, "train_date")
depart_input.clear()
depart_input.send_keys(depart)
return_input = driver.find_element(By.ID, "back_train_date")
return_input.clear()
return_input.send_keys(return_date)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, filename)
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "往返").click()
sleep(0.5)
def test_12306_R006(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "往返").click()
sleep(0.5)
# 使用车站选择控件选择“福州南”和“厦门”
from_input = driver.find_element(By.ID, "fromStationText")
from_input.click()
sleep(0.5)
# 点击 FGHIJ 分组
driver.find_element(By.ID, "nav_list3").click()
sleep(0.5)
# 选择“福州南”
driver.find_element(By.XPATH, "//li[@title='福州南']").click()
to_input = driver.find_element(By.ID, "toStationText")
to_input.click()
sleep(0.5)
# 选择热门城市中的“厦门”
driver.find_element(By.XPATH, "//li[@title='厦门']").click()
depart_input = driver.find_element(By.ID, "train_date")
depart_input.clear()
depart_input.send_keys("2025-10-31")
return_input = driver.find_element(By.ID, "back_train_date")
return_input.clear()
return_input.send_keys("2025-11-07")
# 勾选学生票
driver.find_element(By.ID, "sf2").click()
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(3)
# 筛选操作
try:
# 选择 11-03(假设是第4个日期Tab)
driver.find_element(By.XPATH, "//*[@id='date_range']/ul/li[4]").click()
sleep(0.5)
# 车次类型最后一个复选框(假设是第8个)
driver.find_element(By.XPATH, "//*[@id='_ul_station_train_code']/li[8]").click()
sleep(0.5)
# 出发车站“全部”
driver.find_element(By.ID, "from_station_name_all").click()
sleep(0.5)
# 到达车站“全部”
driver.find_element(By.ID, "to_station_name_all").click()
sleep(0.5)
# 显示折扣车次
driver.find_element(By.ID, "avail_zk").click()
sleep(1)
except Exception:
pass
self.take_screenshot(driver, "12306_R006_001.png")
def test_12306_R007(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
cities_from = ["哈尔滨", "长春", "安吉"]
cities_to = ["济南", "西安", "拉萨"]
date = "2025-11-01" # 比赛当天后一天(当前是2025-10-31)
idx = 1
for f in cities_from:
for t in cities_to:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(f)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(t)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
date_input = driver.find_element(By.ID, "train_start_date")
date_input.clear()
date_input.send_keys(date)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, f"12306_R007_{idx:03d}.png")
idx += 1
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
def test_12306_R008(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
test_cases = [
("郑州东", "上海", "2025-11-01", "", "12306_R008_001.png"),
("郑州东", "上海", "2025-11-01", "&*", "12306_R008_002.png"),
("郑州东", "上海", "2025-11-01", "红苹果", "12306_R008_003.png"),
]
for from_city, to_city, date, change_station, filename in test_cases:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
date_input = driver.find_element(By.ID, "train_start_date")
date_input.clear()
date_input.send_keys(date)
# 勾选“指定换乘站”
driver.find_element(By.ID, "radio_input_search").click()
sleep(0.3)
change_input = driver.find_element(By.ID, "changeStationText")
change_input.clear()
change_input.send_keys(change_station)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(3)
self.take_screenshot(driver, filename)
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
def test_12306_R009(self, driver):
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
test_cases = [
("哈尔滨", "南京", "2025-11-01", "济南", "12306_R009_001.png"),
("哈尔滨", "南京", "2025-11-01", "安阳", "12306_R009_002.png"),
("哈尔滨", "南京", "2025-11-01", "周口", "12306_R009_003.png"),
]
for from_city, to_city, date, change_station, filename in test_cases:
from_input = driver.find_element(By.ID, "fromStationText")
from_input.clear()
from_input.click()
from_input.send_keys(from_city)
sleep(0.3)
from_input.send_keys(Keys.ENTER)
to_input = driver.find_element(By.ID, "toStationText")
to_input.clear()
to_input.click()
to_input.send_keys(to_city)
sleep(0.3)
to_input.send_keys(Keys.ENTER)
date_input = driver.find_element(By.ID, "train_start_date")
date_input.clear()
date_input.send_keys(date)
driver.find_element(By.ID, "radio_input_search").click()
sleep(0.3)
change_input = driver.find_element(By.ID, "changeStationText")
change_input.clear()
change_input.send_keys(change_station)
sleep(0.3)
change_input.send_keys(Keys.ENTER)
driver.find_element(By.LINK_TEXT, "查询").click()
sleep(2)
self.take_screenshot(driver, filename)
driver.get("https://www.12306.cn/index/index.html")
ticket_menu = driver.find_element(By.LINK_TEXT, "车票")
ActionChains(driver).move_to_element(ticket_menu).perform()
sleep(0.5)
driver.find_element(By.LINK_TEXT, "中转换乘").click()
sleep(0.5)
# 自由发挥部分(R010/R011)可按需添加,此处略
# test-code-end
@staticmethod
def take_screenshot(driver, file_name):
timestamp = datetime.now().strftime("%H%M%S%f")
timestamped_file_name = f"{timestamp}_{file_name}"
screenshots_dir = "screenshots12306"
if not os.path.exists(screenshots_dir):
os.makedirs(screenshots_dir)
screenshot_file_path = os.path.join(screenshots_dir, timestamped_file_name)
driver.save_screenshot(screenshot_file_path)
元素定位策略(核心原则)
根据 【注意事项】 和 12306 页面结构,定位优先级为:
ID > NAME > CLASSNAME > LINKTEXT > CSS > XPATH
且 超链接优先用 LINK_TEXT
元素定位方式(来自 12306 页面):
|
“车票”菜单 |
|
|
|
“单程”链接 |
|
|
|
出发地输入框 |
|
|
|
目的地输入框 |
|
|
|
出发日期 |
|
|
|
查询按钮 |
|
|
|
往返返程日 |
|
|
|
中转换乘日期 |
|
|
|
指定换乘站输入框 |
|
|
|
学生票复选框 |
|
|
⚠️ 注意:12306 的“查询”按钮是
<a>标签,文字为“查 询”(中间有空格),但 Selenium 的LINK_TEXT会自动忽略多余空格,所以写"查询"即可匹配。
关键技巧总结
|
悬停菜单 |
|
|
输入城市后确认 |
|
|
避免状态残留 |
每次循环 |
|
日期输入 |
直接 |
|
截图命名 |
自动加时间戳,符合规范 |
|
元素找不到 |
优先用 ID/LINK_TEXT,实在不行再用 XPATH |
什么是等价类(Equivalence Class)
在软件测试中,等价类划分 是一种黑盒测试技术,其核心思想是:
如果输入数据在某个范围内行为相同,那么只需选一个代表值测试即可,无需穷举所有值。
两类等价类:
|
有效等价类(Valid Equivalence Class) |
符合业务规则、系统应正常处理的输入 |
2025-10-31 到 2025-11-14(15天预售期内) |
|
无效等价类(Invalid Equivalence Class) |
不符合业务规则、系统应报错或拒绝的输入 |
2025-10-30(已过期)、2025-11-15(超出预售期)、2025-13-01(非法日期) |
总结:比赛当天 2025-10-31,测试日期应如何选?
|
R001(单程有效) |
有效等价类 |
2025-10-31(边界) |
|
R002(单程无效) |
无效等价类 |
2025-10-30(早一天) |
|
R004(往返有效) |
有效等价类 |
出发日=2025-11-01 |
|
R005(往返无效) |
无效等价类 |
出发日=2025-11-02 |
注意:
掌握 如何在浏览器中使用 F12 开发者工具定位元素 是编写 Selenium 自动化脚本的基础。结合 页面结构 和 定位优先级(ID > NAME > CLASSNAME > LINKTEXT > CSS > XPATH),详细学习每种定位方式 如何在 DOM 树中找到、如何验证、如何写代码。
生成 完整的 Excel 文件内容(含换行符):完整测试用例(CSV 格式,含换行符标记 \n)
使用方法:
- 复制全部内容
- 粘贴到 Excel 的 Sheet1(从 A2 单元格开始)
- 选中 “输入数据”列(G列)
- 按
Ctrl + H→ 查找\n→ 在替换框中直接按Ctrl + J代表换行符 → 全部替换 - 保存为
测试用例模版.xlsx
所有评论(0)