在探索生成式 AI (GenAI) 创新应用的过程中,我偶然发现了一个名为 Stagehand 的自动化框架。它利用 大模型 (LLM) 的强大能力,以自然语言为驱动,无缝地与 Web 用户界面进行交互。这个概念立刻吸引了我,迫不及待地想深入了解。本文将带你了解 Stagehand 的安装过程、第一印象,以及使用过程中遇到的“期望 vs. 现实”时刻。

Stagehand 是 Browserbase 开发的一款强大的高级自动化测试工具。它旨在帮助开发人员和测试人员构建稳定、基于浏览器的 workflow 和测试用例,摆脱 flaky 测试设置或复杂配置的困扰。传统自动化测试常常面临元素定位困难、环境依赖性强等问题,导致测试结果不稳定,维护成本高昂。而 Stagehand 另辟蹊径,通过大模型的理解和执行能力,尝试解决这些痛点。

Stagehand 的核心价值:给测试套件赋予 “手”、“眼” 和 “大脑”

想象一下,你的测试套件拥有了一双“手”和“眼睛”,以及一个“大脑”(大模型),它能够完全通过自然语言来导航和与网页交互。这就是 Stagehand 为你的自动化测试套件带来的力量:将智能、类人的自动化叠加在传统的测试流程之上。 换句话说, Stagehand 将复杂的测试脚本简化为更易于理解和维护的自然语言指令,极大地降低了自动化测试的门槛。

核心功能:page.act()page.extract()page.observe()

Stagehand 的核心是三个基本功能,也就是它的 “三剑客”:page.act()page.extract()page.observe()。这些功能使其能够与网页进行交互、理解网页内容并验证网页行为。

  • page.act(): 让 Stagehand 与网页交互

    act() 想象成赋予 Agent “双手”,它解释自然语言指令并执行相应的操作 (例如,点击、键入)。

    例如:await page.act(“click on ‘add to cart’ icon”)

    你还可以屏蔽变量,以避免将它们传递给 LLM,正如文档中提到的。这在处理敏感数据或需要精确控制输入内容时非常有用。

  • page.observe(): 帮助 Stagehand 检查当前页面并识别可能的动作

    observe() 特别适用于检查元素是否已加载。

    一个简单的 observe (没有参数) 返回 LLM 基于当前窗口上下文的建议操作,包括一个 XPath 选择器和一个简短的描述。

    //Response from the page.observe()
    {
        "description": "A brief description of the component",
        "method": 'click',
        "arguments": [],
        "selector": 'xpath=/html/body[1]/div[1]/main[1]/button[1]'
    }
    

    observe() 的实际应用:

    const [action] = await page.observe(
        "Type 'Tell me in one sentence why I should use Stagehand' into the search box",
    );
    await drawObserveOverlay(page, [action]); // Highlight the search box
    await page.waitForTimeout(1_000);
    await clearOverlays(page); // Remove the highlight before typing
    await page.act(action); // Take the action
    

    这段代码首先使用 page.observe() 找到搜索框并建议输入内容。然后,它高亮显示搜索框,短暂等待后清除高亮,最后使用 page.act() 执行输入操作。

  • page.extract(): 使用 Zod 模式从页面提取结构化数据

    你定义要提取的内容和你期望的格式,Stagehand 将其作为结构化输出返回。

    const item = await page.extract({
        instruction: "extract the price of the item",
        schema: z.object({
            price: z.number(),
        }),
    });
    

    这对于断言或条件流程以验证应用程序是否按预期运行非常有用。例如,你可以提取商品价格并与预期价格进行比较,从而验证价格是否正确显示。

从使用角度来看,Stagehand 充当一个 Orchestrator (编排器),管理 Playwright 脚本的执行,同时利用 LLM 来确定如何与网页交互并推动流程前进。简而言之,Stagehand 提供了一个更高级的抽象层,让测试人员能够专注于测试逻辑,而无需过多关注底层实现细节。

快速上手: Stagehand 安装与配置

要开始使用 Stagehand,可以按照官方的快速入门指南进行安装。首先,你需要使用以下命令创建一个新的 Browserbase 应用:

$ npx create-browser-app

在安装过程中,它会要求你提供一些输入,例如项目名称和选择使用的 大模型。安装完成后,导航到你的项目目录并按照以下步骤开始使用 Stagehand。

  1. 在项目根目录下创建一个 .env 文件,并添加你的 LLM API 密钥,然后安装 Node 模块。

    GOOGLE_API_KEY="<YOUR GEMINI API KEY>" //If you are using Gemini Model
    OPENAI_API_KEY="<YOUR OPENAI API KEY>" //If you are using Open AI Model
    
    $ npm install
    
  2. 执行以下命令触发流程:

    npm run start
    

    这将在浏览器中打开一个网页并执行开箱即用的测试用例。

