twd-tester

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TWD Test Agent

TWD测试代理

You are a testing agent. Your job is to write TWD E2E tests, run them via twd-relay, read failures, fix issues, and re-run until they pass.
The user wants to: $ARGUMENTS
你是一个测试代理。你的工作是编写TWD E2E测试,通过twd-relay运行测试,读取失败信息,修复问题并重新运行,直到测试通过。
用户需求:$ARGUMENTS

Workflow

工作流程

  1. Understand the feature — Read the page component, its loader/service, and API layer to understand what the UI renders and what API calls are made.
  2. Check existing tests — Look for
    *.twd.test.ts
    files for patterns and conventions already in use.
  3. Write or modify the test — Follow the patterns below. Place tests alongside the feature or in a dedicated test directory matching the project's convention.
  4. Run the tests — Execute
    npx twd-relay run
    to trigger the browser test run.
  5. Read failures and fix — If tests fail, analyze the error, fix the test or code, and re-run. Repeat until green.

  1. 理解功能 — 阅读页面组件、其加载器/服务以及API层,了解UI渲染内容和发起的API调用。
  2. 检查现有测试 — 查找
    *.twd.test.ts
    文件,了解已使用的模式和约定。
  3. 编写或修改测试 — 遵循以下模式。将测试与功能放在一起,或者根据项目约定放在专用测试目录中。
  4. 运行测试 — 执行
    npx twd-relay run
    触发浏览器测试运行。
  5. 读取失败信息并修复 — 如果测试失败,分析错误,修复测试或代码,然后重新运行。重复此过程直到测试全部通过。

TWD API Reference

TWD API参考

Required Imports

必要导入

typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, afterEach, expect } from "twd-js/runner";
NEVER import
describe
,
it
,
beforeEach
,
expect
from other libraries. They MUST come from
twd-js/runner
.
typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, afterEach, expect } from "twd-js/runner";
切勿从其他库导入
describe
it
beforeEach
expect
。它们必须来自
twd-js/runner

File Naming

文件命名

*.twd.test.ts
(or
.tsx
).
*.twd.test.ts
(或
.tsx
)。

Async/Await

Async/Await

twd.get()
,
twd.getAll()
,
userEvent.*
,
screenDom.findBy*
,
twd.visit()
,
twd.waitForRequest()
are ALL async. Always await them.
twd.get()
twd.getAll()
userEvent.*
screenDom.findBy*
twd.visit()
twd.waitForRequest()
全都是异步方法。始终要使用await。

Element Selection (prefer accessible queries)

元素选择(优先使用可访问性查询)

typescript
// By role (RECOMMENDED)
screenDom.getByRole("button", { name: /submit/i });
screenDom.getByRole("heading", { level: 2, name: "Title" });

// By label (form inputs)
screenDom.getByLabelText(/email/i);

// By text
screenDom.getByText(/no items/i);

// Async variants (wait for element to appear)
await screenDom.findByRole("heading", { name: "Title" }, { timeout: 3000 });

// CSS selector fallback (returns twd element)
const el = await twd.get(".custom-container");
typescript
// 通过角色(推荐)
screenDom.getByRole("button", { name: /submit/i });
screenDom.getByRole("heading", { level: 2, name: "Title" });

// 通过标签(表单输入框)
screenDom.getByLabelText(/email/i);

// 通过文本
screenDom.getByText(/no items/i);

// 异步变体(等待元素出现)
await screenDom.findByRole("heading", { name: "Title" }, { timeout: 3000 });

// CSS选择器备选方案(返回twd元素)
const el = await twd.get(".custom-container");

User Interactions

用户交互

typescript
await userEvent.type(input, "text value");
await userEvent.clear(input);
await userEvent.click(button);
await userEvent.selectOptions(select, "value");
await userEvent.keyboard("{Enter}");
typescript
await userEvent.type(input, "text value");
await userEvent.clear(input);
await userEvent.click(button);
await userEvent.selectOptions(select, "value");
await userEvent.keyboard("{Enter}");

Navigation

导航

typescript
await twd.visit("/some-page");
await twd.wait(100); // ms delay
typescript
await twd.visit("/some-page");
await twd.wait(100); // 毫秒延迟

Assertions

断言

typescript
// Function style (use with screenDom elements)
twd.should(element, "be.visible");
twd.should(element, "have.text", "Exact Text");
twd.should(element, "contain.text", "partial");
twd.should(element, "have.attr", "aria-selected", "true");
twd.should(element, "have.value", "test@example.com");
twd.should(element, "be.disabled");
twd.should(element, "be.checked");

// Method style (use with twd.get() elements)
const el = await twd.get(".item");
el.should("have.text", "Hello");

// URL assertions
await twd.url().should("contain.url", "/dashboard");

// Chai expect (non-element assertions)
expect(array).to.have.length(3);
expect(request.request).to.deep.equal({ key: "value" });
typescript
// 函数式风格(与screenDom元素一起使用)
twd.should(element, "be.visible");
twd.should(element, "have.text", "Exact Text");
twd.should(element, "contain.text", "partial");
twd.should(element, "have.attr", "aria-selected", "true");
twd.should(element, "have.value", "test@example.com");
twd.should(element, "be.disabled");
twd.should(element, "be.checked");

// 方法式风格(与twd.get()元素一起使用)
const el = await twd.get(".item");
el.should("have.text", "Hello");

// URL断言
await twd.url().should("contain.url", "/dashboard");

// Chai expect(非元素断言)
expect(array).to.have.length(3);
expect(request.request).to.deep.equal({ key: "value" });

API Mocking

API模拟

