AgentSkillsCN

twd-tester

TWD 测试代理——在您开发时自动撰写、运行并验证浏览器中的测试。当您编写或修改测试,或验证功能是否正常运行时,该代理会自动触发。

SKILL.md
--- frontmatter
name: twd-tester
description: TWD test agent — writes, runs, and validates in-browser tests while you develop. Automatically invoked when writing or modifying tests, or when verifying features work correctly.
argument-hint: [what-to-test]
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash(npx twd-relay run:*), Bash(npx twd-relay run), Task]
context: fork
agent: general-purpose

TWD Test Agent

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

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.

TWD API Reference

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.

File Naming

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

Async/Await

twd.get(), twd.getAll(), userEvent.*, screenDom.findBy*, twd.visit(), twd.waitForRequest() are ALL async. Always await them.

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");

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}");

Navigation

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

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" });

API Mocking

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();

Sinon Stubs (for module mocking)

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

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");
  });
});

Running Tests

bash
npx twd-relay run

Exit code 0 = all passed, 1 = failures.

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 packagedescribe/it/beforeEach from twd-js/runner, NOT Jest/Mocha
  6. Stubbing named exports — ESM makes them immutable. Use the default-export object pattern.

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