软件测试大赛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 driverdriver.quit()class Test12306:# test-code-startdef 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:passself.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 = 1for 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 += 1driver.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@staticmethoddef 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
