最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Playwright+Pytest構(gòu)建Web?UI自動化測試框架完整示例

 更新時間:2026年05月03日 11:54:50   作者:imzed  
Playwright與PyTest強(qiáng)強(qiáng)聯(lián)合,打造高效Web自動化測試方案,前者提供現(xiàn)代化跨瀏覽器自動化能力,后者帶來結(jié)構(gòu)化測試管理與強(qiáng)大擴(kuò)展性,這篇文章主要介紹了使用Playwright+Pytest構(gòu)建Web?UI自動化測試框架的相關(guān)資料,需要的朋友可以參考下

一、為什么選擇 Playwright?

1.1 Playwright vs Selenium 對比

先來看一個直觀的對比:

特性PlaywrightSelenium
瀏覽器支持? 原生支持 Chromium、Firefox、WebKit?? 需要額外驅(qū)動程序
等待機(jī)制? 內(nèi)置自動同步,無需顯式等待? 需要手動 WebDriverWait
網(wǎng)絡(luò)攔截? 原生 Mock API 能力? 第三方擴(kuò)展支持差
鏈路追蹤? 內(nèi)置 Trace 功能,完整記錄操作路徑? 不支持
跨域處理? 無跨域限制?? 有跨域問題
執(zhí)行效率? 更快(優(yōu)化程度更高)?? 相對較慢
API 設(shè)計? 現(xiàn)代化、簡潔直觀?? 有歷史包袱

核心優(yōu)勢示例:

# ? Playwright:自動等待,開箱即用
page.click('button')  # 自動等待元素可點擊,無需手動等待
page.fill('#username', 'admin')  # 自動等待輸入框可用

# ? Selenium:需要手動配置等待
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.TAG_NAME, "button")))
element.click()

二、架構(gòu)設(shè)計與分層

2.1 項目目錄結(jié)構(gòu)

playwright_ui/
├── cases/                      # ?? 測試用例層
│   ├── conftest.py            # Fixture 定義
│   ├── test_login.py          # 登錄功能測試
│   ├── test_add_project.py    # 創(chuàng)建項目測試
│   ├── test_project_list.py   # 項目列表測試
│   └── more_accounts/         # 多賬號場景
│
├── pages/                      # ?? Page Object Model 層
│   ├── login_page.py          # 登錄頁面對象
│   ├── register_page.py       # 注冊頁面對象
│   ├── add_project_page.py    # 新增項目頁面對象
│   ├── project_list_page.py   # 項目列表頁面對象
│   └── list_env_page.py       # 環(huán)境列表頁面對象
│
├── plugins/                    # ?? 自定義插件
│   ├── pytest_playwright.py   # Playwright 集成插件
│   └── pytest_base_url_plugin.py  # URL 管理插件
│
├── mocks/                      # ?? Mock 數(shù)據(jù)
│   └── mock_api.py            # API Mock 配置
│
├── conftest.py                # 全局 Fixture
├── pytest.ini                 # Pytest 配置
├── run.py                     # 啟動入口
└── requirements.txt           # 依賴列表

2.2 分層架構(gòu)設(shè)計

┌──────────────────────────────────┐
│   Test Case Layer                │
│   - 業(yè)務(wù)場景測試                  │
│   - 數(shù)據(jù)驅(qū)動                      │
└─────────────┬──────────────────┘
              │
┌─────────────▼──────────────────┐
│   Page Object Model Layer      │
│   - 頁面元素封裝                │
│   - 業(yè)務(wù)操作方法                │
└─────────────┬──────────────────┘
              │
┌─────────────▼──────────────────┐
│   Fixture & Plugin Layer       │
│   - Browser/Page fixture       │
│   - Screenshot/Video/Trace     │
│   - 自動化報告                  │
└─────────────┬──────────────────┘
              │
┌─────────────▼──────────────────┐
│   Playwright API               │
│   - 瀏覽器自動化能力             │
└──────────────────────────────────┘

三、核心特性詳解

3.1 Page Object Model(POM)模式

什么是 POM? 將頁面元素定位和業(yè)務(wù)操作封裝為對象,提高代碼可維護(hù)性。

示例:LoginPage

# pages/login_page.py
from playwright.sync_api import Page

