試框架設(shè)計(jì)與落地實(shí)踐)
簡(jiǎn)介本資源是一份面向軟件測(cè)試工程師、自動(dòng)化測(cè)試初學(xué)者及高校計(jì)算機(jī)相關(guān)專業(yè)學(xué)生的實(shí)踐型技術(shù)文檔聚焦Web自動(dòng)化測(cè)試框架的設(shè)計(jì)與落地。文檔系統(tǒng)闡述了軟件測(cè)試?yán)碚摶A(chǔ)、PythonSelenium框架核心設(shè)計(jì)思想、自動(dòng)化測(cè)試生命周期管理、UI兼容性與穩(wěn)定性測(cè)試實(shí)現(xiàn)等關(guān)鍵內(nèi)容并提供了完整框架搭建思路與簡(jiǎn)易功能驗(yàn)證示例。資源為單文件Word文檔.doc共1個(gè)文件大小1.53MB結(jié)構(gòu)清晰含摘要、關(guān)鍵詞、緒論、傳統(tǒng)測(cè)試對(duì)比、需求分析、框架設(shè)計(jì)與實(shí)現(xiàn)等10余章節(jié)便于快速掌握自動(dòng)化測(cè)試框架的構(gòu)建邏輯與工程化要點(diǎn)。目前已有497人學(xué)習(xí)下載適合希望從零理解并動(dòng)手復(fù)現(xiàn)輕量級(jí)Web自動(dòng)化測(cè)試框架的開發(fā)者參考使用。1. 為什么一個(gè)基于 PythonSelenium 的分層測(cè)試框架比直接寫driver.find_element(By.ID, kw).send_keys(test)更值得花三天重寫你剛接手一個(gè)電商后臺(tái)的回歸測(cè)試任務(wù)每天要跑 47 個(gè)頁面的登錄、搜索、訂單創(chuàng)建、狀態(tài)校驗(yàn)流程。第一次用 Selenium 寫腳本時(shí)你興奮地敲出 200 行“直連式”代碼——所有定位器硬編碼、所有等待寫死time.sleep(3)、所有斷言混在操作里。第三天前端把“搜索框”的idkw改成idq你改了 12 處第四天測(cè)試經(jīng)理說“把 Chrome 瀏覽器換成 Edge 跑一遍”你發(fā)現(xiàn)ChromeOptions()全得重寫第五天產(chǎn)品加了個(gè)新字段“優(yōu)惠券碼”你要在 8 個(gè)用例里補(bǔ)輸入邏輯……這時(shí)你才意識(shí)到不是 Selenium 不好用而是沒把它當(dāng)工程來建。這篇文檔講的不是一個(gè)“能跑通百度搜索”的玩具 Demo而是一個(gè)真實(shí)可交付的 Web 自動(dòng)化測(cè)試框架——它把driver封裝進(jìn)Utility層提供統(tǒng)一等待與異常處理把頁面元素和操作抽象成Page類如LoginPage.enter_username()把業(yè)務(wù)流程組織進(jìn)Test層如test_login_success()再用TestSuite控制執(zhí)行順序。它不解決“能不能點(diǎn)按鈕”而是解決“按鈕改名后改幾處”“換瀏覽器要?jiǎng)佣嗌傥募薄靶略鲆粋€(gè)用例要復(fù)制粘貼幾遍”這些讓測(cè)試工程師深夜改腳本的真實(shí)痛點(diǎn)。適合兩類人一是剛用 Selenium 寫過 3 個(gè)用例、正被維護(hù)成本壓得喘不過氣的初級(jí)測(cè)試開發(fā)二是需要快速搭建可擴(kuò)展 UI 自動(dòng)化能力、但不想從零造輪子的中小團(tuán)隊(duì)技術(shù)負(fù)責(zé)人。它不承諾“全自動(dòng)識(shí)別元素”但保證你改一次定位器全項(xiàng)目生效它不替代人工探索性測(cè)試但讓你把重復(fù)勞動(dòng)壓縮到 5% 以內(nèi)。2. 分層架構(gòu)設(shè)計(jì)為什么 Utility/ Page/ Test/ UI/ TestSuite 五層結(jié)構(gòu)是當(dāng)前 PythonSelenium 項(xiàng)目的事實(shí)標(biāo)準(zhǔn)2.1 分層不是炫技而是為了解耦“變”與“不變”傳統(tǒng)腳本里driver.find_element(By.XPATH, //input[nameusername]).send_keys(admin)這一行代碼同時(shí)承擔(dān)了三件事找元素定位策略、操作元素輸入動(dòng)作、關(guān)聯(lián)業(yè)務(wù)這是登錄用戶名。一旦頁面重構(gòu)三者全崩。分層的本質(zhì)是把這三類變化頻率不同的東西拆開定位策略最易變→ 放進(jìn)Page層的屬性定義里操作動(dòng)作相對(duì)穩(wěn)定→ 封裝進(jìn)Page層的方法里業(yè)務(wù)含義最穩(wěn)定→ 留在Test層的用例描述中這樣當(dāng)username輸入框從input nameusername變成input># utils/web_driver_util.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class WebDriverUtil: def __init__(self, driver, timeout10): self.driver driver self.timeout timeout self.wait WebDriverWait(driver, timeout) def find_element(self, locator, timeoutNone): 智能查找元素先等存在再等可點(diǎn)擊 try: element self.wait.until(EC.element_to_be_clickable(locator)) return element except TimeoutException: # 降級(jí)嘗試僅等待存在 fallback_wait WebDriverWait(self.driver, timeout or self.timeout) return fallback_wait.until(EC.presence_of_element_located(locator)) def safe_click(self, locator): 安全點(diǎn)擊處理遮罩層、滾動(dòng)到視圖、重試機(jī)制 for _ in range(3): # 最多重試3次 try: element self.find_element(locator) self.driver.execute_script(arguments[0].scrollIntoView(true);, element) element.click() return True except Exception as e: if element click intercepted in str(e): continue # 遮罩層攔截繼續(xù)重試 raise e raise Exception(fFailed to click {locator} after 3 retries)提示safe_click中的execute_script(arguments[0].scrollIntoView(true);)是關(guān)鍵。很多初學(xué)者遇到“ElementClickInterceptedException”就卡住本質(zhì)是元素在視口外或被遮罩。Utility 層統(tǒng)一處理Test 層完全無感。2.1.2 Page 層以頁面為單位建模把 HTML 轉(zhuǎn)成 Python 對(duì)象Page 層是“頁面對(duì)象模型POM”的 Python 實(shí)現(xiàn)。每個(gè)頁面對(duì)應(yīng)一個(gè)類類中只放兩樣?xùn)|西元素定位器元數(shù)據(jù)和對(duì)該頁面的操作方法行為。絕不出現(xiàn)driver或find_element這類底層調(diào)用。# pages/login_page.py from utils.web_driver_util import WebDriverUtil class LoginPage: def __init__(self, driver): self.driver driver self.util WebDriverUtil(driver) # 所有定位器集中在此格式統(tǒng)一(By.METHOD, locator_value) self.username_field (By.NAME, username) self.password_field (By.NAME, password) self.login_button (By.XPATH, //button[contains(text(), 登錄)]) self.error_message (By.CLASS_NAME, error-message) def enter_username(self, username): 業(yè)務(wù)語義化方法隱藏定位細(xì)節(jié) self.util.find_element(self.username_field).send_keys(username) def enter_password(self, password): self.util.find_element(self.password_field).send_keys(password) def click_login(self): self.util.safe_click(self.login_button) def get_error_text(self): 封裝讀取操作Test 層只關(guān)心“錯(cuò)誤信息是什么” return self.util.find_element(self.error_message).text注意LoginPage構(gòu)造函數(shù)接收driver但內(nèi)部所有操作都通過self.util完成。這意味著未來如果要替換底層驅(qū)動(dòng)比如從 Selenium 換成 Playwright只需修改WebDriverUtil的實(shí)現(xiàn)Page 層代碼零改動(dòng)。2.1.3 Test 層用自然語言描述業(yè)務(wù)流程與技術(shù)細(xì)節(jié)徹底隔離Test 層是測(cè)試用例的“劇本”。它只調(diào)用 Page 層提供的方法像寫業(yè)務(wù)文檔一樣組織步驟。這里沒有By.ID、沒有WebDriverWait、沒有try/except——只有l(wèi)ogin_page.enter_username(admin)這樣的語句。# tests/test_login.py import pytest from pages.login_page import LoginPage from utils.driver_factory import get_driver # 后文詳解 class TestLogin: def setup_method(self): self.driver get_driver(browserchrome) # 啟動(dòng)瀏覽器 self.login_page LoginPage(self.driver) self.driver.get(https://example.com/login) def teardown_method(self): self.driver.quit() def test_login_success(self): 成功登錄場(chǎng)景業(yè)務(wù)邏輯清晰可見 self.login_page.enter_username(admin) self.login_page.enter_password(123456) self.login_page.click_login() # 斷言跳轉(zhuǎn)到首頁而非檢查URL是否包含/dashboard assert Dashboard in self.driver.title def test_login_failure_with_wrong_password(self): 失敗場(chǎng)景復(fù)用同一Page對(duì)象只改輸入?yún)?shù) self.login_page.enter_username(admin) self.login_page.enter_password(wrong) # 錯(cuò)誤密碼 self.login_page.click_login() assert 密碼錯(cuò)誤 in self.login_page.get_error_text()邏輯說明setup_method和teardown_method是 pytest 的生命周期鉤子。get_driver(browserchrome)返回一個(gè)預(yù)配置好的 WebDriver 實(shí)例支持傳參切換瀏覽器見 3.2 節(jié)。Test 層完全不知道enter_username底層怎么找元素、怎么等待——它只相信 Page 層提供的契約。2.1.4 UI 層與 TestSuite 層支撐多環(huán)境與批量執(zhí)行的骨架UI 層不是指用戶界面而是User Interface Abstraction Layer即對(duì)不同瀏覽器驅(qū)動(dòng)的統(tǒng)一接口。它定義start_browser()、quit_browser()等方法具體實(shí)現(xiàn)由子類完成如ChromeDriverManager、EdgeDriverManager。這樣get_driver(browseredge)才能工作。TestSuite 層不是單個(gè)測(cè)試類而是組織測(cè)試集的入口。它用pytest的--markers或自定義test_suite.py文件按標(biāo)簽pytest.mark.smoke、模塊test_login.py、目錄tests/smoke/批量觸發(fā)執(zhí)行。# test_suite.py import pytest import sys if __name__ __main__: # 支持命令行參數(shù)python test_suite.py --browseredge --envstaging browser sys.argv[sys.argv.index(--browser) 1] if --browser in sys.argv else chrome env sys.argv[sys.argv.index(--env) 1] if --env in sys.argv else prod # pytest.main() 是核心它啟動(dòng)測(cè)試引擎加載所有 tests/ 下的 test_*.py pytest.main([ tests/, f--browser{browser}, f--env{env}, -v, # 詳細(xì)輸出 --htmlreports/test_report.html, # 生成HTML報(bào)告 --self-contained-html ])參數(shù)說明pytest.main()的參數(shù)列表直接映射命令行。--browserchrome會(huì)被get_driver()函數(shù)讀取動(dòng)態(tài)選擇驅(qū)動(dòng)--html參數(shù)啟用pytest-html插件生成可視化報(bào)告。TestSuite 層讓“運(yùn)行全部冒煙測(cè)試”變成一條命令而非手動(dòng)點(diǎn) 15 個(gè)文件。3. 核心實(shí)現(xiàn)從驅(qū)動(dòng)管理到數(shù)據(jù)驅(qū)動(dòng)讓框架真正可落地3.1 驅(qū)動(dòng)管理工廠解決 Selenium 4 的 WebDriverManager 兼容性問題Selenium 4 廢棄了webdriver.Chrome()直接構(gòu)造方式強(qiáng)制使用Service對(duì)象。但很多教程仍教driver webdriver.Chrome(ChromeDriverManager().install())這在 CI/CD 環(huán)境下會(huì)因權(quán)限問題失敗。正確做法是用webdriver-manager庫的ChromeDriverManager().resolve_driver()獲取路徑再傳給Service。# utils/driver_factory.py from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.core.os_manager import ChromeType from selenium.webdriver.edge.service import Service as EdgeService from webdriver_manager.microsoft import EdgeChromiumDriverManager def get_driver(browserchrome, headlessFalse): 驅(qū)動(dòng)工廠函數(shù)統(tǒng)一管理瀏覽器啟動(dòng)參數(shù) :param browser: chrome, edge, firefox :param headless: 是否無頭模式 :return: WebDriver 實(shí)例 if browser chrome: options webdriver.ChromeOptions() if headless: options.add_argument(--headlessnew) # Chrome 109 新無頭模式 options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) # 關(guān)鍵使用 Service 加載驅(qū)動(dòng)而非舊式 install() service Service(ChromeDriverManager().install()) return webdriver.Chrome(serviceservice, optionsoptions) elif browser edge: options webdriver.EdgeOptions() if headless: options.add_argument(--headlessnew) service EdgeService(EdgeChromiumDriverManager().install()) return webdriver.Edge(serviceservice, optionsoptions) else: raise ValueError(fUnsupported browser: {browser})為什么必須用ServiceChromeDriverManager().install()返回的是驅(qū)動(dòng)二進(jìn)制文件的絕對(duì)路徑。Service(path)顯式指定路徑避免 Selenium 自動(dòng)搜索導(dǎo)致版本錯(cuò)亂。--headlessnew是 Chrome 109 后推薦的無頭參數(shù)舊版--headless已廢棄。3.2 數(shù)據(jù)驅(qū)動(dòng)用 YAML 替代 Excel讓測(cè)試數(shù)據(jù)與腳本徹底分離很多團(tuán)隊(duì)用 Excel 存測(cè)試數(shù)據(jù)但 Excel 在 Git 中無法 diff協(xié)作時(shí)容易沖突。YAML 是純文本、可版本控制、結(jié)構(gòu)清晰的替代方案。# data/login_test_data.yaml valid_users: - username: admin password: 123456 expected_result: success invalid_users: - username: admin password: wrong expected_result: failure - username: password: 123456 expected_result: failure在 Test 層用pytest的pytest.mark.parametrize注入數(shù)據(jù)# tests/test_login.py import yaml from pathlib import Path # 讀取 YAML 數(shù)據(jù) def load_test_data(): with open(Path(__file__).parent / ../data/login_test_data.yaml) as f: return yaml.safe_load(f) pytest.mark.parametrize(case, load_test_data()[valid_users]) def test_login_with_valid_data(case): login_page LoginPage(get_driver()) login_page.enter_username(case[username]) login_page.enter_password(case[password]) login_page.click_login() assert case[expected_result] success # 實(shí)際應(yīng)檢查頁面跳轉(zhuǎn) pytest.mark.parametrize(case, load_test_data()[invalid_users]) def test_login_with_invalid_data(case): login_page LoginPage(get_driver()) login_page.enter_username(case[username]) login_page.enter_password(case[password]) login_page.click_login() assert error in login_page.get_error_text().lower()優(yōu)勢(shì)對(duì)比Excel 需要openpyxl庫解析代碼臃腫YAML 用內(nèi)置yaml庫5 行搞定。Git 提交時(shí)能看到 password: new_pwd這樣的清晰變更而非二進(jìn)制 diff。3.3 測(cè)試報(bào)告與結(jié)果輸出不只是截圖而是可追溯的執(zhí)行證據(jù)自動(dòng)化測(cè)試的價(jià)值不在“跑完”而在“證明它為什么通過或失敗”。框架需在失敗時(shí)自動(dòng)截圖、記錄日志、保存頁面源碼。# utils/report_util.py import os import time from datetime import datetime def take_screenshot_on_failure(test_name, driver): 失敗時(shí)截圖按時(shí)間戳命名避免覆蓋 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_dir reports/screenshots os.makedirs(screenshot_dir, exist_okTrue) filename f{screenshot_dir}/{test_name}_{timestamp}.png driver.save_screenshot(filename) return filename def save_page_source_on_failure(test_name, driver): 保存 HTML 源碼定位渲染問題的關(guān)鍵證據(jù) timestamp datetime.now().strftime(%Y%m%d_%H%M%S) source_dir reports/html_sources os.makedirs(source_dir, exist_okTrue) filename f{source_dir}/{test_name}_{timestamp}.html with open(filename, w, encodingutf-8) as f: f.write(driver.page_source) return filename在Test層的teardown_method中集成def teardown_method(self): if hasattr(self, _outcome) and self._outcome is not None: # pytest 7 獲取測(cè)試結(jié)果的方式 result self._outcome.result if result.errors or result.failures: # 測(cè)試失敗執(zhí)行取證 screenshot_path take_screenshot_on_failure(self._testMethodName, self.driver) source_path save_page_source_on_failure(self._testMethodName, self.driver) print(f? Failed: {self._testMethodName} | Screenshot: {screenshot_path} | Source: {source_path}) self.driver.quit()排錯(cuò)價(jià)值當(dāng)test_login_failure失敗時(shí)你不僅看到報(bào)錯(cuò)堆棧還能立刻打開reports/screenshots/test_login_failure_20240520_143022.png查看當(dāng)時(shí)頁面狀態(tài)或用瀏覽器打開reports/html_sources/test_login_failure_20240520_143022.html檢查 DOM 結(jié)構(gòu)是否被 JS 動(dòng)態(tài)修改——這是手工測(cè)試都無法做到的精準(zhǔn)回溯。4. 兼容性與穩(wěn)定性實(shí)戰(zhàn)如何讓一套腳本在 Chrome/Firefox/Edge 上 100% 通過4.1 定位策略優(yōu)先級(jí)為什么 XPath 不是萬能的CSS Selector 才是首選新手常濫用By.XPATH因?yàn)樗笆裁炊寄苷摇?。?XPath 在不同瀏覽器中解析行為不一致尤其含索引的//div[2]且性能低于 CSS Selector??蚣軕?yīng)強(qiáng)制約定定位優(yōu)先級(jí)優(yōu)先級(jí)推薦方式示例原因說明1id屬性(By.ID, submit-btn)唯一、高效、W3C 標(biāo)準(zhǔn)2># pages/login_page.py class LoginPage: def __init__(self, driver): self.driver driver self.util WebDriverUtil(driver) # ? 推薦用># utils/web_driver_util.py def wait_for_order_submit(self): 訂單提交按鈕的黃金等待鏈 # Step1: 等待按鈕元素存在DOM 加載完成 submit_btn self.wait.until(EC.presence_of_element_located((By.ID, order-submit))) # Step2: 等待按鈕可見CSS 渲染完成 self.wait.until(EC.visibility_of(submit_btn)) # Step3: 等待按鈕可點(diǎn)擊AJAX 請(qǐng)求結(jié)束無 loading 遮罩 self.wait.until(EC.element_to_be_clickable((By.ID, order-submit))) return submit_btn為什么不用一步到位EC.element_to_be_clickable要求元素同時(shí)滿足存在、可見、啟用。但某些 SPA 應(yīng)用按鈕 DOM 已存在CSS 渲染未完成visibility為hidden此時(shí)直接調(diào)用會(huì)超時(shí)。分步等待更魯棒。4.3 兼容性測(cè)試矩陣用 pytest-xdist 并行跑 3 瀏覽器 × 2 環(huán)境單機(jī)跑完 Chrome/Firefox/Edge 三套用例太慢用pytest-xdist插件并行執(zhí)行# 安裝插件 pip install pytest-xdist # 命令行并行啟動(dòng)-n 3 表示開3個(gè)進(jìn)程每個(gè)進(jìn)程跑一個(gè)瀏覽器 pytest tests/ --browserchrome --envstaging -n 3 -v pytest tests/ --browserfirefox --envstaging -n 3 -v pytest tests/ --browseredge --envstaging -n 3 -v但更優(yōu)雅的是用pytest的--tbshort和--maxfail3控制失敗行為并生成合并報(bào)告# 一行命令跑全量兼容性矩陣 pytest tests/ \ --browserchrome --browserfirefox --browseredge \ --envstaging --envprod \ -n 6 \ --htmlreports/compatibility_report.html \ --self-contained-html \ --maxfail5關(guān)鍵參數(shù)說明-n 6表示開 6 個(gè) worker 進(jìn)程pytest-xdist會(huì)自動(dòng)將tests/下的用例分發(fā)到各進(jìn)程。--browserchrome --browserfirefox是 pytest 的--browser自定義參數(shù)需在conftest.py中注冊(cè)# conftest.py def pytest_addoption(parser): parser.addoption( --browser, actionappend, # 支持多次 --browser 參數(shù) default[], helpBrowser to run tests on ) def pytest_generate_tests(metafunc): if browser in metafunc.fixturenames: browsers metafunc.config.getoption(browser) if not browsers: browsers [chrome] # 默認(rèn)值 metafunc.parametrize(browser, browsers)5. 進(jìn)階技巧用 pytest 的 fixture 機(jī)制實(shí)現(xiàn)跨測(cè)試的數(shù)據(jù)共享與環(huán)境隔離5.1 Session 級(jí) fixture登錄一次復(fù)用整個(gè)測(cè)試會(huì)話登錄是耗時(shí)操作每個(gè)測(cè)試都enter_username太慢。用scopesessionfixture在整個(gè)測(cè)試會(huì)話開始時(shí)登錄一次后續(xù)測(cè)試復(fù)用該driver實(shí)例# conftest.py import pytest from pages.login_page import LoginPage pytest.fixture(scopesession) def logged_in_driver(): Session 級(jí) fixture登錄一次供所有測(cè)試復(fù)用 driver get_driver(browserchrome) login_page LoginPage(driver) driver.get(https://example.com/login) login_page.enter_username(admin) login_page.enter_password(123456) login_page.click_login() # 確保登錄成功 assert Dashboard in driver.title yield driver # 提供給測(cè)試函數(shù) driver.quit() # 會(huì)話結(jié)束時(shí)退出 # 在 Test 層直接使用 class TestDashboard: def test_view_orders(self, logged_in_driver): # logged_in_driver 已登錄直接訪問訂單頁 logged_in_driver.get(https://example.com/orders) assert Order List in logged_in_driver.title注意作用域scopesession表示整個(gè)pytest運(yùn)行周期只執(zhí)行一次yield前的代碼。yield driver后的driver.quit()在所有測(cè)試結(jié)束后執(zhí)行。這比每個(gè)setup_method都登錄快 5 倍以上。5.2 Module 級(jí) fixture為一組相關(guān)測(cè)試準(zhǔn)備獨(dú)立環(huán)境session級(jí)別太粗function級(jí)別太細(xì)。module級(jí)別最適合“一個(gè)模塊下的多個(gè)用例共享前置條件”# tests/test_payment.py import pytest pytest.fixture(scopemodule) def payment_setup(): 為支付模塊準(zhǔn)備創(chuàng)建測(cè)試訂單、獲取支付鏈接 driver get_driver() # 步驟1創(chuàng)建測(cè)試訂單 order_id create_test_order(driver) # 自定義函數(shù) # 步驟2獲取支付頁 URL payment_url fhttps://example.com/pay/{order_id} yield {driver: driver, order_id: order_id, payment_url: payment_url} # 清理取消訂單 cancel_order(driver, order_id) driver.quit() class TestPaymentFlow: def test_alipay_payment(self, payment_setup): driver payment_setup[driver] driver.get(payment_setup[payment_url]) # 執(zhí)行支付寶支付流程... def test_wechat_payment(self, payment_setup): driver payment_setup[driver] driver.get(payment_setup[payment_url]) # 執(zhí)行微信支付流程...優(yōu)勢(shì)test_alipay_payment和test_wechat_payment共享同一個(gè)driver和order_id避免重復(fù)創(chuàng)建訂單。yield后的清理代碼確保環(huán)境干凈不影響其他模塊。5.3 自定義標(biāo)記Marker用pytest.mark快速篩選執(zhí)行范圍為不同目的打標(biāo)比寫多個(gè)test_*文件更靈活# tests/test_login.py import pytest pytest.mark.smoke def test_login_success(): pass pytest.mark.regression pytest.mark.slow def test_login_with_ldap(): pass pytest.mark.ui def test_login_ui_layout(): pass執(zhí)行時(shí)按需篩選# 只跑冒煙測(cè)試CI 第一關(guān) pytest -m smoke # 跑回歸測(cè)試但跳過慢用例 pytest -m regression and not slow # 跑所有 UI 相關(guān)測(cè)試 pytest -m ui工程實(shí)踐在pytest.ini中配置 marker 說明讓團(tuán)隊(duì)新人一眼看懂# pytest.ini [tool:pytest] markers smoke: 驗(yàn)證核心功能是否可用5分鐘內(nèi)跑完 regression: 覆蓋主干業(yè)務(wù)流程的完整回歸 slow: 執(zhí)行時(shí)間 30秒通常含文件上傳/下載 ui: 檢查頁面布局、顏色、字體等視覺一致性框架的最終價(jià)值不是寫出多少行代碼而是讓test_login_success這個(gè)用例在 Chrome/Firefox/Edge 上穩(wěn)定運(yùn)行 100 次不失敗讓test_login_with_ldap的失敗日志里直接顯示“LDAP 服務(wù)器連接超時(shí)10.0.1.5:389”而不是“ElementNotInteractableException”。當(dāng)你把driver.find_element從 200 行腳本里抽離成LoginPage.enter_username()你就已經(jīng)完成了從腳本編寫者到框架設(shè)計(jì)者的跨越。本文還有配套的精品資源點(diǎn)擊獲取