阅读视图

发现新文章,点击刷新页面。

Playwright使用体验

Playwright 安装

在项目中执行yarn create playwright即可。该命令会在package.json中添加依赖,并生成以下文件和目录。

playwright.config.ts  
tests/  
    example.spec.ts  
tests-examples/  
    demo-todo-app.spec.ts

Playwright 配置

playwright.config.ts是Playwrigth的配置文件,生成文件基本不用改动即可运行。

import { defineConfig, devices } from '@playwright/test';

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: './tests',
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://local.test.com:3001',
    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',
  },
  /* Configure projects for major browsers */
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
        viewport: { width: 1920, height: 1080 },
      },
      dependencies: ['setup'],
    },

    // {
    //   name: 'firefox',
    //   use: { ...devices['Desktop Firefox'] },
    // },

    // {
    //   name: 'webkit',
    //   use: { ...devices['Desktop Safari'] },
    // },

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },
  ],
  timeout: 5 * 60 * 1000,
  /* Run your local dev server before starting the tests */
  webServer: {
    command: 'yarn dev',
    url: 'http://local.aaa.com:3001',
    reuseExistingServer: !process.env.CI,
  },
});

不过在实践中,有几项建议修改:

  1. baseURL,设置后,使用 page.goto不需要再指定完整路径

  2. timeout,不知道是否是网络原因,Playwright打开网页总是很慢,测试用例很容易跑超时,所以我将timeout改成了5 * 60 * 1000

  3. webServer 本地测试时强烈建议开启该功能

      webServer: {
        command: 'yarn dev',
        url: 'http://local.test.com:3001',
        reuseExistingServer: !process.env.CI,
      },
    

    运行测试时,Playwright将先运行yarn dev启动服务,并等待local.test.com:3001 可访问时再开始测试。这个配置非常好用,相对比cypress就需要额外安装start-server-and-test

  4. 用户登录相关,见下一节。

Playwright缓存用户登录信息

一般运行测试前需要先进行登录,Playwright可以缓存用户信息,在之后多次测试中复用,配置也很简单。

创建auth文件夹

按照官网建议,直接在项目中创建playwright.auth文件夹

编写登录程序

在tests目录中新增auth.setup.ts,内容可参考如下

import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  // Perform authentication steps. Replace these actions with your own.
  await page.goto('https://xxx.xxx.com/login/');
  await page.locator('#account').fill('xxx');
  await page.locator('#password').fill('xxx');
  await page.locator('button[type=submit]').click();
  // Wait until the page receives the cookies.
  //
  // Sometimes login flow sets cookies in the process of several redirects.
  // Wait for the final URL to ensure that the cookies are actually set.
  await page.waitForURL('https://xxx.xxx.com/');

  // Alternatively, you can wait until the page reaches a state where all cookies are set.
  await expect(page.locator('#title')).toBeVisible();

  // End of authentication steps.

  await page.context().storageState({ path: authFile });
});

这段代码先访问统一登录地址(测试网页域名可能和登录地址不一样),模拟输入用户名/密码,点击提交。登录成功后,page.context().storageState({ path: authFile })将cookie/storage信息存入authFile文件。

修改配置文件

修改配置文件中的projects配置

  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
        viewport: { width: 1920, height: 1080 },
      },
      dependencies: ['setup'],
    },
    ...
]

Playwright 运行

直接运行命令yarn playwright test --ui,会出现下面弹窗

image.png

点击example.spec.ts旁边的开始键就可以运行测试用例了,可以看到auth.setup.ts中的登录程序也被自动运行了。 点击每个用例可以显示更多运行信息。

image.png

Playwright 简单小结

Playwright上手非常简单,但是ui mode比较简陋,不适合边测试边debug。

SSE与流式传输(Streamable HTTP)

SSE被冷落了十多年,终于随AI火了一把。过去大家宁可用websocket也不愿意使用SSE,以至于AI出来后,很多人认为这是个新技术。实际上它很久以前就是HTML5标准中的一部分了。

MCP兴起后,有些人认为SSE与Streamable HTTP是两个概念,其实不然。本文将理清SSE和Streamalbe HTTP两者的概念与关系,并给出实践中的一些小建议。

SSE

SSE是Streamable HTTP的一个实现:SSE不仅对请求头有要求——必须设置Content-Type: text/event-stream,而且对传输格式、数据解析做了详细约定:

image.png

除此之外还有自动重连机制,具体可见HTML5标准 html.spec.whatwg.org/multipage/s…

除了这些具体的规范外,SSE只能发送get请求,并且只能由客户端主动关闭。另外,从"text/event-stream"上可以看出,SSE聚焦于文本内容传输,要传二进制内容就比较麻烦。

总的来说,SSE是由HTML5标准规定,针对前端场景特殊规定的流式传输协议。基于SSE的流式传输,可以通过EventSource对象实现,也可以通过fetch自行处理请求/解析/重连。

Streamable HTTP

Streamable HTTP虽然与SSE一样依赖http协议中的keep-alive,但更底层和中立。

它的核心是Transfer-Encoding: chunked(http1.1),此外没有其他约束。

如果使用http2,可以不声明Transfer-Encoding,只要持续写就行了,因为http2能自动分帧。

当服务器返回的响应中缺少Content-Length头部,且连接保持开放(Connection: keep-alive)时,HTTP/1.1 协议会默认使用Transfer-Encoding: chunked编码来传输响应数据,SSE刚好满足这两个条件,因此也是chunked transfer传输的。

从这个角度来说,SSE就是Streamable HTTP传输的一个实现——SSE = Streamable HTTP + 事件编码规范

由于Streamable HTTP并没有规定数据格式和解析方法,因此使用者可以根据场景自行协商:

SSE传输:
data: {"token":"Hello"}
data: {"token":"world"}
data: [DONE]

Streamable HTTP传输:
{"type":"token","content":"Hello"}
{"type":"token","content":"world"}
{"type":"done"}

从内容上可以看出,SSE必须解析data:开头,而Streamable可传输json string line等多种格式。

为什么MCP更青睐Streamable HTTP

原因 说明
🌐 跨语言兼容 SSE 原生仅限浏览器;Streamable HTTP 适配 SDK、CLI、服务端
🧱 结构灵活 支持 NDJSON、JSON Lines、Protocol Buffers
⚙️ 更贴近底层 I/O 方便控制 chunk 大小、流速、关闭机制
🧩 多类型输出 AI 不止发文本,还要发图像、语音、函数调用等
📦 工具链统一 与现代 fetch/Response API 一致

对ai应用来说,SSE过于死板。它规定了传输格式,编码方式,无法以二进制传输。在非浏览器环境中,使用更原始的Streamable HTTP显然更合适。

流式传输的实践建议

  1. 如果没有二进制传输需求,可以使用SSE协议,服务端第三方开源工具也较多
  2. 浏览器端建议使用fetch而不是EventSource,便于传参和认证
  3. 浏览器端使用AbortController取消流式传输
  4. 服务端根据请求头的 Accept: 'text/event-stream' 判断是否以SSE格式传输(如果需要同时支持流式传输和普通分页传输)
❌