class LoginPage:
    """登錄頁面對象"""
    
    def __init__(self, page: Page):
        self.page = page
        # 元素定位 - 使用 Playwright 的強(qiáng)大選擇器
        self.locator_username = page.get_by_label("用 戶 名:")
        self.locator_password = page.get_by_label("密     碼:")
        self.locator_login_btn = page.locator('text=立即登錄')
        self.locator_login_error = page.locator('text=賬號或密碼不正確!')
    
    # 頁面操作 - 原子操作
    def navigate(self):
        """導(dǎo)航到登錄頁"""
        self.page.goto("/login.html")
    
    def fill_username(self, username):
        """輸入用戶名"""
        self.locator_username.fill(username)
    
    def fill_password(self, password):
        """輸入密碼"""
        self.locator_password.fill(password)
    
    def click_login_button(self):
        """點擊登錄按鈕"""
        self.locator_login_btn.click()
    
    # 業(yè)務(wù)操作 - 組合操作
    def login(self, username, password) -> None:
        """完整登錄流程"""
        self.fill_username(username)
        self.fill_password(password)
        self.click_login_button()

測試代碼變得簡潔優(yōu)雅:

# cases/test_login.py
class TestLogin:
    """登錄功能測試"""
    
    @pytest.fixture(autouse=True)
    def start_for_each(self, unlogin_page):
        self.login = LoginPage(unlogin_page)
        self.login.navigate()
        yield
    
    def test_login_success(self):
        """正常登錄流程"""
        # 業(yè)務(wù)代碼一目了然
        self.login.login("py", "123456")
        expect(self.login.page).to_have_url("/index.html")
    
    def test_login_with_empty_username(self):
        """用戶名為空驗證"""
        self.login.fill_username('')
        self.login.fill_password('123456')
        self.login.click_login_button()
        
        # 使用 Playwright 的斷言
        expect(self.login.locator_username_tip1).to_be_visible()
        expect(self.login.locator_username_tip1).to_contain_text("不能為空")

POM 的優(yōu)勢:

? 提高可維護(hù)性:定位器改變只需改一處
? 提高復(fù)用性:多個測試文件可共享同一 Page 對象
? 提高可讀性:測試用例自文檔化
? 便于擴(kuò)展:添加新功能無需修改測試代碼

3.2 強(qiáng)大的 Fixture 機(jī)制

Pytest 的 Fixture 是依賴注入的優(yōu)雅實現(xiàn),框架采用三層 Fixture 架構(gòu):

# ┌─────────────────────────────────────────┐
# │ Session 級別(整個測試會話)              │
# │ - browser_type_launch_args              │
# │ - browser_context_args                  │
# │ - login_first (全局登錄)                │
# └─────────────────────────────────────────┘
#
# ┌─────────────────────────────────────────┐
# │ Module 級別(每個測試模塊)              │
# │ - unlogin_context (無登錄上下文)         │
# │ - admin_context (管理員上下文)          │
# └─────────────────────────────────────────┘
#
# ┌─────────────────────────────────────────┐
# │ Function 級別(每個測試函數(shù))            │
# │ - page (頁面對象)                       │
# │ - unlogin_page (無登錄頁面)             │
# └─────────────────────────────────────────┘

全局登錄 Fixture(Session 級別):

# cases/conftest.py
@pytest.fixture(scope="session", autouse=True)
def login_first(context, base_url, pytestconfig) -> None:
    """
    有些網(wǎng)站網(wǎng)頁關(guān)閉cookie就失效了,全局登錄一次
    這樣所有測試都能共享登錄狀態(tài),避免重復(fù)登錄浪費時間
    """
    print("base_url----", base_url)
    page = context.new_page()
    LoginPage(page).navigate()
    LoginPage(page).login("py", "123456")
    # 等待登錄成功頁面重定向
    page.wait_for_url(url='**/index.html')

多賬號隔離 Fixture(Module 級別):

# cases/more_accounts/conftest.py
@pytest.fixture(scope="module")
def admin_context(browser, base_url, pytestconfig, browser_context_args):
    """
    創(chuàng)建 admin 賬戶的獨立上下文
    避免與默認(rèn)登錄沖突,支持多賬號測試
    """
    context = browser.new_context(**browser_context_args)
    page = context.new_page()
    LoginPage(page).navigate()
    LoginPage(page).login("admin", "123456")
    page.wait_for_url(url='**/index.html')
    yield context
    context.close()

使用示例:

class TestAddProject:
    """添加項目 - 使用全局登錄"""
    
    @pytest.fixture(autouse=True)
    def start_for_each(self, login_first, page):
        self.add_project = AddProjectPage(page)
        self.add_project.navigate()
        yield