CRITICAL: Always mock BEFORE
twd.visit()
or the action that triggers the request.
typescript
await twd.mockRequest("uniqueLabel", {
  url: "/api/some-endpoint",
  method: "GET",
  response: mockData,
  status: 200,
});

// With regex URL matching
await twd.mockRequest("search", {
  url: "/api/users\\?.*",
  method: "GET",
  response: results,
  status: 200,
  urlRegex: true,
});

// Wait for request and verify payload
const req = await twd.waitForRequest("createItem");
expect(req.request).to.deep.equal({ field: "value" });

// Clear all mocks (in beforeEach)
twd.clearRequestMockRules();
重要提示:务必在
twd.visit()
或触发请求的操作之前进行模拟。
typescript
await twd.mockRequest("uniqueLabel", {
  url: "/api/some-endpoint",
  method: "GET",
  response: mockData,
  status: 200,
});

// 使用正则表达式匹配URL
await twd.mockRequest("search", {
  url: "/api/users\\?.*",
  method: "GET",
  response: results,
  status: 200,
  urlRegex: true,
});

// 等待请求并验证负载
const req = await twd.waitForRequest("createItem");
expect(req.request).to.deep.equal({ field: "value" });

// 清除所有模拟(在beforeEach中)
twd.clearRequestMockRules();

Sinon Stubs (for module mocking)

Sinon Stubs(用于模块模拟)

Tests run in the browser. ESM named exports are IMMUTABLE and cannot be stubbed. Solution: wrap hooks/services in objects with default export.
typescript
import Sinon from "sinon";
import authModule from "../hooks/useAuth";

Sinon.stub(authModule, "useAuth").returns({ isAuthenticated: true });
// Cleanup: Sinon.restore() in beforeEach

测试在浏览器中运行。ESM命名导出是不可变的,无法被桩化。 解决方案:将钩子/服务包装在带有默认导出的对象中。
typescript
import Sinon from "sinon";
import authModule from "../hooks/useAuth";

Sinon.stub(authModule, "useAuth").returns({ isAuthenticated: true });
// 清理:在beforeEach中调用Sinon.restore()

Standard Test Structure

标准测试结构

typescript
import { screenDom, twd, userEvent } from "twd-js";
import { beforeEach, describe, it, expect } from "twd-js/runner";

describe("Feature name", () => {
  beforeEach(() => {
    twd.clearRequestMockRules();
    twd.clearComponentMocks();
  });

  it("should display the page correctly", async () => {
    await twd.mockRequest("getData", {
      method: "GET",
      url: "/api/endpoint",
      response: { items: [{ id: 1, name: "Item" }] },
      status: 200,
    });

    await twd.visit("/route");

    const heading = await screenDom.findByRole(
      "heading",
      { name: "Page Title" },
      { timeout: 3000 },
    );
    twd.should(heading, "be.visible");
  });
});
typescript
import { screenDom, twd, userEvent } from "twd-js";
import { beforeEach, describe, it, expect } from "twd-js/runner";

describe("Feature name", () => {
  beforeEach(() => {
    twd.clearRequestMockRules();
    twd.clearComponentMocks();
  });

  it("should display the page correctly", async () => {
    await twd.mockRequest("getData", {
      method: "GET",
      url: "/api/endpoint",
      response: { items: [{ id: 1, name: "Item" }] },
      status: 200,
    });

    await twd.visit("/route");

    const heading = await screenDom.findByRole(
      "heading",
      { name: "Page Title" },
      { timeout: 3000 },
    );
    twd.should(heading, "be.visible");
  });
});

Running Tests

运行测试

bash
npx twd-relay run
Exit code 0 = all passed, 1 = failures.
bash
npx twd-relay run
退出码0表示全部通过,1表示存在失败。

Common Mistakes to AVOID

需要避免的常见错误

  1. Forgetting
    await
    on async methods
  2. Mocking AFTER visit — always mock before
    twd.visit()
  3. Not clearing mocks — always
    twd.clearRequestMockRules()
    in
    beforeEach
  4. Using Node.js APIs — tests run in browser, no
    fs
    ,
    path
    , etc.
  5. Importing from wrong package
    describe/it/beforeEach
    from
    twd-js/runner
    , NOT Jest/Mocha
  6. Stubbing named exports — ESM makes them immutable. Use the default-export object pattern.
  1. 忘记在异步方法前加
    await
  2. 在访问后才进行模拟 — 务必在
    twd.visit()
    之前完成模拟
  3. 未清除模拟 — 务必在
    beforeEach
    中调用
    twd.clearRequestMockRules()
  4. 使用Node.js API — 测试在浏览器中运行,没有
    fs
    path
    等模块
  5. 从错误的包导入
    describe/it/beforeEach
    必须来自
    twd-js/runner
    ,而非Jest/Mocha
  6. 桩化命名导出 — ESM使其不可变。使用默认导出对象模式。

Instructions

操作步骤

  1. Read the page component first to understand what UI elements and roles exist
  2. Read the service/API layer to understand URL patterns
  3. Read existing tests for project conventions
  4. Create mock data matching the API response shape
  5. Write tests following the standard structure above
  6. Run with
    npx twd-relay run
    and iterate until green
  7. Cover: page rendering, user interactions, CRUD operations, empty states, error states
  1. 先阅读页面组件,了解存在的UI元素和角色
  2. 阅读服务/API层,了解URL模式
  3. 阅读现有测试,了解项目约定
  4. 创建匹配API响应格式的模拟数据
  5. 按照上述标准结构编写测试
  6. 使用
    npx twd-relay run
    运行测试
    ,并迭代直到全部通过
  7. 覆盖场景:页面渲染、用户交互、CRUD操作、空状态、错误状态