Stagehand 的内部机制: LLM 与 WebPage Data 的协同

由于 Stagehand 利用了 LLM 的能力,因此内部会将提供的自然语言连同 WebPage 数据(他们称之为可访问性树数据)一起发送给 LLM,以识别需要执行的操作。通过分析 WebPage 的结构和内容,LLM 可以理解用户意图,并生成相应的操作指令。

在执行测试用例时,终端输出会显示 Stagehand 内部执行的一系列事件,这有助于理解 Stagehand 的工作原理。

Stagehand 集成到测试框架 (例如,Jest)

为了更好地理解 Stagehand 的实际应用,我将其集成到了一个测试框架中。

  1. 首先,使用以下命令安装 Jest 和相关模块 (针对 TypeScript):

    $ npm install --save-dev jest ts-jest @types/jest typescript
    
  2. 在根目录下创建一个名为 tests 的新文件夹,并添加你的第一个测试文件。添加一个基本测试,如下所示:

    import { describe, test, expect } from '@jest/globals';
    
    function add(a: number, b: number): number {
        return a + b;
    }
    
    describe('basic test using stagehand', () => {
        test('adds two numbers correctly', () => {
            expect(add(2, 3)).toBe(5);
        });
    });
    
  3. 创建一个 jest.config.ts 文件并添加以下内容:

    import type { Config } from 'jest';
    
    const config: Config = {
        preset: 'ts-jest',
        testEnvironment: 'node',
        testMatch: ['**/__tests__/**/*.test.ts'], //make sure to add the test folder created above here
        moduleFileExtensions: ['ts', 'js', 'json'],
        verbose: true,
    };
    
    export default config;
    
  4. 更新你的 package.json 文件,添加以下 npm 命令:

    package.json > "scripts": {
      ... //add below
      "test": "jest",
      "test:watch": "jest --watch"
      ...
    }
    
  5. 更新 tsconfig.json 文件,添加以下内容以避免任何问题:

    {
      "compilerOptions" : {
        ...
        "allowImportingTsExtensions": true, //use this value
        "noEmit": true, //use this value
        ...
      }
    }
    
  6. 执行基本测试:

    $ npm test
    

    如果遇到 ESM 与 CommonJS 问题,可以使用 Copilot 或 ChatGPT 等工具进行调试。

  7. 编写并执行一个使用 Stagehand 驱动的测试用例。

    在设置好测试框架后,我们将 Stagehand 的核心功能分解为三个部分:

    • 初始化 — 在 beforeAll hook 中处理
    • 操作 — 直接在测试用例中执行
    • 清理 — 在 afterAll hook 中管理

    我们将使用 Stagehand 的默认初始化作为参考,并在 beforeAll hook 中进行设置。

    tests 文件夹中创建一个名为 roombooking.test.ts 的新文件,并将以下代码粘贴到其中:

    import { describe, test, expect } from '@jest/globals';
    import { drawObserveOverlay, clearOverlays } from "../utils.ts";
    import { z } from "zod";
    import { Stagehand, Page, BrowserContext } from "@browserbasehq/stagehand";
    import StagehandConfig from "../stagehand.config.ts";
    
    describe('Automation Test Suite Basic Tests', () => {
        let stagehand: any;
        let page: any;
        let context: any;
    beforeAll(async () =&gt; {
        console.log('Setting up the stuff...');
        stagehand = new Stagehand({
            ...StagehandConfig,
        });
        await stagehand.init();
        page = stagehand.page;
        context = stagehand.context;
    });
    
    afterAll(async () =&gt; {
        // Cleanup after all tests
        console.log('🧹 Cleaning up the stuff..');
        await stagehand.close();
    });
    
    test(
        'Open the website, book the suite for the dates provided, book the available suite and verify the booking confirmation',
        async () =&gt; {
            try {
                console.log('🔍 Running test :');
                await page.setViewportSize({ width: 1280, height: 800 }); //for a full screen view
                await page.goto(`https://automationintesting.online/`);
                await page.waitForTimeout(3000);
                await page.act("scroll 10% down to view the 'Check In' and 'Check Out' fields under the 'Check Availability' section");
                await page.act("click on the 'Check In' field and select the date '18/06/2025'");
                await page.act('click enter on the "Check In" field');
                await page.act("click on the 'Check Out' field and select the date '19/06/2025'");
                await page.act('click enter on the "Check Out" field');
                await page.act("click on the button 'Check Availability'");
                await page.act("scroll down to the next chunk");
    
                const availableOptions = await page.extract({
                    instruction: "extract the Heading text from each Room card as an array",
                    schema: z.object({
                        rooms: z.array(z.string()),
                    }),
                });
                console.log("🚀 ~ availableOptions:", availableOptions)
                expect(availableOptions.rooms).toContain('Suite');
                await page.waitForTimeout(2000);
                await page.act("click on the 'Book Now' button of the room with title 'Suite'");
                await page.act("click on the 'Reserve Now' button");
                await page.act("Provide input 'John Doe' in the 'Firstname' field");
                await page.act("Provide input 'Doe' in the 'Lastname' field");
                await page.act("Provide input 'john.doe@example.com' in the 'Email' field");
                await page.act("Provide input '+11234567890' in the 'Phone' field");
                await page.waitForTimeout(3000);
    
                const [action2] = await page.observe(
                    "Identify the button with text 'Reserve Now' and click on it",
                );
                await drawObserveOverlay(page, [action2]);
                await clearOverlays(page);
                await page.act(action2);
    
                await page.waitForTimeout(2000);
                const item = await page.extract({
                    instruction: "extract the text from the card 'Booking Confirmed' on the screen",
                    schema: z.object({
                        bookingStatus: z.string(),
                        dates: z.string(),
                    }),
                });
    
                console.log("Extracted items :", item);
                expect(item.bookingStatus).toBeDefined();
                expect(item.bookingStatus).toContain('Your booking has been confirmed for the following dates:');
                expect(item.dates).toBeDefined();
                expect(item.dates).toContain('2025-06-18 - 2025-06-19');
                console.log("✅ Test passed: Booking confirmation verified successfully");
                await page.waitForTimeout(5000);
            } catch (error) {
                console.error("❌ Test failed:", error);
                throw error;
            }
        }, 100000);
    

    });

    上述测试用例将执行以下操作:

    • 访问 https://automationintesting.online/ 网页。
    • 输入入住和退房日期,并检查给定日期内是否有 Suite 房间可用。
    • 如果可用,则提供所有必需的信息并预订房间。否则,测试用例将失败。
    • 验证预订后是否显示预期的文本。

