医院预约挂号平台web管理端用例设计及自动化测试实践

一.项目介绍

该项目是⼀个用于地区医院就诊挂号场景的微信小程序。项目由小程序用户端、Web管理端、后台服务三部分组成:微信小程序用户端有注册登录、绑定就诊卡、预约挂号、信用评价等模块。管理Web端按角色区分,管理员可以维护医院、科室、诊室和医生信息,给医生排班等;医生角色可以查看患者信息、出诊信息等。

二.测试用例设计

image

image

三.登录模块功能测试&自动化测试

测试用例

image

用例模块 用例名 用户 密码 预期结果
登录 正确账号和密码 admin admin 登录成功,跳转到首页
登录 账号为空 admin 提示"请输入账号"
登录 密码为空 admin 提示"请输入密码"
登录 账号密码均为空 点击登录按钮无响应
登录 账号错误密码正确 admintest admin 提示"用户名或密码错误!"
登录 账号正确密码错误 admin adminerror 提示"用户名或密码错误!"

功能测试

1.正确账号和密码

成功跳转到首页
image

2.账号为空

提示"请输入账号"
image

3.密码为空

提示"请输入密码"
image

4.账号密码均为空

点击登录按钮无响应,提示"请输入账号" &"请输入密码"
image

5.账号错误密码正确

提示"用户名或密码错误!"
image

6.账号正确密码错误

提示"用户名或密码错误!"
image

自动化测试

1.整体结构

点击查看代码
pytestdemo/
├── test_login.py        # 登录功能测试用例
├── test_data.yml        # 测试数据文件
├── conftest.py          # pytest 配置与 fixture
├── allure-results/      # allure 测试结果数据
├── *.png                # 测试过程截图
├── __pycache__/         # Python 缓存文件夹
└── .pytest_cache/       # pytest 缓存文件夹

2.自动化测试实现代码 test_login.py

点击查看代码
import pytest
import allure
import yaml
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import time  # 用于等待

# 读取 YAML 测试数据
def load_test_data():
    with open("test_data.yml", encoding="utf-8") as f:
        return yaml.safe_load(f)['login_cases']

# 处理空值
def process_empty(value):
    return "" if value == "空" else value

# 定位器解析函数
def parse_locator(locator_str):
    if locator_str.startswith("//"):
        return By.XPATH, locator_str
    elif locator_str.startswith("."):
        return By.CSS_SELECTOR, locator_str
    else:
        raise ValueError(f"不支持的定位器格式:{locator_str}")