class TestLogin:
    """登錄測試 - 使用無登錄上下文"""
    
    @pytest.fixture(autouse=True)
    def start_for_each(self, unlogin_page):
        self.login = LoginPage(unlogin_page)
        self.login.navigate()
        yield

3.3 網(wǎng)絡(luò)攔截與 API Mock

Playwright 的網(wǎng)絡(luò)攔截能力是其一大優(yōu)勢,可以輕松 Mock API 返回值,測試異常場景。

API Mock 配置:

# mocks/mock_api.py
mock_project_400 = (
    '**/api/addProject',
    lambda route: route.abort_with_error_code(400)
)

mock_project_500 = (
    '**/api/addProject',
    lambda route: route.abort_with_error_code(500)
)

mock_project_200 = (
    '**/api/addProject',
    lambda route: route.continue_()
)

測試不同 HTTP 狀態(tài)碼:

# cases/test_add_project.py
def test_add_project_400(self, page):
    """項目名稱重復(fù),返回 400 錯誤"""
    self.add_project.fill_project_name("yo yo")
    self.add_project.fill_publish_app("xx")
    
    # Mock API 返回 400
    page.route(**mock_api.mock_project_400)
    self.add_project.click_save_button()
    
    # 驗證 UI 正確處理錯誤
    expect(self.add_project.locator_boot_box).to_be_visible()
    expect(self.add_project.locator_boot_box).to_contain_text('已存在')

def test_add_project_500(self, page):
    """服務(wù)器異常,返回 500 錯誤"""
    self.add_project.fill_project_name("test")
    
    # Mock API 返回 500
    page.route(**mock_api.mock_project_500)
    self.add_project.click_save_button()
    
    # 驗證 UI 提示
    expect(self.add_project.locator_boot_box).to_contain_text('操作異常')

捕獲網(wǎng)絡(luò)請求和響應(yīng):

def test_login_ajax(self):
    """捕獲并驗證登錄請求"""
    self.login.fill_username("py")
    self.login.fill_password("123456")
    
    # 捕獲 API 請求
    with self.login.page.expect_request('**/api/login') as req:
        self.login.click_login_button()
    
    # 驗證請求內(nèi)容
    assert req.value.method == 'POST'
    assert req.value.header_value('content-type') == 'application/json'
    assert req.value.post_data_json == {
        'username': 'py',
        'password': '123456'
    }

def test_login_ajax_response(self):
    """捕獲并驗證登錄響應(yīng)"""
    self.login.fill_username("py")
    self.login.fill_password("123456")
    
    # 捕獲 API 響應(yīng)
    with self.login.page.expect_response('**/api/login') as res:
        self.login.click_login_button()
    
    # 驗證響應(yīng)
    assert res.value.ok
    assert res.value.status == 200

3.4 項目列表搜索功能測試

這個例子展示了如何進(jìn)行實際的業(yè)務(wù)流程測試:

# cases/test_project_list.py
class TestProjectList:
    """項目列表功能測試"""
    
    @pytest.fixture(autouse=True)
    def start_for_each(self, login_first, page):
        """每個測試前打開項目列表頁"""
        self.project = ProjectListPage(page)
        self.project.navigate()
        yield
    
    def test_search_project_ajax(self):
        """驗證搜索請求的參數(shù)和方法"""
        self.project.search_project("test")
        
        # 點擊搜索時捕獲請求
        with self.project.page.expect_request('**/api/project**') as req:
            self.project.click_search_button()
        
        # 驗證請求參數(shù)
        assert "project_name=test" in req.value.url
        assert req.value.method == "GET"
    
    def test_search_project_empty_result(self):
        """測試搜索無結(jié)果的情況"""
        self.project.search_project("test")
        
        # Mock 搜索返回空結(jié)果
        self.project.page.route(**mock_api.mock_project_search_0)
        self.project.click_search_button()
        
        # 驗證提示信息
        expect(self.project.locator_table_tr).to_contain_text('沒有找到匹配的記錄')
    
    def test_search_project_single_result(self):
        """測試搜索返回單條結(jié)果"""
        self.project.search_project("test")
        
        # Mock 搜索返回 1 條結(jié)果
        self.project.page.route(**mock_api.mock_project_search_1)
        self.project.click_search_button()
        
        # 驗證結(jié)果數(shù)量
        expect(self.project.locator_table_tr).to_have_count(1)
    
    def test_table_delete(self):
        """表格行內(nèi)刪除操作"""
        # Mock 搜索結(jié)果
        self.project.page.route(**mock_api.mock_project_search_1)
        self.project.page.reload()
        
        # 點擊刪除
        self.project.locator_table_delete.click()
        expect(self.project.locator_boot_box).to_contain_text('確定要刪除選中的數(shù)據(jù)?')
        
        # Mock 刪除返回 403(無權(quán)限)
        self.project.page.route(**mock_api.mock_project_delete_403)
        self.project.locator_boot_box_accept.click()
        
        # 驗證錯誤提示
        expect(self.project.locator_boot_box.last).to_contain_text('無權(quán)限操作')