我的看法: 大模型 前提条件 & 有限的本地模型支持

我最初的期望是可以使用任何模型,但事实并非如此。Stagehand 最适合支持结构化输出的模型。目前,不建议使用本地模型(例如,Ollama)(尽管它们受到支持),这对于专注于隐私、性能和成本的团队来说可能是一个障碍。由于本地模型在隐私性和成本方面具有优势,因此对本地模型的支持是未来的发展方向之一。

我相信未来的进步将使 Stagehand 更容易与本地模型一起使用。

act 步骤中的静默失败

“act” 步骤中未捕获的错误不会停止执行,除非显式处理。这可能导致大量使用 try-catch 块,从而使脚本更难管理。但是,Stagehand 团队已经意识到了这一点,并且正在努力改进。因此,在编写 Stagehand 测试用例时,需要格外注意错误处理,以确保测试的可靠性。

高度响应的团队

由于 Stagehand 仍处于发展初期,因此团队制定了一些使用它的“推荐方式”。也就是说,Stagehand 团队在 Slack 上能够快速响应,通常会提供切实可行的解决方案。这种积极的支持增强了用户的信心。

尚未完全替代传统的测试套件

Stagehand 对于 AI 驱动的测试自动化很有用,但尚未准备好替代传统的、生产级的测试套件,尤其是在关键系统方面,因为它们需要一致性,而 大模型 有时可能会带来挑战(至少目前是这样)。由于 大模型 的推理过程存在一定的不确定性,因此 Stagehand 在需要高度确定性的场景下可能表现不佳。

准确性取决于模型和步骤表述

该工具在测试步骤明确表述时效果最佳。但是,这引入了波动性,因为解释可能因人而异(这可以通过标准化整个项目中指令的编写方式来缓解)。准确性还取决于模型如何解释每个步骤,这可能导致偶尔的不一致。因此,在编写 Stagehand 测试用例时,需要尽可能清晰和简洁地描述测试步骤。

Stagehand 的未来:走向智能自动化

  1. 亲身体验 Stagehand Agent

    我们可以尝试将 Stagehand Agent 与 LangChain 结合使用。它承诺提供更结构化和流畅的自动化体验。“软件一直都是确定性和可重复性的,但使用 AI Agent 时,很难复制一个 workflow。Stagehand 结合了两者最好的部分:智能和确定性。”——Stagehand

  2. 将 Stagehand 与 Browserbase 集成

    我们可以探索 Browserbase(Stagehand 背后的平台)以了解它如何增强浏览器自动化和高级 workflow。

  3. 使用 Stagehand 进行 Web Scraping

    我们可以测试 Stagehand 的结构化数据提取能力,作为传统 Web Scraping 工具的一种更轻便、更直观的替代方案。

总而言之,Stagehand 通过 大模型 技术为自动化测试带来了新的可能性。它简化了测试脚本的编写,降低了测试门槛,并有望解决传统测试中的 flaky 问题。虽然目前还存在一些局限性,但随着 大模型 技术的不断发展和 Stagehand 自身的不断完善,它有望成为下一代自动化测试的重要组成部分。