
1. 从一次登录态丢失说起Playwright TypeScript 调试与 Cookie 复用到底解决什么问题如果你正在用 Playwright TypeScript 写端到端测试大概率遇到过这种场景本地跑得好好的用例一上 CI 就红或者每个用例开头都要老老实实走一遍登录跑 30 条用例就登录 30 次慢得让人想砸键盘。更崩溃的是某条用例失败后你盯着报错信息发呆完全不知道页面当时长什么样、点击到底有没有生效。这篇就围绕两个最痛的点展开Playwright 运行中怎么 debug以及Cookie 怎么注入、保存和复用把登录态保持和失败重跑这两件事讲透。先说清楚这套东西适合谁。如果你是会写一点 TypeScript 的前端想给项目补上自动化测试或者你已经在用 Playwright但调试全靠console.log和截图那这篇就是给你准备的。Playwright 是微软开源的浏览器自动化框架支持 Chromium、Firefox、WebKit 三大内核TypeScript 是它的第一等公民类型提示非常完整。它能做的事包括模拟真实用户点击输入、断言页面状态、拦截网络请求、录制 trace 回放以及我们今天重点讲的 Cookie 上下文管理。为什么调试和 Cookie 要放在一起讲因为登录态本身就是调试里最容易出问题的一环。你断点停下来的时候如果当前 page 没有带上正确的 Cookie看到的页面就是未登录状态断点里查到的 DOM 全是错的越调越迷糊。反过来Cookie 复用做得好用例启动就能直接进到登录后的页面调试时你面对的就是真实业务场景。所以这两块是咬合在一起的。我试过的坑是这样早期每个 spec 文件里都写一遍登录流程代码重复不说一旦登录页改了个 placeholder几十个文件全要改。后来改成auth.setup.ts统一登录、存storageState再用dependencies串起来才算清爽。下面从环境准备开始一步步把配置、调试命令、Cookie 读写代码全部给全你照着复制就能跑。2. 前置准备TaoToken 接入与 Playwright TypeScript 工程初始化在动手写测试之前先把两件事搞定一个是模型能力的接入后面写用例、排查报错时用得上一个是 Playwright 工程本身。这里先讲接入因为很多同学卡在想用 AI 帮忙分析 trace 或生成用例但不知道怎么配。TaoToken 是一个聚合式的模型调用入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的作用是让你用一个统一的 Base URL 和 Key去调用不同厂商的模型省得每个平台单独注册、单独管额度。对做自动化测试的人来说典型用法是把 Playwright 跑失败时的报错、trace 摘要丢给模型让它帮你判断是选择器写错了还是等待时机不对。接入的核心三件套是Base URL API Key Model ID。Base URL 填https://taotoken.net/apiKey 在控制台生成Model ID 按你实际要用的模型填。如果你用的是 Claude Code 这类命令行工具配置方式是在 settings 里指定ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN如果用的是 Cline、Roo Code 这类编辑器插件就在 MCP 或 provider 配置里填上面三件套。Codex 的话对应的是auth.json里面写OPENAI_BASE_URL和OPENAI_API_KEY。拿 Key 的入口在这里https://taotoken.net/api-keys 接入文档在 https://taotoken.net/doc 。想先试试模型对话效果可以直接去 https://taotoken.net/models 玩一下。如果你打算长期用 AI 辅助写测试、跑 Agent 任务Coding Plan 会更划算地址是 https://taotoken.net/coding-plan 。Claude Code 用户看这个https://taotoken.net/claude-code-anthropic 。工程初始化部分假设你已经装了 Node 18。执行mkdir pw-ts-demo cd pw-ts-demo npm init -y npm i -D playwright/test typescript npx playwright installnpx playwright install会把三大内核的浏览器都下下来第一次会慢一点。然后建一个tsconfig.json让 TypeScript 认识 Playwright 的类型{ compilerOptions: { target: ES2020, module: commonjs, strict: true, esModuleInterop: true, skipLibCheck: true, types: [node, playwright/test] } }目录结构建议这样组织后面所有代码都按这个来pw-ts-demo/ ├── tests/ │ ├── auth.setup.ts │ └── demo.spec.ts ├── auth/ # 存放 storageState记得加进 .gitignore ├── playwright.config.ts └── tsconfig.jsonauth/目录一定要写进.gitignore因为里面存的是登录凭证提交上去等于把账号送人。这一点很多人第一次做会忽略等 CI 上跑出诡异结果才反应过来。3. 可复制配置playwright.config.ts 里 storageState、trace 与 setup 项目怎么配这一节是全文的核心配置直接给可复制的完整文件。先理解三个关键点storageState决定每个用例启动时带不带 Cookietrace决定失败时能不能回放现场projects里的setup项目决定登录流程什么时候跑、跑几次。先写登录脚本tests/auth.setup.ts。它的职责是打开登录页、填账号密码、点登录、等跳转完成然后把当前上下文的 Cookie 存成 JSONimport { test as setup, expect } from playwright/test const adminFile auth/login.json setup(authenticate as login, async ({ page }) { await page.goto(http://your-app.com/login) // 按 placeholder 定位输入框比 CSS 选择器更稳 await page.getByPlaceholder(账号).fill(process.env.APP_USER!) await page.getByPlaceholder(密码).fill(process.env.APP_PASS!) // 按 role 文本定位按钮语义清晰 await page.getByRole(button, { name: 登录 }).click() // 关键等最终 URL确保 setCookie 已经完成 await page.waitForURL(http://your-app.com/) // 把上下文里的 Cookie 落盘 await page.context().storageState({ path: adminFile }) })这里有个细节值得展开为什么用waitForURL而不是waitForTimeout因为登录流程经常伴随多次重定向后端setCookie可能发生在中间某一跳。如果你用固定等待网络慢的时候 Cookie 还没写进去就存了盘后面用例全挂。waitForURL会等到 URL 真正变成登录后的地址此时 Cookie 基本已经就位。更严谨的写法是再断言一个只有登录后才可见的元素比如用户头像await expect(page.getByRole(button, { name: 个人中心 })).toBeVisible()账号密码不要硬编码用环境变量。本地建.envCI 里配 secrets。Playwright 本身不读.env需要装dotenv并在 config 顶部require(dotenv).config()。接下来是playwright.config.ts完整版import { defineConfig, devices } from playwright/test require(dotenv).config() export default defineConfig({ testDir: ./tests, fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [[html], [list]], use: { baseURL: http://your-app.com, storageState: auth/login.json, trace: on-first-retry, screenshot: only-on-failure, video: retain-on-failure, }, projects: [ { name: setup, testMatch: /.*\.setup\.ts/ }, { name: chromium, use: { ...devices[Desktop Chrome] }, dependencies: [setup], }, { name: firefox, use: { ...devices[Desktop Firefox] }, dependencies: [setup], }, ], })逐项解释。storageState: auth/login.json放在顶层use里意味着所有项目默认都带这份 Cookie。trace: on-first-retry表示第一次重试时才录 trace既省性能又能抓到失败现场。screenshot和video都设成失败才留避免产物爆炸。dependencies: [setup]是精髓跑 chromium 之前先跑 setupsetup 生成login.jsonchromium 再读它。这样登录只发生一次所有用例共享。如果你想让某个项目不带登录态比如专门测登录页本身可以在那个 project 的use里覆盖成storageState: { cookies: [], origins: [] }把默认值清掉。这个技巧在测登出、测权限边界时很有用。配置写完后跑一次 setup 验证npx playwright test --projectsetup成功的话auth/login.json会生成打开能看到cookies数组和origins里的 localStorage。如果文件是空的或者没有 cookies八成是waitForURL的地址写错了或者登录接口根本没返回 Set-Cookie。4. 运行中 debug 与 Cookie 读写断点、trace 查看器、注入与复用全流程配置就绪进入实操。先说 debug 的三种姿势再说 Cookie 的读写。姿势一VS Code 断点。装 Playwright 官方插件后在代码行号左侧点一下出现红点然后在测试用例上右键选 Debug Test。执行到断点会停住左侧面板能看到当前作用域的变量、page对象、调用栈。这时候你可以在调试控制台里直接敲await page.title()看当前页面标题非常直观。姿势二代码里写debugger。在想要停的地方插入debugger语句然后用PWDEBUG1 npx playwright test启动。浏览器会以 headed 模式打开并弹出 Playwright Inspector你可以单步执行、查看选择器、甚至实时录制新操作。姿势三命令行--debug。直接npx playwright test tests/demo.spec.ts --debug同样会打开 Inspector。如果只想看某一条用例加-g 用例名过滤。调试时最怕的就是断点停下来发现页面是未登录的。这就是 Cookie 没挂上的典型症状。解决办法有两个一是确认 config 里storageState路径对二是在调试时手动检查const cookies await page.context().cookies() console.log(JSON.stringify(cookies, null, 2))如果打印出来是空数组说明上下文没读到登录态。这时候检查auth/login.json是否存在、路径是否相对于项目根目录。Cookie 的注入。除了用storageState文件你也可以在代码里手动注入test(手动注入 cookie, async ({ browser }) { const context await browser.newContext() await context.addCookies([ { name: session_id, value: abc123, domain: your-app.com, path: /, httpOnly: true, secure: true, sameSite: Lax, }, ]) const page await context.newPage() await page.goto(/dashboard) })addCookies接收一个数组每个对象至少要有name、value、domain、path。httpOnly和secure要跟后端实际设置的一致否则浏览器可能不认。这个方式适合临时调试或者从接口拿到 token 后直接塞进去。Cookie 的复用。在用例里用browser.newContext({ storageState })创建带登录态的上下文import { test, expect } from playwright/test test(demo 复用登录态, async ({ browser }) { const context await browser.newContext({ storageState: auth/login.json, }) const page await context.newPage() await page.goto(/) await page.getByRole(button, { name: 创建 }).click() await expect(page).toHaveURL(/create/) await context.close() })注意这里用的是browser.newContext而不是直接用 fixture 的page。区别在于fixture 的page已经自动应用了 config 里的storageState你什么都不用做而手动newContext适合你需要在一个用例里开多个不同登录态的上下文比如同时测管理员和普通用户。读取和修改已有 Cookie。有时候需要在测试中途改 Cookie比如模拟会话过期const cookies await context.cookies() const target cookies.find(c c.name session_id) if (target) { await context.addCookies([{ ...target, value: expired }]) } await page.reload() await expect(page).toHaveURL(/login/)这段代码先读出所有 Cookie找到目标改值后重新注入再刷新页面就能验证会话失效后是否跳登录页。trace 查看器。失败重跑时 trace 会自动录。跑完后执行npx playwright show-trace test-results/xxx/trace.zip浏览器会打开一个时间轴界面左边是每个操作的快照右边是网络请求、console 日志、源码。你可以拖动时间轴看每一步的 DOM 快照定位到底是哪一步开始不对。这个工具比截图强太多强烈建议每次失败都看一眼。5. 常见报错排查401、local proxy failed、reading choices、OAuth 逐个击破跑起来之后报错是免不了的。这一节把几个高频错误和对应解法列清楚。401 Unauthorized。这个最常见含义是请求没带有效凭证。在 Playwright 场景里通常有两种来源一是被测应用的接口返回 401说明storageState里的 Cookie 过期或没挂上二是你调用模型 API 时 Key 不对。前者检查auth/login.json的生成时间重新跑 setup后者检查 Base URL 是不是https://taotoken.net/apiKey 有没有多余空格。如果用的是 Claude Code确认ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN都配对。local proxy failed。这个报错一般出现在网络层提示本地代理连接失败。先确认你的运行环境网络是通的能正常访问目标地址。如果是 CI 环境检查 runner 的出网策略。这个错误跟 Playwright 本身无关是环境问题把网络打通就好。reading choices。这是调用模型接口时解析响应体报的错典型原因是返回结构跟你预期的不一样。比如你以为返回data.choices[0].message.content实际返回的是错误对象{ error: {...} }。排查方法先把原始响应打印出来看const res await fetch(https://taotoken.net/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAOTOKEN_KEY}, }, body: JSON.stringify({ model: your-model-id, messages: [{ role: user, content: ping }], }), }) const text await res.text() console.log(res.status, text)先看 status 是不是 200再看 body 结构。如果是 401 就是 Key 问题如果是 404 就是路径或 Model ID 问题。确认无误后再去取choices。OAuth 相关报错。如果你用 Claude Code 或类似工具可能会遇到 OAuth token 失效的提示。这类工具通常支持两种认证API Key 和 OAuth。用 TaoToken 接入时走的是 API Key 模式配置里填ANTHROPIC_AUTH_TOKEN即可不需要走 OAuth 流程。如果工具提示要登录 OAuth检查是不是配置项名字写错了或者旧的环境变量还在生效。清掉旧的ANTHROPIC_API_KEY只留ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN。Cookie 相关报错。如果报storageState file not found说明 setup 没跑或者路径不对。确认dependencies: [setup]配了且adminFile路径跟 config 里的storageState一致。如果报 Cookie domain 不匹配检查addCookies里的domain是不是漏了前导点比如.your-app.com和your-app.com作用范围不同。选择器超时。报Timeout waiting for locator先别急着加等待时间。用--debug打开 Inspector看看页面当时到底长什么样。八成是选择器写得太具体或者元素在 iframe 里。Playwright 的getByRole、getByPlaceholder、getByText比 CSS 稳优先用这些。排查顺序建议先看 trace 回放确认现场再看 console 和 network最后才改代码。盲目改选择器只会越改越乱。6. 把调试和 Cookie 复用串成日常给前端同学的一套落地建议走到这里配置、调试、Cookie 三块都齐了。最后聊点实战心得帮你把这套东西真正用起来。第一setup 项目只做登录别塞别的。有人图省事把数据准备也放进去结果 setup 越来越重失败一次全盘皆输。数据准备应该用 fixture 或者单独的 API 调用跟登录解耦。第二storageState 要定期刷新。Cookie 有有效期CI 上如果缓存了旧的login.json跑着跑着就 401。稳妥做法是每次 CI 都重新跑 setup别缓存这个文件。本地开发可以缓存但记得手动删一次重跑。第三trace 是你的朋友但要会看。on-first-retry是性价比最高的设置本地调试可以临时改成on跑完记得改回来不然产物目录会很大。第四多登录态用多 context。需要同时验证管理员和普通用户时准备两份 storageState在用例里开两个 context互不干扰。这比在一个 context 里来回切账号干净得多。第五把 AI 用起来但别依赖。失败时把 trace 摘要和报错丢给模型让它给排查方向效率很高。但最终判断还得靠你自己看 trace。模型能帮你省时间不能替你理解业务。如果你还没配好模型接入回到第 2 节把 Base URL、Key、Model ID 三件套填上接入文档在 https://taotoken.net/doc 需要 Key 去 https://taotoken.net/api-keys 。想先验证模型能不能通用 https://taotoken.net/models 发一条消息试试。长期写测试、跑 Agent 的话Coding Plan 在 https://taotoken.net/coding-plan 。最后给一个可以直接抄的验证命令组合跑通它说明你的环境没问题npx playwright test --projectsetup npx playwright test --projectchromium npx playwright show-report第一条生成登录态第二条跑用例第三条打开 HTML 报告。报告里点进失败的用例能看到 trace、截图、视频。到这一步你的 Playwright TypeScript 自动化测试就算真正立起来了。