3.5 參數(shù)化測試

使用 @pytest.mark.parametrize 進(jìn)行數(shù)據(jù)驅(qū)動測試,避免代碼重復(fù):

# cases/test_env_list.py
class TestEnvList:
    """環(huán)境列表參數(shù)化測試示例"""
    
    @pytest.mark.parametrize("name", ["abc!@", "$32", "\\xx"])
    def test_add_env_name_invalid(self, name):
        """
        參數(shù)化測試:不能有特殊字符
        生成 3 個測試用例
        """
        self.env.click_add_env()
        self.env.input_env_name(name)
        self.env.click_modal_save()
        
        # 驗證錯誤提示
        expect(self.env.locator_modal_env_tip3).to_be_visible()
        expect(self.env.locator_modal_env_tip3).to_have_text('不能有特殊字符')
    
    @pytest.mark.parametrize("address", ["abchttp", "httpx:", "httpsx://"])
    def test_add_env_address_invalid(self, address):
        """
        參數(shù)化測試:URL 格式驗證
        生成 3 個測試用例
        """
        self.env.click_add_env()
        self.env.input_env_name('env')
        self.env.input_env_address(address)
        self.env.click_modal_save()
        
        # 驗證錯誤提示
        expect(self.env.locator_boot_box).to_be_visible()
        expect(self.env.locator_boot_box).to_have_text('base_url must start with http:// or https://')

執(zhí)行結(jié)果:

$ pytest cases/test_env_list.py::TestEnvList::test_add_env_name_invalid -v

test_add_env_name_invalid[abc!@] PASSED
test_add_env_name_invalid[$32] PASSED
test_add_env_name_invalid[\xx] PASSED

3.6 自動化報告生成

框架集成了完整的自動化報告系統(tǒng),??括截圖、視頻、鏈路追蹤:

# plugins/pytest_playwright.py 中實現(xiàn)
@pytest.fixture
def page(context, pytestconfig, request):
    page = context.new_page()
    yield page
    
    # 判斷測試是否失敗
    failed = request.node.rep_call.failed if hasattr(request.node, "rep_call") else True
    
    # 根據(jù)配置自動截圖
    screenshot_option = pytestconfig.getoption("--screenshot")
    capture_screenshot = screenshot_option == "on" or (failed and screenshot_option == "only-on-failure")
    
    if capture_screenshot:
        for index, page in enumerate(pages):
            screenshot_path = _build_artifact_test_folder(pytestconfig, request, f"test-{human_readable_status}-{index + 1}.png")
            page.screenshot(timeout=5000, path=screenshot_path)
            # 自動附加到 Allure 報告
            allure.attach.file(screenshot_path, attachment_type=allure.attachment_type.PNG)
    
    # 自動錄制視頻
    video_option = pytestconfig.getoption("--video")
    preserve_video = video_option == "on" or (failed and video_option == "retain-on-failure")
    
    if preserve_video:
        for page in pages:
            video = page.video
            if video:
                video_path = video.path()
                video.save_as(path=file_path)
                allure.attach.file(file_path, attachment_type=allure.attachment_type.WEBM)

啟動報告:

# 運行測試
python run.py

# 或使用命令行
pytest --alluredir ./reports
allure serve ./reports

報告產(chǎn)物:

test-results/
├── trace.zip           # 完整鏈路追蹤(可在線查看)
├── test-failed-1.png   # 失敗時截圖
└── test-failed.webm    # 失敗時視頻

四、最佳實踐

4.1 等待策略

Playwright 的智能等待是其一大優(yōu)勢,但也需要正確使用:

