如题,AI收集整理,作为了解
Playwright 完整使用指南
Date: October 23, 2025
Code: https://github.com/microsoft/playwright
目录
Playwright 概述
核心概念
环境搭建
基本用法
元素定位与操作
智能等待机制
网络请求处理
高级特性
最佳实践
实战案例
常见问题与解决方案
1. Playwright 概述
1.1 什么是 Playwright
Playwright 是由微软开发的一款开源自动化测试工具,专为现代 Web 应用程序设计。它提供了一套统一的 API,支持 Chromium、Firefox 和 WebKit 三大主流浏览器引擎,能够实现真正的跨浏览器测试。
1.2 核心优势
特性
传统工具(如 Selenium)
Playwright 解决方案
跨浏览器支持
需分别配置不同驱动,兼容性问题多
原生支持三大浏览器,API 完全统一
元素等待机制
依赖 time.sleep() 或显式等待,脚本不稳定
所有操作内置智能等待,自动等待元素"可操作"状态
元素定位能力
主要依赖 CSS、XPath,对动态页面支持差
支持文本、组件、属性组合等 10+ 种定位方式
多场景模拟
多标签页、多用户场景配置复杂
基于"上下文隔离"机制,多场景独立运行
网络请求控制
需借助第三方工具,集成复杂
内置网络拦截、Mock、限速功能
移动端测试
需搭建额外环境,配置繁琐
一键模拟手机设备,支持 GPS 定位模拟
1.3 适用场景
现代 Web 应用测试 :尤其适合 React、Vue、Angular 等前端框架开发的 SPA 应用
跨浏览器兼容性测试 :需在 Chrome、Firefox、Safari 同时验证功能的项目
自动化业务流程 :用户登录、订单提交、数据导出等重复性业务操作
异常场景测试 :模拟接口报错、网络延迟、弱网环境的测试
移动端响应式测试 :无需真实设备,模拟不同手机型号的浏览器表现
2. 核心概念
2.1 浏览器(Browser)
浏览器实例代表一个具体的浏览器进程,可以是 Chromium、Firefox 或 WebKit。
1 2 3 4 5 6 7 from playwright.sync_api import sync_playwrightwith sync_playwright() as p: chromium_browser = p.chromium.launch() firefox_browser = p.firefox.launch() webkit_browser = p.webkit.launch()
2.2 浏览器上下文(Browser Context)
浏览器上下文是一个隔离的会话环境,相当于一个无痕浏览器窗口。它可以独立管理 cookies、localStorage 等状态信息。
1 2 3 4 5 6 7 context = browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" , viewport={"width" : 1920 , "height" : 1080 }, geolocation={"longitude" : 12.492507 , "latitude" : 41.889938 }, permissions=["geolocation" ] )
2.3 页面(Page)
页面代表浏览器中的一个标签页,是进行页面操作的主要对象。
1 2 3 4 5 6 7 8 page = context.new_page() page.goto("https://playwright.dev" ) print (page.title())
2.4 元素定位器(Locator)
定位器是用于查找页面元素的对象,支持多种定位策略。
1 2 3 4 5 6 7 8 page.locator("text=Get Started" ).click() page.locator("button#submit" ).click() page.locator("//input[@name='username']" ).fill("admin" )
3. 环境搭建
3.1 安装 Playwright
Python 环境
1 2 3 4 5 pip install playwright playwright install
Node.js 环境
1 2 3 4 5 6 7 8 npm init playwright@latest yarn create playwright pnpm create playwright
3.2 验证安装
创建一个简单的测试脚本验证环境是否正常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from playwright.sync_api import sync_playwrightdef test_installation (): with sync_playwright() as p: browser = p.chromium.launch(headless=False ) page = browser.new_page() page.goto("https://www.baidu.com" ) assert "百度" in page.title() print ("环境搭建成功!" ) browser.close() if __name__ == "__main__" : test_installation()
3.3 浏览器配置
1 2 3 4 5 6 7 8 9 10 11 browser = p.chromium.launch( headless=False , slow_mo=50 , args=[ "--start-maximized" , "--disable-gpu" , "--no-sandbox" ], devtools=True )
4. 基本用法
4.1 同步 API vs 异步 API
同步 API
1 2 3 4 5 6 7 8 from playwright.sync_api import sync_playwrightwith sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto("https://example.com" ) print (page.title()) browser.close()
异步 API
1 2 3 4 5 6 7 8 9 10 11 12 import asynciofrom playwright.async_api import async_playwrightasync def main (): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() await page.goto("https://example.com" ) print (await page.title()) await browser.close() asyncio.run(main())
4.2 页面导航
1 2 3 4 5 6 7 8 9 10 11 12 13 page.goto("https://example.com" ) page.goto("https://example.com" , wait_until="networkidle" ) page.go_back() page.go_forward() page.reload() current_url = page.url
4.3 页面操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 page.screenshot(path="screenshot.png" , full_page=True ) page.pdf(path="page.pdf" , format ="A4" ) page.scroll_by_amount(0 , 500 ) content = page.content() page.wait_for_load_state("load" ) page.wait_for_load_state("domcontentloaded" ) page.wait_for_load_state("networkidle" )
5. 元素定位与操作
5.1 定位策略
文本定位
1 2 3 4 5 6 7 8 page.locator("text=Submit" ).click() page.locator("text=Log in" ).click() page.locator("text=/^Submit$/" ).click()
CSS 选择器定位
1 2 3 4 5 6 7 8 9 10 11 page.locator("#username" ).fill("admin" ) page.locator(".btn-primary" ).click() page.locator("[name='email']" ).fill("test@example.com" ) page.locator("form#login input[type='password']" ).fill("password" )
XPath 定位
1 2 3 4 5 6 7 8 page.locator("/html/body/div[1]/form/input[2]" ).click() page.locator("//input[@id='username']" ).fill("admin" ) page.locator("//button[text()='Submit']" ).click()
ARIA 角色定位
1 2 3 4 5 6 7 8 page.locator("role=button" , name="Submit" ).click() page.locator("label=Username" ).fill("admin" ) page.locator("placeholder=Enter your email" ).fill("test@example.com" )
5.2 元素操作
输入操作
1 2 3 4 5 6 7 8 9 10 11 12 page.locator("#username" ).fill("admin" ) page.locator("#username" ).clear() page.locator("#username" ).type ("admin" , delay=100 ) page.keyboard.press("Enter" ) page.keyboard.type ("Hello World!" )
点击操作
1 2 3 4 5 6 7 8 9 10 11 page.locator("button#submit" ).click() page.locator("div#item" ).dblclick() page.locator("div#menu" ).click(button="right" ) page.locator("checkbox#select-all" ).click(modifiers=["Shift" ])
表单操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 page.locator("select#country" ).select_option("China" ) page.locator("select#country" ).select_option(value="CN" ) page.locator("checkbox#agree" ).check() page.locator("checkbox#agree" ).uncheck() page.locator("input[type='file']" ).set_input_files("document.pdf" ) page.locator("input[type='file']" ).set_input_files(["file1.txt" , "file2.txt" ])
5.3 元素状态检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if page.locator("div.success-message" ).is_visible(): print ("操作成功" ) if page.locator("button#submit" ).is_enabled(): page.locator("button#submit" ).click() page.locator("div.loading" ).wait_for(state="hidden" ) value = page.locator("input#username" ).input_value() text = page.locator("div.message" ).text_content() attribute = page.locator("img.logo" ).get_attribute("src" )
6. 智能等待机制
6.1 自动等待原理
Playwright 的所有操作都会自动等待元素满足以下条件:
元素已附加到 DOM (attached)
元素可见 (visible)
元素稳定 (stable,没有动画)
元素能够接收事件 (not obscured)
元素已启用 (enabled)
6.2 显式等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 page.locator("div.result" ).wait_for(state="visible" ) page.locator("div.loading" ).wait_for(state="hidden" ) page.locator("div.message" ).wait_for(text="Success" ) page.wait_for_function("() => document.title === 'Home Page'" ) with page.expect_request("**/api/data" ) as request_info: page.click("button#load-data" ) request = request_info.value
6.3 等待超时配置
1 2 3 4 5 6 7 8 context = browser.new_context(timeout=30000 ) page.locator("button#submit" ).click(timeout=5000 ) page.goto("https://example.com" , timeout=10000 )
7. 网络请求处理
7.1 请求拦截与 Mock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def handle_route (route ): if "api/user" in route.request.url: route.fulfill( status=200 , content_type="application/json" , body='{"id": 123, "name": "Test User", "email": "test@example.com"}' ) elif route.request.url.endswith(".png" ): route.abort() else : route.continue_() page.route("**/*" , handle_route)
7.2 模拟网络错误
1 2 3 4 5 6 7 8 9 10 11 12 page.route("**/api/payment" , lambda route: route.fulfill( status=500 , content_type="text/plain" , body="Internal Server Error" )) page.route("**/api/slow" , lambda route: route.continue_(delay=3000 )) page.route("**/api/offline" , lambda route: route.abort("connectionrefused" ))
7.3 请求监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 page.on("request" , lambda request: print (f">> {request.method} {request.url} " )) page.on("response" , lambda response: print (f"<< {response.status} {response.url} " )) page.on("requestfailed" , lambda request: print (f"!! {request.url} {request.failure} " )) with page.expect_request("**/api/login" ) as request_info: page.click("button#login" ) login_request = request_info.value print (f"Login request: {login_request.post_data} " )
7.4 HTTP 认证
1 2 3 4 5 6 7 8 9 10 11 12 13 context = browser.new_context( http_credentials={"username" : "user" , "password" : "pass" } ) context = browser.new_context( proxy={ "server" : "http://proxy.example.com:8080" , "username" : "proxyuser" , "password" : "proxypass" } )
8. 高级特性
8.1 多页面和弹出窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 with page.expect_popup() as popup_info: page.click("a[target='_blank']" ) popup_page = popup_info.value with page.expect_download() as download_info: page.click("button#download" ) download = download_info.value download.save_as("file.pdf" ) page.on("dialog" , lambda dialog: dialog.accept()) page.on("dialog" , lambda dialog: dialog.dismiss()) page.on("dialog" , lambda dialog: dialog.accept("input text" ))
8.2 iframe 处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 frame = page.frame(name="payment-form" ) frame = page.frame(url="https://payment.example.com/form" ) iframe_element = page.locator("iframe#checkout" ) frame = iframe_element.content_frame() frame.locator("input#card-number" ).fill("4111 1111 1111 1111" ) frame.locator("button#pay" ).click() parent_frame = page.frame(name="parent" ) child_frame = parent_frame.frame(name="child" )
8.3 设备模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 iphone_13 = playwright.devices["iPhone 13" ] context = browser.new_context(**iphone_13) context = browser.new_context( viewport={"width" : 375 , "height" : 667 }, user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15" , device_scale_factor=2 , is_mobile=True , has_touch=True ) context = browser.new_context( geolocation={"longitude" : 12.492507 , "latitude" : 41.889938 }, permissions=["geolocation" ] ) context = browser.new_context(color_scheme="dark" )
8.4 视频录制和追踪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 context = browser.new_context(record_video_dir="videos/" ) page = context.new_page() page.goto("https://example.com" ) context.close() context.tracing.start(screenshots=True , snapshots=True ) page.goto("https://example.com" ) page.click("button#submit" ) context.tracing.stop(path="trace.zip" ) metrics = page.evaluate("""() => { const navigation = performance.getEntriesByType('navigation')[0]; return { domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart, loadComplete: navigation.loadEventEnd - navigation.loadEventStart, totalTime: navigation.loadEventEnd - navigation.navigationStart }; }""" )
8.5 并行执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from playwright.sync_api import sync_playwrightimport concurrent.futuresdef run_test (browser_type ): with sync_playwright() as p: browser = getattr (p, browser_type).launch() page = browser.new_page() page.goto("https://example.com" ) print (f"{browser_type} : {page.title()} " ) browser.close() with concurrent.futures.ThreadPoolExecutor() as executor: executor.map (run_test, ["chromium" , "firefox" , "webkit" ])
9. 最佳实践
9.1 测试设计原则
测试用户可见行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def test_login_success (page ): page.goto("/login" ) page.locator("input#username" ).fill("admin" ) page.locator("input#password" ).fill("password" ) page.locator("button#submit" ).click() assert page.is_visible("text=Welcome, admin!" ) assert page.url.endswith("/dashboard" ) def test_login_api_call (page ): with page.expect_request("**/api/login" ) as request_info: page.click("button#submit" ) request = request_info.value assert request.post_data_json["username" ] == "admin"
保持测试隔离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def test_user_management (page, login ): page.goto("/users" ) def test_multi_user_scenario (): with sync_playwright() as p: browser = p.chromium.launch() user1_context = browser.new_context() user1_page = user1_context.new_page() login(user1_page, "user1" , "pass1" ) user2_context = browser.new_context() user2_page = user2_context.new_page() login(user2_page, "user2" , "pass2" )
9.2 代码组织模式
Page Object Model (POM)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class LoginPage : def __init__ (self, page ): self .page = page self .username_input = page.locator("input#username" ) self .password_input = page.locator("input#password" ) self .submit_button = page.locator("button#submit" ) self .error_message = page.locator("div.error" ) def navigate (self ): self .page.goto("/login" ) def login (self, username, password ): self .username_input.fill(username) self .password_input.fill(password) self .submit_button.click() def get_error_message (self ): return self .error_message.text_content() def test_successful_login (page ): login_page = LoginPage(page) login_page.navigate() login_page.login("admin" , "password" ) assert page.url.endswith("/dashboard" ) def test_invalid_credentials (page ): login_page = LoginPage(page) login_page.navigate() login_page.login("admin" , "wrong_password" ) assert "Invalid credentials" in login_page.get_error_message()
Fixture 模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import pytestfrom playwright.sync_api import sync_playwright@pytest.fixture(scope="session" ) def playwright (): with sync_playwright() as p: yield p @pytest.fixture(scope="function" ) def browser (playwright ): browser = playwright.chromium.launch(headless=True ) yield browser browser.close() @pytest.fixture(scope="function" ) def page (browser ): page = browser.new_page() yield page page.close() @pytest.fixture(scope="function" ) def authenticated_page (page ): page.goto("/login" ) page.locator("input#username" ).fill("admin" ) page.locator("input#password" ).fill("password" ) page.locator("button#submit" ).click() return page
9.3 性能优化
选择性截图
1 2 3 4 5 6 7 8 9 10 11 12 def test_critical_functionality (page, request ): try : page.goto("/critical-feature" ) page.click("button#execute" ) assert page.is_visible("text=Success" ) except Exception as e: screenshot_path = f"screenshots/{request.node.name} .png" page.screenshot(path=screenshot_path, full_page=True ) raise e
网络优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def optimize_network (page ): page.route("**/*.{png,jpg,jpeg,gif,svg}" , lambda route: route.abort()) page.route("**/*.{js}" , lambda route: route.abort() if "analytics" in route.request.url else route.continue_()) page.route("**/api/slow-data" , lambda route: route.fulfill( status=200 , content_type="application/json" , body='{"data": []}' ))
9.4 调试技巧
可视化调试
1 2 3 4 5 6 7 8 browser = p.chromium.launch(headless=False , slow_mo=100 ) browser = p.chromium.launch(headless=False , devtools=True ) page.pause()
日志记录
1 2 3 4 5 6 7 8 9 10 11 12 13 import logginglogging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def test_with_logging (page ): logger.info("Starting login test" ) page.goto("/login" ) logger.info(f"Current URL: {page.url} " ) page.on("request" , lambda req: logger.debug(f"Request: {req.method} {req.url} " )) page.on("response" , lambda res: logger.debug(f"Response: {res.status} {res.url} " ))
10. 实战案例
10.1 电商网站测试案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class ECommerceTest : def __init__ (self, page ): self .page = page self .product_list = page.locator("div.product-item" ) self .add_to_cart_button = page.locator("button.add-to-cart" ) self .cart_icon = page.locator("div.cart-icon" ) self .checkout_button = page.locator("button.checkout" ) def search_product (self, keyword ): """搜索商品""" search_input = self .page.locator("input#search" ) search_input.fill(keyword) search_input.press("Enter" ) self .page.wait_for_load_state("networkidle" ) def add_product_to_cart (self, product_index=0 ): """添加商品到购物车""" products = self .product_list.all () if products: product = products[product_index] product.hover() add_button = product.locator("button.add-to-cart" ) add_button.click() self .page.locator("div.added-to-cart" ).wait_for(state="visible" ) def go_to_cart (self ): """进入购物车""" self .cart_icon.click() self .page.wait_for_url("**/cart" ) def proceed_to_checkout (self ): """进入结算页面""" self .checkout_button.click() self .page.wait_for_url("**/checkout" ) def test_ecommerce_flow (page ): """完整电商流程测试""" ecommerce = ECommerceTest(page) page.goto("https://example-ecommerce.com" ) ecommerce.search_product("laptop" ) ecommerce.add_product_to_cart() cart_count = ecommerce.cart_icon.text_content() assert cart_count == "1" ecommerce.go_to_cart() assert page.is_visible("text=Laptop" ) ecommerce.proceed_to_checkout() page.locator("input#shipping-address" ).fill("123 Test St" ) page.locator("input#credit-card" ).fill("4111 1111 1111 1111" ) page.locator("button#place-order" ).click() assert page.is_visible("text=Order placed successfully" ) assert page.url.endswith("/order-confirmation" )
10.2 数据抓取案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import csvimport asynciofrom playwright.async_api import async_playwrightclass DataScraper : def __init__ (self, browser ): self .browser = browser async def scrape_page (self, url ): """抓取单个页面数据""" page = await self .browser.new_page() try : await page.goto(url) await page.wait_for_load_state("networkidle" ) products = [] product_elements = await page.query_selector_all("div.product-item" ) for element in product_elements: product = { "name" : await element.locator("h3.product-name" ).text_content(), "price" : await element.locator("span.price" ).text_content(), "rating" : await element.locator("div.rating" ).text_content(), "image_url" : await element.locator("img.product-image" ).get_attribute("src" ) } products.append(product) return products finally : await page.close() async def scrape_multiple_pages (self, base_url, total_pages ): """抓取多页数据""" all_products = [] for page_num in range (1 , total_pages + 1 ): url = f"{base_url} ?page={page_num} " print (f"Scraping page {page_num} : {url} " ) page_data = await self .scrape_page(url) all_products.extend(page_data) await asyncio.sleep(1 ) return all_products def save_to_csv (self, data, filename ): """保存数据到 CSV 文件""" if not data: return with open (filename, mode='w' , newline='' , encoding='utf-8' ) as file: writer = csv.DictWriter(file, fieldnames=data[0 ].keys()) writer.writeheader() writer.writerows(data) async def main (): async with async_playwright() as p: browser = await p.chromium.launch(headless=True ) scraper = DataScraper(browser) base_url = "https://example-ecommerce.com/products" products = await scraper.scrape_multiple_pages(base_url, total_pages=5 ) scraper.save_to_csv(products, "products.csv" ) print (f"Scraped {len (products)} products successfully!" ) await browser.close() if __name__ == "__main__" : asyncio.run(main())
10.3 API 测试集成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import pytestimport requestsfrom playwright.sync_api import sync_playwrightclass APITester : def __init__ (self, base_url ): self .base_url = base_url self .session = requests.Session() def get_user (self, user_id ): """获取用户信息""" response = self .session.get(f"{self.base_url} /api/users/{user_id} " ) response.raise_for_status() return response.json() def create_user (self, user_data ): """创建用户""" response = self .session.post(f"{self.base_url} /api/users" , json=user_data) response.raise_for_status() return response.json() def update_user (self, user_id, user_data ): """更新用户信息""" response = self .session.put(f"{self.base_url} /api/users/{user_id} " , json=user_data) response.raise_for_status() return response.json() @pytest.fixture(scope="module" ) def api_tester (): """API 测试器 fixture""" return APITester("https://api.example.com" ) def test_api_and_ui_integration (api_tester, page ): """API 和 UI 集成测试""" user_data = { "name" : "Test User" , "email" : "test@example.com" , "password" : "password123" } created_user = api_tester.create_user(user_data) user_id = created_user["id" ] try : page.goto("https://example.com/login" ) page.locator("input#email" ).fill(user_data["email" ]) page.locator("input#password" ).fill(user_data["password" ]) page.locator("button#login" ).click() page.goto("https://example.com/profile" ) assert page.locator("h1.username" ).text_content() == user_data["name" ] assert page.locator("div.email" ).text_content() == user_data["email" ] page.locator("button#edit-profile" ).click() page.locator("input#name" ).fill("Updated Name" ) page.locator("button#save" ).click() updated_user = api_tester.get_user(user_id) assert updated_user["name" ] == "Updated Name" finally : api_tester.session.delete(f"{api_tester.base_url} /api/users/{user_id} " )
11. 常见问题与解决方案
11.1 元素定位问题
问题:元素定位不稳定
1 2 3 4 5 6 7 page.locator("div:nth-child(3) > button" ).click() page.locator("button#submit" ).click() page.locator("button[type='submit']" ).click() page.locator("text=Submit" ).click()
解决方案:
优先使用 ID 和 name 属性 :最稳定的定位方式
使用文本定位 :对于用户可见的元素
组合定位策略 :提高定位的唯一性
等待元素稳定 :使用 wait_for() 方法
11.2 异步加载问题
问题:页面内容动态加载导致元素找不到
1 2 3 4 5 6 7 8 9 10 11 page.goto("/products" ) page.locator("div.product" ).click() page.goto("/products" ) page.locator("div.product" ).wait_for(state="visible" ) page.locator("div.product" ).click() page.goto("/products" , wait_until="networkidle" )
解决方案:
使用 wait_until 参数 :确保页面完全加载
等待特定元素 :使用 wait_for() 方法
监听网络请求 :确保关键 API 调用完成
使用 page.wait_for_function() :等待自定义条件
11.3 测试稳定性问题
问题:测试时有时无地失败(Flaky Tests)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def test_flaky (page ): page.goto("/form" ) page.locator("input#name" ).fill("Test" ) page.locator("button#submit" ).click() assert page.is_visible("text=Success" ) def test_stable (page ): page.goto("/form" ) page.locator("input#name" ).fill("Test" ) with page.expect_navigation(): page.locator("button#submit" ).click() success_message = page.locator("text=Success" ) success_message.wait_for(state="visible" ) assert success_message.is_visible()
解决方案:
使用 expect_navigation() :等待页面跳转完成
明确等待条件 :不要依赖隐式等待
增加重试机制 :对不稳定的测试增加重试
使用 tracing :记录测试执行过程便于分析
11.4 性能优化问题
问题:测试执行速度慢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def test_slow_1 (): with sync_playwright() as p: browser = p.chromium.launch() @pytest.fixture(scope="session" ) def browser (): with sync_playwright() as p: browser = p.chromium.launch() yield browser browser.close() browser = p.chromium.launch(headless=True ) page.route("**/*.{png,jpg,jpeg,gif}" , lambda route: route.abort())
解决方案:
复用浏览器实例 :减少启动时间
使用无头模式 :提高执行速度
网络优化 :阻止不必要的资源加载
并行执行 :利用多核 CPU 资源
11.5 跨浏览器兼容性问题
问题:在不同浏览器中表现不一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import pytest@pytest.mark.parametrize("browser_type" , ["chromium" , "firefox" , "webkit" ] ) def test_cross_browser (browser_type, playwright ): browser = getattr (playwright, browser_type).launch() page = browser.new_page() try : page.goto("https://example.com" ) assert "Example Domain" in page.title() finally : browser.close()
解决方案:
参数化测试 :在多个浏览器中运行相同测试
统一选择器策略 :避免浏览器特定的选择器
处理浏览器差异 :对特定浏览器添加兼容代码
使用设备模拟 :测试移动端兼容性
11.6 调试技巧
使用 Playwright Inspector
1 2 PWDEBUG=1 python your_script.py
生成测试报告
1 2 3 4 5 6 pytest --html=report.html --self -contained-html pytest --alluredir=allure-results allure serve allure-results
录制和回放
1 2 3 4 5 playwright codegen https://example.com playwright codegen --save-storage=auth.json https://example.com/login