@allure.feature("登录功能测试")
class TestLogin:

    @pytest.mark.parametrize("case", load_test_data())
    def test_login(self, driver, case):
        with allure.step("打开登录页面"):
            driver.get("http://localhost:8081/#/")

        # 输入账号
        username_input = WebDriverWait(driver, 5).until(
            EC.presence_of_element_located((By.XPATH, "//input[@placeholder='账号']"))
        )
        with allure.step("点击账号输入框"):
            username_input.click()

        # 使用封装方法清空账号输入框
        self.clear_input_field(driver, username_input)

        username = process_empty(case["username"])
        with allure.step(f"输入账号:{username}"):
            username_input.send_keys(username)
        time.sleep(0.5)

        # 输入密码
        password_input = driver.find_element(By.XPATH, "//input[@placeholder='密码']")
        with allure.step("点击密码输入框"):
            password_input.click()

        # 使用封装方法清空密码输入框
        self.clear_input_field(driver, password_input)

        password = process_empty(case["password"])
        with allure.step(f"输入密码:{password}"):
            password_input.send_keys(password)
        time.sleep(0.5)

        # 获取登录按钮
        login_button = driver.find_element(By.CSS_SELECTOR, ".el-button.el-button--primary")

        with allure.step("点击登录按钮"):
            expected_result = case["expected_result"]

            if "登录按钮为禁用状态" in expected_result:
                with allure.step("验证登录按钮禁用状态"):
                    try:
                        assert not login_button.is_enabled(), "登录按钮未被禁用"
                    except AssertionError as e:
                        self._take_screenshot_and_attach(driver, case, "错误截图")
                        raise e

            else:
                login_button.click()

                # 点击登录后等待提示信息出现
                if "提示" in expected_result:
                    self._wait_for_error_messages(driver, case)
                    time.sleep(1)  # 添加这一行,等待1秒

                # 点击登录后截图
                self._take_screenshot_and_attach(driver, case, "点击登录后页面状态")

                if "跳转到首页" in expected_result:
                    expected_url = case["expected_url"]
                    with allure.step(f"验证跳转地址:{expected_url}"):
                        try:
                            WebDriverWait(driver, 10).until(
                                EC.url_to_be(expected_url)
                            )
                            actual_url = driver.current_url
                            assert actual_url == expected_url, f"预期地址:{expected_url},实际:{actual_url}"
                        except AssertionError as e:
                            self._take_screenshot_and_attach(driver, case, "错误截图")
                            raise e
                    time.sleep(0.5)

                elif "提示" in expected_result:
                    # 判断是单个提示还是多个提示
                    if "tip_locators" in case:
                        # 多个提示信息
                        tip_locators = case.get("tip_locators", [])
                        tip_texts = case.get("tip_texts", [])
                        for locator_str, expected_text in zip(tip_locators, tip_texts):
                            locator_type, locator_value = parse_locator(locator_str)
                            with allure.step(f"验证提示信息:{expected_text}"):
                                try:
                                    tip_element = WebDriverWait(driver, 10).until(
                                        EC.visibility_of_element_located((locator_type, locator_value))
                                    )
                                    actual_text = tip_element.text
                                    assert expected_text in actual_text, f"预期提示:{expected_text},实际:{actual_text}"
                                except AssertionError as e:
                                    self._take_screenshot_and_attach(driver, case, "错误截图")
                                    raise e
                            time.sleep(1)
                    else:
                        # 单个提示信息
                        tip_locator = case.get("tip_locator")
                        tip_text = case.get("tip_text")
                        locator_type, locator_value = parse_locator(tip_locator)
                        with allure.step(f"验证提示信息:{tip_text}"):
                            try:
                                tip_element = WebDriverWait(driver, 5).until(
                                    EC.visibility_of_element_located((locator_type, locator_value))
                                )
                                actual_text = tip_element.text
                                assert tip_text in actual_text, f"预期提示:{tip_text},实际:{actual_text}"
                            except AssertionError as e:
                                self._take_screenshot_and_attach(driver, case, "错误截图")
                                raise e
                        time.sleep(0.5)

    def clear_input_field(self, driver, element, max_attempts=3):
        """
        使用全选 + BACKSPACE 清空输入框内容
        """
        with allure.step("清空输入框(全选+BACKSPACE)"):
            for attempt in range(max_attempts):
                # 点击输入框并全选内容
                actions = ActionChains(driver)
                actions.click(element)
                actions.key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL)
                actions.perform()

                # 使用 BACKSPACE 删除内容
                for _ in range(10):  # 尝试最多删除10次
                    actions = ActionChains(driver)
                    actions.send_keys(Keys.BACKSPACE)
                    actions.perform()

                # 验证输入框是否为空
                current_value = element.get_attribute("value")
                if current_value == "":
                    break
            else:
                raise TimeoutException("输入框清空失败,尝试次数已达上限")

            # 验证输入框内容是否为空
            current_value = element.get_attribute("value")
            assert current_value == "", f"输入框未正确清空,当前值:'{current_value}'"

    def _wait_for_error_messages(self, driver, case):
        """
        点击登录后,等待提示信息出现
        """
        if "tip_locators" in case:
            tip_locators = case.get("tip_locators", [])
            for locator_str in tip_locators:
                locator_type, locator_value = parse_locator(locator_str)
                try:
                    WebDriverWait(driver, 15).until(
                        EC.visibility_of_element_located((locator_type, locator_value))
                    )
                except TimeoutException:
                    pass  # 可选处理
        else:
            tip_locator = case.get("tip_locator")
            locator_type, locator_value = parse_locator(tip_locator)
            try:
                WebDriverWait(driver, 15).until(
                    EC.visibility_of_element_located((locator_type, locator_value))
                )
            except TimeoutException:
                pass

    def _take_screenshot_and_attach(self, driver, case, name="截图"):
        """
        截图并附加到 Allure 报告中
        """
        file_name = f"{name}_{case['case_name']}.png"
        driver.save_screenshot(file_name)
        time.sleep(0.5)
        allure.attach.file(file_name, name=name, attachment_type=allure.attachment_type.PNG)

3.测试报告

image

小结

1.测试过程中使用clear()send_keys("")清空输入框和传入空字符串后,前端验证逻辑未触发,导致测试用例误判(如账号为空却能登录成功使用)。

解决方案:
使用ActionChains模拟真实用户行为 (全选并删除内容)。

点击查看代码
actions = ActionChains(driver)
        actions.click(password_input)  # 再次点击确保聚焦
        actions.key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL)  # 全选
        actions.send_keys(Keys.BACKSPACE)  # 删除内容
        actions.perform()

2.点击登录按钮后,前端未执行验证逻辑,导致提示信息未出现。

解决方案:
使用WebDriverWait等待验证结果 (如等待提示信息出现)。

WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.XPATH, "//div[text()='请输入账号']")) )

posted @ 2025-06-20 16:46  ReSt_Vikin  阅读(91)  评论(0)    收藏  举报