# ? 優(yōu)先使用隱式等待(推薦)
page.click()  # 自動等待元素可點擊
page.fill()   # 自動等待元素可輸入

# ? 導(dǎo)航等待
with page.expect_navigation(url='**/list_project.html'):
    self.add_project.click_save_button()

# ? 網(wǎng)絡(luò)等待
page.wait_for_load_state('networkidle')  # 所有網(wǎng)絡(luò)請求完成

# ? 避免硬編碼等待
time.sleep(2)  # 不要這樣做!

4.2 測試組織最佳實踐

class TestLogin:
    """登錄功能測試"""
    
    @pytest.fixture(autouse=True)
    def setup(self, unlogin_page):
        """前置條件:準(zhǔn)備測試環(huán)境"""
        self.login = LoginPage(unlogin_page)
        self.login.navigate()
        yield
        # 后置處理(可選)
    
    # 正常流程
    def test_login_success(self):
        """用戶使用正確的用戶名和密碼能成功登錄"""
        self.login.login("py", "123456")
        expect(self.login.page).to_have_url("/index.html")
    
    # 異常場景1:輸入驗證
    def test_login_with_empty_username(self):
        """用戶名為空時應(yīng)該顯示錯誤提示"""
        self.login.fill_username('')
        self.login.fill_password('123456')
        self.login.click_login_button()
        expect(self.login.locator_username_tip1).to_contain_text("不能為空")
    
    # 異常場景2:錯誤憑證
    def test_login_with_wrong_password(self):
        """密碼錯誤時應(yīng)該顯示錯誤提示"""
        self.login.login("py", "wrongpassword")
        expect(self.login.locator_login_error).to_be_visible()
    
    # 邊界值測試
    def test_login_with_long_username(self):
        """用戶名過長時應(yīng)該受限"""
        self.login.fill_username('hello world hello world hello world')
        expect(self.login.locator_login_btn).not_to_be_enabled()

4.3 調(diào)試技巧

# 1. 使用 --headed 模式查看瀏覽器窗口
pytest --headed cases/test_login.py

# 2. 使用 --slowmo 延遲每個操作
pytest --slowmo=1000 cases/test_login.py

# 3. 啟用詳細(xì) Trace
pytest --tracing=on cases/test_login.py

# 4. 查看 Trace
playwright show-trace test-results/trace.zip

# 5. 在代碼中暫停
def test_with_pause(page):
    page.pause()  # 瀏覽器會暫停,允許手動操作

五、性能優(yōu)化建議

5.1 減少重復(fù)登錄

# ? 不好的做法:每個測試都登錄
@pytest.fixture(autouse=True)
def setup(self, page):
    LoginPage(page).login("py", "123456")  # 浪費時間

# ? 好的做法:全局登錄一次
@pytest.fixture(scope="session", autouse=True)
def login_first(context):
    page = context.new_page()
    LoginPage(page).login("py", "123456")
    # 所有測試共享 Cookie

5.2 使用 Mock API

# ? 真實 API 調(diào)用(慢)
self.add_project.click_save()  # 等待數(shù)據(jù)庫操作

# ? Mock API(快)
page.route('**/api/addProject', lambda route: route.continue_())

5.3 并行執(zhí)行

# 安裝 pytest-xdist
pip install pytest-xdist

# 4 個進(jìn)程并行執(zhí)行
pytest -n 4

# 自動檢測 CPU 核數(shù)
pytest -n auto

六、完整示例:添加項目功能測試

最后,讓我展示一個完整的功能測試示例:

# cases/test_add_project.py
from pages.add_project_page import AddProjectPage
from playwright.sync_api import expect, Page
import pytest
import uuid
from mocks import mock_api

