1. 项目概述为什么是Playwright Python如果你正在寻找一个能搞定Web自动化测试、数据抓取、甚至网页监控的Python工具并且已经厌倦了Selenium的复杂配置和偶尔的“抽风”或者觉得Puppeteer只能绑在Node.js上不够灵活那Playwright Python绝对值得你花时间研究。我最初接触它是因为一个跨浏览器兼容性测试的项目当时被Selenium在不同浏览器驱动版本上的“玄学”问题折腾得够呛直到用了Playwright才真正体会到什么叫“开箱即用”的顺畅。简单来说Playwright是一个由微软开源的现代化Web自动化测试和浏览器自动化库。它的Python版本playwright-python让你能用Python代码直接控制Chromium、Firefox和WebKitSafari的内核浏览器进行点击、输入、截图、拦截网络请求等几乎所有你能在浏览器里手动完成的操作。它的核心优势在于其架构设计它为每个测试或自动化脚本启动一个独立的浏览器上下文Browser Context这相当于一个全新的浏览器会话彼此完全隔离避免了Cookie、本地存储的污染也让并行执行变得异常简单和稳定。相比于Selenium基于WebDriver协议的“远程控制”模式Playwright直接通过DevTools Protocol与浏览器内核通信速度更快功能也更底层、更强大。这套教程的目标很明确让你从一个对Playwright只有耳闻的小白快速成长为能用它解决实际工作中复杂自动化需求的熟手。无论是测试工程师想要编写稳定可靠的E2E测试用例还是数据分析师需要从动态加载的网页中抓取数据或者是运维开发同学想做一个定时巡检网站健康状态的脚本你都能在这里找到可以直接“抄作业”的解决方案。我们不会面面俱到地罗列所有API而是聚焦于最核心的20%的功能解决80%的实际问题并分享那些官方文档里不会写的“踩坑”经验和性能调优技巧。2. 环境搭建与核心概念速通工欲善其事必先利其器。Playwright Python的安装过程已经非常简化但其中一些步骤背后的选择直接影响着你后续开发的体验。2.1 一站式安装与初始化首先你需要一个Python环境3.7及以上。我强烈建议使用虚拟环境如venv或conda来管理项目依赖避免包冲突。# 创建并激活虚拟环境以venv为例 python -m venv playwright-env # Windows playwright-env\Scripts\activate # macOS/Linux source playwright-env/bin/activate # 安装playwright库 pip install playwright # 安装Playwright所需的浏览器内核Chromium, Firefox, WebKit playwright install这里有几个关键点需要注意pip install playwright安装的是Playwright的Python客户端库它提供了我们编写脚本用的API。playwright install这个命令至关重要。它会下载Playwright自己维护的、与其API版本严格匹配的浏览器二进制文件。这些浏览器是专门为自动化优化过的通常比你自己从官网下载的浏览器更稳定并且默认支持无头模式。你也可以指定安装特定浏览器如playwright install chromium或playwright install firefox。浏览器管理Playwright管理的浏览器默认安装在用户目录下的缓存文件夹中例如~/Library/Caches/ms-playwright/on macOS。这意味着它不会干扰你系统上已安装的Chrome或Firefox。安装完成后用一个最简单的脚本验证一切正常import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动Chromium浏览器headlessFalse表示显示界面 browser await p.chromium.launch(headlessFalse) # 创建一个新的浏览器上下文隔离环境 context await browser.new_context() # 在新上下文中打开一个页面 page await context.new_page() # 导航到百度 await page.goto(https://www.baidu.com) # 等待3秒以便观察 await page.wait_for_timeout(3000) # 关闭浏览器 await browser.close() asyncio.run(main())运行这个脚本你应该能看到一个Chromium浏览器窗口自动打开并访问了百度。恭喜你的Playwright环境已经就绪。2.2 理解核心三要素Browser, Context, Page这是Playwright架构中最核心、也最需要理解清楚的三个概念它们的关系决定了脚本的稳定性和可维护性。Browser 可以理解为安装的浏览器程序本身如Chromium。通过launch()方法启动一个浏览器实例。一个Browser实例可以创建多个独立的Context。实操心得 对于大多数单任务脚本启动一个Browser就够了。如果是并行执行大量测试用例可以考虑启动多个Browser实例消耗更多资源但更常见的做法是在一个Browser下创建多个Context因为Context是轻量级且隔离的。Context这是Playwright的精华所在。你可以把它想象成一个全新的、独立的浏览器会话Profile。每个Context拥有独立的Cookie、本地存储LocalStorage、SessionStorage、缓存和证书。它相当于隐身模式打开的一个新窗口但功能更强大、可控。为什么重要在自动化中状态隔离是关键。比如你一个脚本要测试登录和未登录两种状态。如果没有Context你就需要反复清理Cookie非常麻烦且容易出错。有了Context你只需要创建两个Context一个用来登录一个保持未登录它们互不干扰。关闭Context会自动清理其所有资源。常用配置 创建Context时可以设置视口大小、User-Agent、地理位置、权限如是否允许通知等模拟各种浏览器环境。context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., localezh-CN # 设置语言环境为中文 )Page 代表Context中的一个标签页。我们绝大部分的自动化操作如点击、输入都是在Page对象上完成的。一个Context可以拥有多个Page即多个标签页。关系Browser- 创建多个Context- 每个Context包含多个Page。注意 官方推荐的最佳实践是为每个独立的测试用例或自动化任务创建一个新的Browser Context而不是复用同一个Context或Page。这能最大程度保证测试的独立性和可重复性。2.3 同步 vs. 异步API如何选择你可能注意到了上面的示例代码使用了async/await语法。Playwright Python同时提供了同步和异步两套API。异步API (playwright.async_api) 基于asyncio。当你的操作涉及大量等待如网络请求、元素出现时异步API允许你在等待一个页面加载时去处理另一个页面的操作能极大提升脚本效率特别是在执行并行任务时。这是Playwright的主推模式也是性能更优的选择。同步API (playwright.sync_api) 代码写起来更直观像传统的线性脚本。适合简单的、线性的自动化任务或者你对异步编程不熟悉的情况。# 同步API示例 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) context browser.new_context() page context.new_page() page.goto(https://example.com) print(page.title()) browser.close()我的建议是除非你的项目非常简单或已有大量同步代码基础否则直接从异步API开始学习。现代Python异步生态已经很成熟掌握它对于编写高效的网络相关程序有长远好处。本教程后续也将主要使用异步API进行讲解。3. 元素定位与交互自动化操作的基石自动化就是模拟人的操作而操作的前提是找到正确的目标。Playwright提供了丰富、强大且稳定的元素定位器Locators这是它比旧式工具好用的关键之一。3.1 定位器最佳实践与智能等待Playwright的定位器是延迟执行的并且内置了自动等待机制这解决了传统自动化中令人头疼的“元素未加载完成就进行操作”的问题。核心定位方式get_by_*系列推荐首选 这是最语义化、最不易失效的定位方式。Playwright会优先根据可访问性属性和文本内容来定位。page.get_by_role(): 通过ARIA角色定位如button,textbox。page.get_by_text(): 通过文本内容定位。page.get_by_label(): 通过关联的label文本定位输入框。page.get_by_placeholder(): 通过占位符文本定位。page.get_by_alt_text(): 通过图片的alt属性定位。page.get_by_title(): 通过title属性定位。page.get_by_test_id(): 通过开发者专门为测试添加的>async def login(page): # 更推荐使用 get_by_* 系列 await page.get_by_placeholder(请输入用户名).fill(my_username) # 如果输入框有配套的label用get_by_label更好 # await page.get_by_label(密码).fill(my_password) await page.locator(input[typepassword]).fill(my_password) # 用CSS选择器作为补充 # 点击登录按钮优先用role或text await page.get_by_role(button, name登录).click() # 或者 await page.get_by_text(登录).click()智能等待你不需要在每次操作前都写time.sleep。Playwright的几乎所有操作如click,fill,goto都内置了等待。它会等待元素满足可操作状态如可见、启用、稳定。page.wait_for_selector(): 等待特定选择器的元素出现。page.wait_for_function(): 等待页面JavaScript执行到某个状态。page.wait_for_load_state(): 等待页面加载到特定状态load,domcontentloaded,networkidle。networkidle在单页应用SPA中很有用表示网络空闲。踩坑记录 对于动态加载的单页应用如Vue, Reactpage.goto()的默认等待load事件可能不够因为此时前端框架可能还在异步加载数据。一个更可靠的做法是结合wait_for_load_state(networkidle)和等待某个特定元素出现。await page.goto(https://spa.example.com) await page.wait_for_load_state(networkidle) # 等待主要网络活动停止 await page.wait_for_selector(.data-loaded-indicator) # 等待数据加载完成的UI指示器3.2 高级交互键盘、鼠标、文件与拖放除了点击和输入Playwright还能模拟复杂的用户交互。键盘操作await page.get_by_role(textbox).press(ControlA) # 全选 await page.keyboard.type(Hello World!) # 模拟键盘输入 await page.keyboard.press(Enter)鼠标操作await page.mouse.move(x, y) # 移动鼠标到坐标 await page.mouse.down() # 按下鼠标左键 await page.mouse.move(x 100, y) # 拖动 await page.mouse.up() # 松开鼠标左键 # 更简单的方式使用locator的drag_to方法 source page.locator(#draggable) target page.locator(#droppable) await source.drag_to(target)文件上传这是Playwright做得特别好的地方无需模拟点击文件选择框这种不稳定的操作。# 直接设置文件输入框的值 file_input page.locator(input[typefile]) await file_input.set_input_files([ /path/to/file1.txt, /path/to/file2.jpg ]) # 如果要清空已选文件 await file_input.set_input_files([])处理弹窗与对话框Playwright可以监听并响应alert,confirm,prompt以及页面上弹出的自定义模态框。# 监听对话框并接受 page.on(dialog, lambda dialog: dialog.accept()) await page.locator(button#trigger-alert).click() # 点击触发alert的按钮 # 或者更精确的控制 def handle_dialog(dialog): print(f对话框消息: {dialog.message}) if dialog.type confirm: dialog.accept() # 点击确定 else: dialog.dismiss() # 点击取消 page.once(dialog, handle_dialog) # 使用once监听一次4. 网络拦截与Mock掌控请求与响应这是Playwright相对于纯UI自动化工具的降维打击能力。你可以监听、修改甚至伪造浏览器发出的任何网络请求和收到的响应这对于测试、爬虫和性能分析至关重要。4.1 监听与修改网络流量async def handle_request(request): # 修改所有请求的请求头 headers request.headers headers[x-custom-header] my-value # 可以继续请求也可以中止 request.abort() async def handle_response(response): if /api/data in response.url: # 拦截特定API的响应 json_data await response.json() print(f拦截到数据: {json_data}) # 注意这里无法直接修改原始响应内容但可以记录或触发其他逻辑 # 在page或context上添加监听器 page.on(request, handle_request) page.on(response, handle_response) await page.goto(https://example.com) # 记得在不需要时移除监听器避免内存泄漏 page.remove_listener(request, handle_request)4.2 模拟API响应Mock在测试中我们经常需要模拟后端API返回特定数据如错误状态、空数据来测试前端表现。Playwright的route功能可以轻松实现。async def mock_api_response(route): # 拦截对 /api/user 的请求 if /api/user in route.request.url: # 伪造一个JSON响应 await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({name: Mock User, id: 123}) ) else: # 其他请求继续正常进行 await route.continue_() # 开始路由拦截 await page.route(**/api/**, mock_api_response) # ** 是通配符 await page.goto(https://your-app.com) # 此时页面中调用 /api/user 的请求将收到我们伪造的数据实操心得 Mock数据时务必确保返回的数据结构JSON字段、类型与真实API完全一致否则可能导致前端JavaScript报错测试无法真实反映UI逻辑。最好从浏览器开发者工具的Network面板里拷贝一份真实的响应作为模板。4.3 性能分析与资源管理通过监听请求和响应你可以轻松地收集页面性能数据。import time start_time time.time() all_responses [] async def log_response(response): all_responses.append({ url: response.url, status: response.status, timing: response.request.timing }) page.on(response, log_response) await page.goto(https://example.com) page.remove_listener(response, log_response) total_time time.time() - start_time print(f页面加载总耗时: {total_time:.2f}秒) # 分析all_responses找出慢请求 slow_requests [r for r in all_responses if r[timing][responseEnd] - r[timing][requestStart] 1]5. 高级特性与实战技巧掌握了基础操作和网络控制后我们来探索一些能大幅提升脚本能力和稳定性的高级特性。5.1 处理iframe、多标签页与弹窗iframe 需要先定位到iframe元素然后获取其内部的frame对象才能操作。# 通过iframe的name属性或选择器定位 iframe_element page.frame_locator(iframe[namecontent]) # 或者通过URL匹配 frame page.frame(urlr.*login.*) # 然后在frame对象上操作就像操作page一样 await frame.locator(input#username).fill(user)多标签页 Playwright通过context.pages来管理一个上下文中的所有页面。# 点击一个会打开新标签页的链接通常需要监听popup事件 async with page.expect_popup() as popup_info: await page.get_by_text(Open New Tab).click() new_page await popup_info.value await new_page.bring_to_front() # 切换到新标签页 # 操作new_page... # 遍历所有标签页 for single_page in context.pages: print(single_page.title())弹窗非对话框 对于由window.open()或target_blank打开的新窗口使用expect_popup是最佳实践。5.2 执行JavaScript与获取页面状态有时需要通过注入JS来获取复杂数据或执行特殊操作。# 获取页面标题 title await page.title() # 获取页面URL url page.url # 执行JS并返回值 element_count await page.evaluate(() document.querySelectorAll(div).length) # 在页面环境中执行JS可以传入参数 dimensions await page.evaluate(([width, height]) { return { innerWidth: width, innerHeight: height }; }, [800, 600]) # 将JS函数应用到某个元素上 bounding_box await page.locator(button).evaluate((element) element.getBoundingClientRect())注意page.evaluate()中的代码是在浏览器页面环境中执行的与你的Python脚本环境隔离。传递参数和返回值会被自动序列化/反序列化。对于复杂的DOM操作优先使用Playwright内置的定位器和操作方法它们更稳定且提供了自动等待。evaluate通常用于获取Playwright API无法直接获取的信息如复杂的CSS计算值或执行特定JS库的功能。5.3 截图、录屏与PDF生成截图await page.screenshot(pathscreenshot.png, full_pageTrue) # 全屏截图 await page.locator(.chart).screenshot(pathchart.png) # 对特定元素截图录屏 需要在启动Context时开启录屏选项。context await browser.new_context(record_video_dir./videos/) page await context.new_page() # ... 你的操作 ... await context.close() # 关闭context后视频文件才会被保存 # 视频路径可以通过 page.video.path() 获取生成PDFawait page.pdf(pathdocument.pdf, formatA4)生成PDF功能对页面的CSS打印样式支持较好适合生成报告。5.4 设备模拟与移动端测试Playwright内置了多种主流移动设备如iPhone, Pixel的配置可以轻松模拟移动端浏览器环境。from playwright.async_api import async_playwright, Devices async def main(): async with async_playwright() as p: # 使用预定义的设备描述符 iphone Devices[iPhone 13 Pro] browser await p.chromium.launch() # 在new_context时传入设备参数 context await browser.new_context(**iphone) page await context.new_page() await page.goto(https://mobile.example.com) # 此时页面视图、User-Agent、触摸事件等都已模拟为iPhone 13 Pro你也可以自定义设备参数如屏幕尺寸、像素比、是否支持触摸等。6. 测试集成与最佳实践Playwright虽然是一个通用的浏览器自动化库但其在测试领域的应用最为广泛。它提供了专门的测试运行器Playwright Test但用纯Python脚本组织测试也同样高效。6.1 组织可维护的测试脚本即使是使用纯Python遵循一些模式也能让测试代码更清晰。# test_login.py import pytest import asyncio from playwright.async_api import async_playwright, Page pytest.fixture(scopesession) def event_loop(): 为异步测试创建事件循环 loop asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() pytest.fixture(scopefunction) async def page(): 为每个测试用例提供一个干净的页面 async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 无头模式运行 context await browser.new_context() page await context.new_page() yield page # 测试结束后清理 await context.close() await browser.close() pytest.mark.asyncio async def test_successful_login(page: Page): 测试成功登录流程 await page.goto(https://your-app.com/login) await page.get_by_label(用户名).fill(testuser) await page.get_by_label(密码).fill(securepass) await page.get_by_role(button, name登录).click() # 断言登录后应跳转到首页并显示用户名 await page.wait_for_url(**/dashboard) welcome_text await page.get_by_text(欢迎testuser).text_content() assert testuser in welcome_text pytest.mark.asyncio async def test_login_with_invalid_password(page: Page): 测试使用错误密码登录 await page.goto(https://your-app.com/login) await page.get_by_label(用户名).fill(testuser) await page.get_by_label(密码).fill(wrongpass) await page.get_by_role(button, name登录).click() # 断言应显示错误提示信息 error_message page.locator(.alert-error) await expect(error_message).to_be_visible() # 使用Playwright的断言 await expect(error_message).to_contain_text(密码错误)关键点使用Pytest 它是Python生态中最主流的测试框架与异步代码配合良好需要pytest-asyncio插件。Fixture管理资源 如上例中的pagefixture它负责创建和销毁浏览器实例及页面确保每个测试独立运行。使用正式的断言 Playwright提供了expect断言库它内置了智能等待比直接用Python的assert更可靠。例如expect(locator).to_be_visible()会先等待元素可见再断言。6.2 配置与参数化将浏览器类型、是否无头、基础URL等配置外置提高脚本的灵活性。# conftest.py import pytest import os from playwright.async_api import async_playwright def pytest_addoption(parser): parser.addoption(--browser, actionstore, defaultchromium, help浏览器: chromium, firefox, webkit) parser.addoption(--headless, actionstore_true, defaultTrue, help是否无头运行) parser.addoption(--base-url, actionstore, defaulthttps://staging.example.com, help测试环境基础URL) pytest.fixture(scopesession) def base_url(request): return request.config.getoption(--base-url) pytest.fixture(scopefunction) async def page(request, base_url): browser_name request.config.getoption(--browser) headless request.config.getoption(--headless) async with async_playwright() as p: browser await getattr(p, browser_name).launch(headlessheadless) context await browser.new_context(base_urlbase_url) # 设置基础URL page await context.new_page() yield page await context.close() await browser.close()然后可以通过命令行参数运行测试pytest --browserfirefox --headlessFalse --base-urlhttps://prod.example.com。6.3 稳定性提升重试、超时与等待策略自动化测试最大的敌人是不稳定Flaky Tests。Playwright提供了多种机制来对抗它。自动重试 Pytest可以配置测试失败自动重试。pytest --reruns 2 --reruns-delay 1 # 失败后重试2次每次间隔1秒或者在代码中对某些不稳定操作使用自定义重试逻辑。import asyncio async def click_with_retry(locator, max_retries3): for i in range(max_retries): try: await locator.click(timeout5000) # 单次操作超时5秒 return except Exception as e: if i max_retries - 1: raise await asyncio.sleep(1) print(f点击失败第{i1}次重试...)合理设置超时 Playwright的全局超时、导航超时、操作超时都可以配置。# 在创建context或page时设置 context await browser.new_context( viewportVIEWPORT, # 设置全局超时 default_timeout30000, # 30秒 ) page await context.new_page() # 设置页面级别的导航超时 page.set_default_navigation_timeout(60000) # 60秒 page.set_default_timeout(20000) # 20秒使用更稳定的定位器 如前所述优先使用get_by_role(),get_by_text(),get_by_test_id()。避免使用可能随样式或微小布局变化而改变的XPath或复杂CSS选择器。等待策略 不要用固定的sleep。多用wait_for_selector,wait_for_function和wait_for_load_state(networkidle)来等待页面达到预期状态。7. 常见问题排查与性能优化即使遵循了最佳实践在实际项目中还是会遇到各种问题。这里记录了一些高频问题的排查思路和优化技巧。7.1 元素找不到或操作超时这是最常见的问题没有之一。检查元素是否在iframe内 这是新手最容易忽略的一点。确保你的操作对象在当前页面的主文档中如果元素在iframe里必须先切换到对应的frame。检查页面是否加载完成 对于SPA确保在操作前页面数据已加载。使用page.wait_for_load_state(networkidle)并结合等待特定元素出现。检查定位器是否正确 使用Playwright Inspector来辅助调试。通过设置环境变量PWDEBUG1运行脚本它会打开一个可交互的调试工具让你可以查看页面、生成定位器、逐步执行代码。PWDEBUG1 python your_script.py检查是否有弹窗/对话框阻塞 未处理的alert、confirm会阻塞页面脚本执行。添加对话框监听器处理它们。元素被遮挡 有时要点击的元素可能被另一个透明或悬浮的元素覆盖。Playwright的click默认会检查元素是否可操作包括是否被覆盖。如果确认要强制点击可以使用forceTrue参数但需谨慎。await locator.click(forceTrue)7.2 脚本运行速度慢启用无头模式 在服务器或CI环境中运行务必使用headlessTrue默认就是True图形渲染会消耗大量资源。复用Browser实例但创建新的Context 启动浏览器的开销很大。对于一组相关的测试或任务复用同一个Browser实例但为每个独立场景创建新的Context。# 不好的做法每个任务都启动关闭浏览器 # 好的做法 async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) for task in tasks: context await browser.new_context() # 复用Browser新建Context page await context.new_page() await run_task(page, task) await context.close() # 关闭Context清理状态 await browser.close()避免不必要的等待 用智能等待wait_for_*替代固定的page.wait_for_timeout()。并行执行 利用异步API和asyncio.gather()并行执行多个独立任务。async def fetch_page(url, context): page await context.new_page() await page.goto(url) title await page.title() await page.close() return title async with async_playwright() as p: browser await p.chromium.launch() context await browser.new_context() urls [https://example.com/1, https://example.com/2, https://example.com/3] tasks [fetch_page(url, context) for url in urls] results await asyncio.gather(*tasks) # 并行获取 print(results)拦截不必要的资源 如果测试不关心图片、样式表、字体等可以路由拦截并中止它们大幅提升加载速度。async def block_assets(route): if route.request.resource_type in [image, stylesheet, font]: await route.abort() else: await route.continue_() await page.route(**/*, block_assets)7.3 在CI/CD中运行如GitHub Actions, Jenkins在无显示服务器的CI环境中运行需要一些额外配置。安装系统依赖 Playwright的浏览器需要一些系统库。可以使用Playwright CLI自动安装。playwright install-deps # 安装所有浏览器的系统依赖 playwright install-deps chromium # 只安装Chromium的依赖使用官方Docker镜像 最简单可靠的方式。微软提供了包含所有依赖的Docker镜像。FROM mcr.microsoft.com/playwright/python:v1.40.0-noble COPY . /app WORKDIR /app RUN pip install -r requirements.txt CMD [pytest]配置缓存 在CI中缓存Playwright的浏览器二进制文件~/.cache/ms-playwright可以显著加快流水线速度。7.4 调试技巧Playwright Inspector 如前所述PWDEBUG1是最强大的交互式调试工具。录制代码 使用playwright codegen命令可以打开一个浏览器你手动操作它会实时生成对应的Python代码。这是学习API和快速生成脚本原型的绝佳方式。playwright codegen https://example.com追踪查看器 生成一个可视化的操作追踪文件像视频一样回放脚本执行过程能看清每一步发生了什么。context await browser.new_context() # 启动追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue) # ... 执行你的操作 ... # 停止追踪并保存 await context.tracing.stop(pathtrace.zip)然后使用命令playwright show-trace trace.zip打开查看器。控制台输出 将浏览器控制台日志输出到你的终端。page.on(console, lambda msg: print(fCONSOLE: {msg.type} - {msg.text})) page.on(pageerror, lambda err: print(fPAGE ERROR: {err}))从我自己的项目经验来看Playwright Python真正做到了“功能强大”和“开发者友好”的平衡。它抽象了底层浏览器的复杂性提供了稳定、直观的API。花时间熟练掌握它不仅能提升自动化测试的效率和可靠性更能为你打开Web自动化、爬虫、RPA等领域的一扇新大门。开始可能会觉得异步编程有点门槛但一旦习惯你会发现用它写出的脚本既简洁又高效。遇到问题时多利用Inspector和Trace工具大部分难题都能迎刃而解。