Playwright自动化测试:从核心原理到实战框架搭建指南
1. 项目概述为什么是Playwright如果你在过去几年里做过Web自动化测试大概率用过或者听说过Selenium。它像一位功勋卓著的老将开创了一个时代但也背负着沉重的历史包袱——浏览器驱动版本管理、不稳定的等待、跨浏览器兼容性的各种“坑”。当我第一次接触Playwright时我的感觉是“终于有人把这些问题系统性地解决了。” 它不是Selenium的简单改进版而是一个为现代Web应用从头设计的下一代自动化框架。由微软的团队开发Playwright原生支持Chromium、Firefox和WebKit三大浏览器引擎这意味着你可以用一套API测试Chrome、Edge、Firefox和Safari而无需为每个浏览器维护不同的驱动或处理迥异的API。更关键的是它诞生在Web技术栈已经高度复杂化的今天。单页应用SPA、动态内容加载、Shadow DOM、Service Worker、WebSocket……这些技术让传统的“等待页面加载完成”变得毫无意义。Playwright的设计哲学就是拥抱这种复杂性提供了一套更智能、更健壮、更贴近开发者直觉的API。对于测试工程师、前端开发者或者任何需要与浏览器进行可靠、可编程交互的人来说Playwright正在迅速从一个“新选择”变成“默认选择”。接下来我会从一个实践者的角度拆解它的核心优势、如何上手以及那些官方文档里不会写的实战技巧。2. 核心设计理念与架构优势2.1 告别“驱动地狱”一体化架构Selenium最大的痛点之一是WebDriver。你需要为每个浏览器版本下载对应的驱动并确保驱动版本、浏览器版本和客户端库版本三者匹配。版本不匹配导致的诡异错误足以消耗掉半天时间。Playwright彻底摒弃了这种模式。当你通过npm install playwright或pip install playwright安装时它会自动下载一个经过定制和优化的浏览器二进制文件集合包括Chromium、Firefox和WebKit。这个二进制文件与Playwright库本身是强绑定的由Playwright团队进行版本管理和测试。这意味着只要你安装的Playwright版本一致在任何机器上运行的浏览器环境都是完全一致的从根本上消除了环境差异带来的不稳定。注意这种“自带浏览器”的模式虽然带来了稳定性但也意味着更大的初始安装包约100-200MB取决于你安装的浏览器。在CI/CD流水线中你需要考虑缓存策略来优化构建时间。不过与它在测试稳定性上带来的收益相比这点磁盘空间代价是完全可以接受的。2.2 为异步而生自动等待与事件驱动现代Web应用是高度动态的。一个按钮点击后可能触发一个API调用然后局部更新DOM最后才出现一个弹窗。传统的自动化脚本需要在这些步骤之间插入大量的time.sleep()或显式等待WebDriverWait这不仅代码丑陋而且极其脆弱——等待时间短了会失败长了又浪费执行时间。Playwright的API在设计上就是“等待就绪”的。例如page.click(‘button#submit’)这个操作内部会自动执行一系列检查等待元素出现在DOM中、等待元素可见、等待元素可交互例如未被禁用、未被其他元素遮挡最后才执行点击动作。这几乎消除了90%因时机问题导致的测试失败。对于更复杂的场景Playwright提供了基于事件的高级等待机制如page.waitForSelector(‘.toast’, state: ‘visible’)或等待网络请求page.waitForResponse(‘**/api/data‘)。这让你的测试逻辑可以紧密地贴合应用的实际行为流。2.3 超越页面多上下文与多维度支持Playwright的“浏览器上下文”Browser Context概念是一个神来之笔。你可以把它理解为一个完全独立的浏览器会话拥有独立的cookie、localStorage、权限设置如地理位置、通知等。这带来了两个巨大好处并行隔离测试你可以在一个浏览器实例中创建多个互不干扰的上下文并行运行测试用例而无需启动多个沉重的浏览器进程极大地提升了测试套件的执行速度。模拟复杂场景轻松测试多用户、多标签页、隐身模式等场景。例如你可以用一个上下文模拟登录用户用另一个上下文模拟未登录游客在同一测试中验证两者的不同体验。此外Playwright原生支持移动端视图模拟包括设备型号、屏幕尺寸、触摸事件、文件上传/下载拦截、网络请求 mocking、键盘和鼠标的精细模拟如拖拽、悬停甚至可以直接录制操作生成代码。这些功能不是后期添加的补丁而是其核心架构的一部分。3. 从零搭建一个健壮的Playwright测试框架3.1 环境准备与初始化我们以Node.js环境为例Python版本思路类似。首先在你的项目根目录下初始化并安装Playwright。# 初始化项目如果尚未初始化 npm init -y # 安装Playwright测试库和浏览器 npm install --save-dev playwright/test # 安装Playwright支持的浏览器Chromium, Firefox, WebKit npx playwright install这里我推荐直接使用playwright/test这个官方测试运行器而不是单独安装playwright库。因为它集成了测试运行、断言、HTML报告等一整套工具链开箱即用。执行npx playwright install时它会下载所有浏览器。如果你只需要特定浏览器以节省磁盘空间可以使用npx playwright install chromium。接下来进行基础配置。在项目根目录创建playwright.config.ts或.js文件。这是框架的核心配置文件。// playwright.config.ts import { defineConfig, devices } from playwright/test; export default defineConfig({ // 测试文件的位置 testDir: ./tests, // 并行运行所有测试文件谨慎使用取决于资源 fullyParallel: true, // 每个测试文件默认的失败重试次数对端到端测试非常有用 retries: process.env.CI ? 2 : 0, // CI环境下每个工作进程的并发测试数 workers: process.env.CI ? 1 : undefined, // 测试报告 reporter: [ [html, { outputFolder: playwright-report, open: never }], // HTML报告 [list] // 控制台简洁输出 ], // 项目配置可以定义多套环境如桌面Chrome、移动端Safari等 projects: [ { name: chromium, use: { ...devices[Desktop Chrome] }, }, { name: firefox, use: { ...devices[Desktop Firefox] }, }, { name: webkit, use: { ...devices[Desktop Safari] }, }, ], // 全局超时、每个测试的超时等 timeout: 30 * 1000, // 每个测试30秒超时 expect: { timeout: 5000, // 断言超时5秒 }, // 全局的setup和teardown文件可选 // globalSetup: ./global-setup, // globalTeardown: ./global-teardown, });这个配置定义了一个基础但强大的框架支持在Chromium、Firefox、WebKit上并行运行测试失败自动重试并生成直观的HTML报告。3.2 编写你的第一个可维护测试用例不要在单个测试文件里写几百行代码。好的测试框架需要好的代码组织。我推荐使用Page Object ModelPOM模式这是保持测试代码可维护性的黄金法则。首先创建一个页面对象。假设我们要测试一个登录功能。// pages/LoginPage.js class LoginPage { constructor(page) { this.page page; // 使用CSS选择器定位元素 this.usernameInput page.locator(#username); this.passwordInput page.locator(#password); this.submitButton page.locator(button[typesubmit]); this.errorMessage page.locator(.alert-error); } async navigate() { await this.page.goto(https://your-app.com/login); } async login(username, password) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage() { return await this.errorMessage.textContent(); } } module.exports { LoginPage };注意我们使用page.locator()来定位元素。Locator是Playwright的核心抽象它代表一个随时可以执行操作的元素并且内置了自动等待。这比直接使用page.$()或page.$$()要好得多。接着编写测试用例。// tests/login.spec.js const { test, expect } require(playwright/test); const { LoginPage } require(../pages/LoginPage); // 使用test.describe组织相关测试 test.describe(用户登录功能, () { let loginPage; // test.beforeEach会在每个测试用例之前运行 test.beforeEach(async ({ page }) { loginPage new LoginPage(page); await loginPage.navigate(); }); test(使用正确凭据登录成功, async ({ page }) { await loginPage.login(valid_user, valid_password); // 断言登录后应跳转到仪表盘页面 await expect(page).toHaveURL(/.*dashboard/); // 断言页面应包含欢迎文本 await expect(page.locator(h1)).toContainText(欢迎回来); }); test(使用错误密码登录显示错误信息, async ({ page }) { await loginPage.login(valid_user, wrong_password); // 断言错误信息应该可见 await expect(loginPage.errorMessage).toBeVisible(); // 断言错误信息内容符合预期 const errorText await loginPage.getErrorMessage(); expect(errorText).toMatch(/密码错误|无效的凭据/i); }); test(用户名不能为空, async ({ page }) { await loginPage.login(, somepassword); // 可以断言提交按钮被禁用或者有特定的验证提示 await expect(loginPage.submitButton).toBeDisabled(); // 或者检查HTML5验证提示 const isInvalid await loginPage.usernameInput.evaluate(el el.checkValidity()); expect(isInvalid).toBeFalsy(); }); });运行测试npx playwright test。你会看到测试在三个浏览器中依次或并行运行控制台输出结果并且结束后会在playwright-report目录下生成一个详细的HTML报告里面包含了每一步的操作截图、时间线追踪这对于调试失败的测试至关重要。3.3 高级配置与最佳实践1. 环境变量与多环境配置你的测试不应该硬编码测试环境的URL。使用环境变量。// 在playwright.config.ts中 use: { baseURL: process.env.BASE_URL || https://staging.your-app.com, // ... 其他配置 } // 在测试中 await page.goto(/login); // 会自动拼接baseURL你可以通过BASE_URLhttps://prod.your-app.com npx playwright test来指定生产环境测试。2. 认证状态复用登录操作往往很耗时。对于需要登录状态的测试套件可以在全局Setup中登录一次并将认证状态cookies, localStorage保存下来供所有测试复用。// global-setup.js const { chromium } require(playwright/test); module.exports async config { const browser await chromium.launch(); const page await browser.newPage(); await page.goto(https://your-app.com/login); await page.fill(#username, process.env.TEST_USER); await page.fill(#password, process.env.TEST_PASS); await page.click(button[typesubmit]); // 等待导航完成确保登录成功 await page.waitForURL(**/dashboard); // 将当前上下文的存储状态保存到文件中 await page.context().storageState({ path: storageState.json }); await browser.close(); }; // 在playwright.config.ts中配置 // globalSetup: ./global-setup, // 在测试项目中加载存储状态 projects: [ { name: logged-in-chromium, use: { ...devices[Desktop Chrome], storageState: storageState.json, // 复用登录状态 }, }, ]3. 模拟网络与API响应这是Playwright最强大的功能之一可以让你在不依赖后端的情况下测试前端逻辑。test(在离线状态下显示友好提示, async ({ page }) { // 拦截所有类型为“document”或“stylesheet”的请求模拟失败 await page.route(**/*.{css,js,html,png}, route route.abort()); await page.goto(/); await expect(page.locator(.offline-message)).toBeVisible(); }); test(模拟API返回特定数据以测试UI渲染, async ({ page }) { // 拦截特定的GraphQL查询或REST API端点 await page.route(**/api/user/profile, async route { const json { name: 模拟用户, role: admin }; await route.fulfill({ json }); }); await page.goto(/profile); // 现在页面会使用我们模拟的数据进行渲染 await expect(page.locator(.user-name)).toHaveText(模拟用户); });4. 实战避坑指南与性能优化4.1 定位器策略稳定性的基石不稳定的元素定位是自动化测试失败的首要原因。Playwright提供了多种定位方式优先级如下Role-based Locator (首选)这是最稳定、语义化的方式。通过元素的ARIA角色、名称等来定位。// 好通过角色和名称定位提交按钮 await page.getByRole(button, { name: 提交 }).click(); // 好定位搜索框 await page.getByRole(textbox, { name: 搜索 }).fill(关键词);即使按钮的CSS类名或ID变了只要它的可访问性名称没变测试就不会失败。Text-based Locator通过元素内的文本来定位。await page.getByText(登录).click(); await page.getByText(欢迎, { exact: false }).click(); // 模糊匹配Test ID Locator (强烈推荐)与开发协作为关键测试元素添加专用的>// 前端代码button>// 尽量避免 await page.locator(//div[idcontainer]/button[3]).click(); // 相对好一些 await page.locator(.modal-footer .btn-primary).click();实操心得在项目初期就和团队约定>// 错误列表可能还在加载中 const items page.locator(.list-item); const count await items.count(); // 此时count可能为0 // 正确先等待至少一个元素出现或列表容器稳定 await page.waitForSelector(.list-item:first-child); // 或者使用更通用的等待函数 await expect(page.locator(.list-item)).toHaveCount.greaterThan(0); // 或者等待一个加载指示器消失 await page.locator(.loading-spinner).waitFor({ state: hidden });场景二处理非模态弹窗/Toast提示非模态提示往往几秒后自动消失测试需要在其消失前捕获。// 执行某个会触发Toast的操作 await page.getByRole(button, { name: 保存 }).click(); // 立即定位到Toast并断言其文本Playwright的断言内置等待 await expect(page.locator(.toast)).toContainText(保存成功); // 还可以等待其出现再消失确保流程完整 const toast page.locator(.toast); await toast.waitFor({ state: visible }); await toast.waitFor({ state: hidden });场景三在iframe或Shadow DOM内部操作Playwright对这两种“DOM中的孤岛”有很好的支持。// 1. 处理iframe const frame page.frame({ url: /.*payment-gateway.*/ }); // 通过URL匹配 // 或者通过iframe的name或选择器 const frame page.frameLocator(iframe[namepayment]).first(); // 然后在frame的上下文中操作 await frame.locator(#card-number).fill(4111111111111111); // 2. 处理Shadow DOM // 直接穿透Shadow Root进行定位 const shadowHost page.locator(my-custom-element); const shadowButton shadowHost.locator(button); // Playwright会自动穿透shadow DOM await shadowButton.click();4.3 测试数据管理与清理端到端测试经常需要创建测试数据并在测试后清理避免污染后续测试。策略一API准备数据在beforeAll或beforeEach钩子中通过调用后端API快速创建测试所需的数据如一个测试用户、一个订单。这是最快、最干净的方式。测试结束时再通过API删除。你可以封装一个简单的测试数据管理客户端。策略二使用数据库事务或回滚如果测试环境允许可以让测试在一个数据库事务中运行测试结束后无论成功与否都回滚事务。这需要后端服务的特殊支持但能做到完全的数据隔离。策略三使用唯一标识符创建数据时使用唯一标识符如时间戳、UUID来命名这样即使数据残留也不会影响其他测试。test(创建唯一订单, async ({ page }) { const uniqueId Date.now(); const orderName 测试订单_${uniqueId}; // ... 使用orderName进行操作和断言 });4.4 CI/CD集成与性能调优在持续集成环境中运行Playwright测试需要考虑稳定性和速度。1. 使用官方Docker镜像Playwright提供了官方的Docker镜像mcr.microsoft.com/playwright里面已经包含了所有依赖和浏览器。这是CI环境的最佳选择能保证环境绝对一致。2. 并行与分片执行对于大型测试套件利用Playwright Test的并行能力。# 使用所有CPU核心并行运行测试 npx playwright test --workersmax # 将测试套件分片在多台机器上并行运行适用于大规模CI npx playwright test --shard1/3 # 第一份 npx playwright test --shard2/3 # 第二份 npx playwright test --shard3/3 # 第三份3. 视频与追踪在CI中为失败的测试自动保留视频和追踪文件是调试的救命稻草。在配置中开启use: { trace: retain-on-failure, // 失败时保留追踪文件 video: retain-on-failure, // 失败时保留视频 },追踪文件可以用npx playwright show-trace trace-file命令在本地打开一个强大的可视化调试工具重现测试的每一步。4. 超时与重试策略在CI环境中网络或资源的不稳定可能导致偶发失败。合理的重试策略能提升流水线的稳定性。在playwright.config.ts中设置retries: 1或2。但要注意重试会掩盖真正的稳定性问题它应该是最后的手段而不是替代对稳定测试用例的追求。5. 与生态系统的整合及未来展望Playwright不仅仅是一个浏览器自动化库它正在成长为一个完整的测试生态系统。它与流行的开发工具链无缝集成。与Pytest集成Python如果你所在的Python技术栈使用Pytest可以安装pytest-playwright插件在Pytest的灵活框架内使用Playwright的强大功能利用Pytest的fixture系统来管理浏览器和页面。与Visual Studio Code深度集成官方提供了VS Code扩展“Playwright Test for VSCode”。它可以在编辑器侧边栏直接显示测试列表一键运行或调试单个测试并在代码行内显示失败断言的具体原因开发体验极佳。录制与代码生成使用npx playwright codegen https://your-site.com命令会打开一个浏览器和一个代码生成器。你在浏览器中的所有操作都会被实时转换成Playwright代码。这是快速创建测试脚本原型的绝佳工具尤其适合为不熟悉API的团队成员演示或者快速测试一个复杂流程。但生成的代码通常需要重构以符合POM模式。关于“AI赋能”的思考最近社区出现了很多关于用AI如Claude、GPT辅助编写或理解Playwright测试的讨论。我的经验是AI非常适合处理那些模式固定但繁琐的任务比如生成基础定位器描述“一个在表单底部叫提交的蓝色按钮”AI能给出getByRole(‘button’, { name: ‘提交’ })。解释错误信息将一段复杂的测试失败堆栈扔给AI它能帮你快速定位可能的原因。生成测试数据描述“一个有效的美国地址”AI能生成结构化的假数据。但AI目前还无法理解你应用的特定业务逻辑和状态转换。测试用例的设计、关键断言的选择、页面对象的抽象这些核心工作仍然需要测试工程师的思考和经验。将AI视为一个强大的“副驾驶”它能提高效率但方向盘必须在你手里。Playwright的社区活跃版本迭代迅速。它正在从“最好的Web自动化工具”向“端到端测试的默认平台”演进。对于任何面临现代Web应用测试挑战的团队现在投入时间学习和采用Playwright无疑是一项高回报的技术投资。它解决的不仅仅是技术问题更是提升了整个团队对产品质量的信心和交付速度。开始可能会遇到一些概念转换的阵痛特别是从Selenium转过来但一旦你习惯了它的“等待一切就绪”的思维模式就很难再回去了。

相关新闻

霞鹜文楷:如何用一款开源字体解决中文排版三大痛点?

霞鹜文楷:如何用一款开源字体解决中文排版三大痛点?

霞鹜文楷:如何用一款开源字体解决中文排版三大痛点? 【免费下载链接】LxgwWenKai An unprofessional open-source Chinese font derived from Fontworks Klee One. 一款非专业的开源中文字体,基于 FONTWORKS 出品字体 Klee One 衍生。 项目…

2026/6/30 20:21:18阅读更多 →
GELU激活函数原理与工程实践:从数值稳定性到多框架部署

GELU激活函数原理与工程实践:从数值稳定性到多框架部署

1. 项目概述:为什么GELU不是“又一个激活函数”,而是Transformer时代的关键基建GELU,全称Gaussian Error Linear Unit,表面看只是Python、TensorFlow、Torch里几行代码实现的激活函数,但如果你真把它当成ReLU的平替来用…

2026/6/30 20:21:18阅读更多 →
GELU激活函数原理与工程实践:从Transformer稳定训练到框架实现

GELU激活函数原理与工程实践:从Transformer稳定训练到框架实现

1. 项目概述:为什么GELU不是“又一个激活函数”,而是Transformer时代的关键基建你打开任何一篇关于BERT、GPT或LLaMA的源码,翻到模型定义部分,几乎必然在nn.Linear之后、nn.Dropout之前看到那一行不起眼却无处不在的nn.GELU()——…

2026/6/30 20:21:18阅读更多 →
从零玩转Metasploit Framework:渗透测试核心平台实战指南

从零玩转Metasploit Framework:渗透测试核心平台实战指南

1. 项目概述:为什么你需要掌握MSF?如果你对网络安全、渗透测试或者“红队”工作感兴趣,那么Metasploit Framework (MSF) 这个名字你一定不陌生。它不是一个单一的工具,而是一个庞大、成熟且功能极其丰富的渗透测试平台。很多人&am…

2026/6/30 21:31:26阅读更多 →
前后端分离旅游出行指南_ms ()abo系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程

前后端分离旅游出行指南_ms ()abo系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程

博主介绍: 🌟 个人简介 CSDN特邀作者 | 掘金优质创作者,深耕Java生态与现代Web开发技术栈。专业领域涵盖Java企业级开发、Spring Boot微服务架构、前后端分离解决方案,以及学术项目的工程化实践。 📊 影响力数据 全平台…

2026/6/30 21:31:26阅读更多 →
如何快速掌握fullPage.js:构建沉浸式全屏滚动网站的终极指南

如何快速掌握fullPage.js:构建沉浸式全屏滚动网站的终极指南

如何快速掌握fullPage.js:构建沉浸式全屏滚动网站的终极指南 【免费下载链接】fullPage.js fullPage plugin by Alvaro Trigo. Create full screen pages fast and simple 项目地址: https://gitcode.com/gh_mirrors/fu/fullPage.js 想要创建令人惊叹的全屏滚…

2026/6/30 21:31:26阅读更多 →
基于代理模式的服务发现与治理:Agency-Agents实战指南

基于代理模式的服务发现与治理:Agency-Agents实战指南

在分布式系统和微服务架构中,服务发现与治理是保障系统稳定性和可扩展性的基石。然而,随着服务数量的增长,传统的静态配置或中心化注册中心方案在动态扩缩容、多环境隔离和故障自愈等方面面临挑战。agency-agents项目提供了一种基于智能代理&…

2026/6/30 21:31:26阅读更多 →
终极指南:5分钟掌握FanControl Windows风扇控制软件的完整设置方案

终极指南:5分钟掌握FanControl Windows风扇控制软件的完整设置方案

终极指南:5分钟掌握FanControl Windows风扇控制软件的完整设置方案 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub…

2026/6/30 21:31:26阅读更多 →
医学图像分割中的域泛化挑战与SRC技术解析

医学图像分割中的域泛化挑战与SRC技术解析

1. 医学图像分割中的域泛化挑战医学图像分割是计算机辅助诊断系统的核心技术之一,其目标是从CT、MR等医学影像中精确划分出特定解剖结构或病变区域。然而在实际临床应用中,一个长期存在的核心难题是:在单一模态(如CT)上…

2026/6/30 21:26:25阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/6/30 4:03:30阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/6/30 4:36:27阅读更多 →
为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南 【免费下载链接】Destiny-2-Solo-Enabler Repo containing the C# and XAML code for the D2SE program. Included is also the dependency for the program, and image asset. 项目地址: https://gitcode…

2026/6/30 0:02:58阅读更多 →
第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

1. PowerPoint 2010基础操作全攻略 刚接触PowerPoint 2010时,很多人会被它复杂的界面吓到。其实只要掌握几个核心区域,就能快速上手。我最开始用PPT时,经常找不到功能按钮在哪,后来发现主要操作都集中在顶部功能区。 工作窗口主要…

2026/6/30 0:02:58阅读更多 →
XGBoost超参数实战:从理论到调优策略

XGBoost超参数实战:从理论到调优策略

1. XGBoost超参数基础认知 第一次接触XGBoost时,我被它那密密麻麻的参数列表吓到了。这感觉就像面对一架波音747的驾驶舱——每个按钮都可能有神奇的效果,但按错了就可能坠机。经过多年实战,我发现其实掌握十几个核心参数就能解决90%的问题。…

2026/6/30 0:02:59阅读更多 →