class TestAddProject:
    """添加項目功能測試"""
    
    @pytest.fixture(autouse=True)
    def start_for_each(self, login_first, page: Page):
        """前置條件:打開添加項目頁"""
        print("for each--start: 打開添加項目頁")
        self.add_project = AddProjectPage(page)
        self.add_project.navigate()
        yield
        print("for each--end: 后置操作")
    
    # 異常場景:輸入驗證
    @pytest.mark.parametrize("name, app, desc", [
        ["abc!@", "", ""],              # 特殊字符
        ["aaaaabbbbbcccccdddddeeeeefffff1", "", ""],  # 超過 30 字符
        ["abc", "aa!@", ""]             # app 特殊字符
    ])
    def test_add_project_disabled(self, name, app, desc):
        """異常場景-項目名稱無效:特殊字符/大于30個字符"""
        self.add_project.fill_project_name(name)
        self.add_project.fill_publish_app(app)
        self.add_project.fill_project_desc(desc)
        # 斷言提交按鈕不可點擊
        expect(self.add_project.locator_save_button).to_be_disabled()
    
    # 異常場景:空值驗證
    def test_add_project_null(self):
        """異常場景-項目名稱不能為空"""
        self.add_project.fill_project_name("")
        # 斷言提交按鈕不可點擊
        expect(self.add_project.locator_save_button).to_be_disabled()
    
    # 異常場景:服務(wù)器返回 400(項目已存在)
    def test_add_project_400(self, page: Page):
        """項目名稱重復(fù),服務(wù)器返回 400 錯誤"""
        self.add_project.fill_project_name("yo yo")
        self.add_project.fill_publish_app("xx")
        self.add_project.fill_project_desc("xxx")
        
        # Mock API 返回 400
        page.route(**mock_api.mock_project_400)
        self.add_project.click_save_button()
        
        # 驗證錯誤提示
        expect(self.add_project.locator_boot_box).to_be_visible()
        expect(self.add_project.locator_boot_box).to_contain_text('已存在')
    
    # 異常場景:服務(wù)器返回 500(服務(wù)器異常)
    def test_add_project_500(self, page: Page):
        """服務(wù)器異常,返回 500 狀態(tài)碼"""
        self.add_project.fill_project_name("test")
        self.add_project.fill_publish_app("xx")
        self.add_project.fill_project_desc("xxx")
        
        # Mock API 返回 500
        page.route(**mock_api.mock_project_500)
        self.add_project.click_save_button()
        
        # 驗證錯誤提示
        expect(self.add_project.locator_boot_box).to_contain_text('操作異常')
    
    # 正常流程:成功添加項目
    def test_add_project_success(self, page: Page):
        """提交成功,跳轉(zhuǎn)到項目列表"""
        # 生成隨機(jī)項目名
        project_name = str(uuid.uuid4()).replace('-', '')[:25]
        
        self.add_project.fill_project_name(project_name)
        self.add_project.fill_publish_app("xx")
        self.add_project.fill_project_desc("xxx")
        
        # 驗證頁面重定向到列表頁
        with page.expect_navigation(url="**/list_project.html"):
            self.add_project.click_save_button()
    
    # 完整流程:驗證新項目在列表中
    def test_add_project_success_verify_in_list(self, page: Page):
        """添加項目成功后,驗證項目??現(xiàn)在列表中"""
        # 生成隨機(jī)項目???
        new_project_name = str(uuid.uuid4()).replace('-', '')[:25]
        
        self.add_project.fill_project_name(new_project_name)
        self.add_project.fill_publish_app("xx")
        self.add_project.fill_project_desc("xxx")
        
        self.add_project.click_save_button()
        
        # 等待頁面重定向和網(wǎng)絡(luò)請求完成
        self.add_project.page.wait_for_load_state('networkidle')
        
        # 獲取表格中所有項目名稱
        loc_projects = self.add_project.page.locator('//table[@id="table"]//td[3]/a')
        project_names = [i.inner_text() for i in loc_projects.all()]
        
        print(f"新增項目名稱: {new_project_name}")
        print(f"列表中的項目: {project_names}")
        
        # 驗證新項目在列表中
        assert new_project_name in project_names, f"項目 {new_project_name} 未在列表中"

七、總結(jié)與啟示

通過這個企業(yè)級的 Playwright UI 自動化測試框架,我們學(xué)到了:

核心設(shè)計原則:

  1. 分層架構(gòu):清晰的分層使代碼更易維護(hù)和擴(kuò)展
  2. Page Object Model:將頁面元素和業(yè)務(wù)邏輯分離,提高復(fù)用性
  3. Fixture 機(jī)制:優(yōu)雅的依賴注入,減少代碼重復(fù)
  4. 網(wǎng)絡(luò)攔截:Mock API 能力,便于測試異常場景
  5. 自動化報告:完整的測試產(chǎn)物,快速定位問題

最佳實踐:

  • ? 優(yōu)先使用 Playwright 的隱式等待
  • ? 全局登錄一次,避免重復(fù)
  • ? 使用參數(shù)化進(jìn)行數(shù)據(jù)驅(qū)動測試
  • ? 完整的異常場景覆蓋
  • ? 充分利用 Mock API 提升速度

關(guān)鍵優(yōu)勢:

Playwright 相比 Selenium:

  • 更快的執(zhí)行速度(優(yōu)化程度更高)
  • 更好的等待機(jī)制(自動同步)
  • 原生網(wǎng)絡(luò)攔截能力(Mock API)
  • 完整的鏈路追蹤能力(快速定位問題)

參考資源

到此這篇關(guān)于使用Playwright+Pytest構(gòu)建Web UI自動化測試框架完整示例的文章就介紹到這了,更多相關(guān)Playwright+Pytest自動化測試框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用python實現(xiàn)PDF本地化壓縮

    使用python實現(xiàn)PDF本地化壓縮

    這篇文章主要為大家詳細(xì)介紹了如何使用python實現(xiàn)PDF本地化壓縮功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-08-08
  • Python利用PIL庫實現(xiàn)圖片轉(zhuǎn)Base64字符串的完整指南

    Python利用PIL庫實現(xiàn)圖片轉(zhuǎn)Base64字符串的完整指南

    這篇文章主要為大家詳細(xì)介紹了如何使用Python的PIL(Pillow)庫將圖像轉(zhuǎn)換為Base64編碼字符串,并提供多種實用場景的示例代碼,有需要的小伙伴可以了解下
    2026-01-01
  • 對python實現(xiàn)二維函數(shù)高次擬合的示例詳解

    對python實現(xiàn)二維函數(shù)高次擬合的示例詳解

    今天小編就為大家分享一篇對python實現(xiàn)二維函數(shù)高次擬合的示例詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-12-12
  • 通過Python模塊filecmp 對文件比較的實現(xiàn)方法

    通過Python模塊filecmp 對文件比較的實現(xiàn)方法

    這篇文章主要介紹了通過Python模塊filecmp 對文件比較的實現(xiàn)方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • python微元法計算函數(shù)曲線長度的方法

    python微元法計算函數(shù)曲線長度的方法

    今天小編就為大家分享一篇python微元法計算函數(shù)曲線長度的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-11-11
  • 從入門到精通:玩轉(zhuǎn)Python?Fire庫

    從入門到精通:玩轉(zhuǎn)Python?Fire庫

    想快速打造屬于你的Python?GUI應(yīng)用嗎?拋開復(fù)雜的代碼,用Python?Fire庫就能輕松實現(xiàn)!本指南將引領(lǐng)你從零起步,駕馭Python?Fire的強(qiáng)大功能,讓編程既簡單又高效,準(zhǔn)備好了嗎?讓我們開始玩轉(zhuǎn)Python?Fire,開啟你的編程冒險吧!
    2024-02-02
  • OpenCV模板匹配matchTemplate的實現(xiàn)

    OpenCV模板匹配matchTemplate的實現(xiàn)

    這篇文章主要介紹了OpenCV模板匹配matchTemplate的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 利用Python產(chǎn)生加密表和解密表的實現(xiàn)方法

    利用Python產(chǎn)生加密表和解密表的實現(xiàn)方法

    這篇文章主要介紹了利用Python產(chǎn)生加密表和解密表的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Python創(chuàng)建Excel的4種方式小結(jié)

    Python創(chuàng)建Excel的4種方式小結(jié)

    這篇文章主要為大家詳細(xì)介紹了Python中創(chuàng)建Excel的4種常見方式,文中的示例代碼簡潔易懂,具有一定的參考價值,感興趣的小伙伴可以學(xué)習(xí)一下
    2025-02-02
  • Python實現(xiàn)原神抽卡的方法

    Python實現(xiàn)原神抽卡的方法

    這篇文章主要為大家介紹了Python實現(xiàn)原神抽卡的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12

最新評論

茌平县| 昂仁县| 景谷| 渭南市| 五家渠市| 南岸区| 闻喜县| 晋中市| 高尔夫| 温泉县| 迁西县| 措美县| 色达县| 焦作市| 保康县| 衡南县| 花垣县| 兴业县| 全州县| 盐边县| 芜湖市| 瑞安市| 威信县| 涡阳县| 府谷县| 荥经县| 安泽县| 三门峡市| 吉首市| 封丘县| 厦门市| 子长县| 阳东县| 确山县| 巴彦县| 广州市| 孟连| 杭锦后旗| 同德县| 广水市| 大宁县|