twd-test-writer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTWD Test Writing Guide
TWD测试编写指南
You are writing tests for TWD (Test While Developing), an in-browser testing library. Tests run in the browser (not Node.js) with a sidebar UI for instant visual feedback. Syntax is similar to Jest/Cypress but with key differences.
Key characteristics:
- Designed for SPAs (React, Vue, Angular, Solid.js)
- Not suitable for SSR-first architectures (Next.js App Router)
- Uses Mock Service Worker (MSW) for API mocking
- Uses for element queries
@testing-library/dom
你正在为**TWD(开发中测试,Test While Developing)**编写测试,这是一个浏览器端测试库。测试在浏览器(而非Node.js)中运行,带有侧边栏UI可提供即时视觉反馈。语法与Jest/Cypress类似,但存在关键差异。
核心特性:
- 专为SPA(React、Vue、Angular、Solid.js)设计
- 不适用于SSR优先架构(如Next.js App Router)
- 使用Mock Service Worker (MSW) 进行API模拟
- 使用进行元素查询
@testing-library/dom
Required Imports
必需的导入
Every TWD test file needs these exact imports:
typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, afterEach, expect } from "twd-js/runner";Package exports:
- — Main API (
twd-js,twd,userEvent,screenDom)screenDomGlobal - — Test functions (
twd-js/runner,describe,it,beforeEach,afterEach)expect - — UI components (
twd-js/ui)MockedComponent
NEVER import , , , from Jest, Mocha, or other libraries. They MUST come from .
describeitbeforeEachexpecttwd-js/runner每个TWD测试文件都需要以下确切导入:
typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, afterEach, expect } from "twd-js/runner";包导出内容:
- — 主API(
twd-js、twd、userEvent、screenDom)screenDomGlobal - — 测试函数(
twd-js/runner、describe、it、beforeEach、afterEach)expect - — UI组件(
twd-js/ui)MockedComponent
切勿从Jest、Mocha或其他库导入、、、。它们必须来自。
describeitbeforeEachexpecttwd-js/runnerFile Naming
文件命名规则
Test files must follow: or
*.twd.test.ts*.twd.test.tsx测试文件必须遵循: 或
*.twd.test.ts*.twd.test.tsxCore Rules
核心规则
Async/Await is Required
必须使用Async/Await
typescript
// twd.get() and twd.getAll() are async — ALWAYS await
const button = await twd.get("button");
const items = await twd.getAll(".item");
// userEvent methods are async — ALWAYS await
await userEvent.click(button.el);
await userEvent.type(input, "text");
// Test functions should be async
it("should do something", async () => { /* ... */ });typescript
// twd.get() 和 twd.getAll() 是异步函数——必须使用await
const button = await twd.get("button");
const items = await twd.getAll(".item");
// userEvent方法是异步函数——必须使用await
await userEvent.click(button.el);
await userEvent.type(input, "text");
// 测试函数应为异步
it("should do something", async () => { /* ... */ });Element Selection
元素选择
Preferred: Testing Library queries via
screenDomtypescript
// By role (most accessible — RECOMMENDED)
const button = screenDom.getByRole("button", { name: "Submit" });
const heading = screenDom.getByRole("heading", { name: "Welcome", level: 1 });
// By label (for form inputs)
const emailInput = screenDom.getByLabelText("Email Address");
// By text content
const message = screenDom.getByText("Success!");
const partial = screenDom.getByText(/welcome/i);
// By test ID
const card = screenDom.getByTestId("user-card");
// Query variants
screenDom.getByRole("button"); // Throws if not found
screenDom.queryByRole("button"); // Returns null if not found
await screenDom.findByRole("button"); // Waits for element (async)
screenDom.getAllByRole("button"); // Returns arrayFor modals/portals use :
screenDomGlobaltypescript
import { screenDomGlobal } from "twd-js";
const modal = screenDomGlobal.getByRole("dialog");Fallback: CSS selectors via
twd.get()typescript
const button = await twd.get("button");
const byId = await twd.get("#email");
const byClass = await twd.get(".error-message");
const multiple = await twd.getAll(".item");推荐方式:通过使用Testing Library查询
screenDomtypescript
// 通过角色(最具可访问性——推荐)
const button = screenDom.getByRole("button", { name: "Submit" });
const heading = screenDom.getByRole("heading", { name: "Welcome", level: 1 });
// 通过标签(适用于表单输入)
const emailInput = screenDom.getByLabelText("Email Address");
// 通过文本内容
const message = screenDom.getByText("Success!");
const partial = screenDom.getByText(/welcome/i);
// 通过测试ID
const card = screenDom.getByTestId("user-card");
// 查询变体
screenDom.getByRole("button"); // 未找到时抛出错误
screenDom.queryByRole("button"); // 未找到时返回null
await screenDom.findByRole("button"); // 等待元素出现(异步)
screenDom.getAllByRole("button"); // 返回数组对于模态框/门户使用:
screenDomGlobaltypescript
import { screenDomGlobal } from "twd-js";
const modal = screenDomGlobal.getByRole("dialog");备选方式:通过使用CSS选择器
twd.get()typescript
const button = await twd.get("button");
const byId = await twd.get("#email");
const byClass = await twd.get(".error-message");
const multiple = await twd.getAll(".item");User Interactions
用户交互
typescript
const user = userEvent.setup();
// With screenDom elements (direct)
await user.click(screenDom.getByRole("button", { name: "Save" }));
await user.type(screenDom.getByLabelText("Email"), "hello@example.com");
// With twd.get() elements (use .el for raw DOM)
const twdButton = await twd.get(".save-btn");
await user.click(twdButton.el);
// Other interactions
await user.dblClick(element);
await user.clear(input);
await user.selectOptions(select, "option-value");
await user.keyboard("{Enter}");typescript
const user = userEvent.setup();
// 直接使用screenDom元素
await user.click(screenDom.getByRole("button", { name: "Save" }));
await user.type(screenDom.getByLabelText("Email"), "hello@example.com");
// 使用twd.get()元素(需用.el获取原始DOM)
const twdButton = await twd.get(".save-btn");
await user.click(twdButton.el);
// 其他交互
await user.dblClick(element);
await user.clear(input);
await user.selectOptions(select, "option-value");
await user.keyboard("{Enter}");Assertions
断言
Method style (on twd elements):
typescript
const element = await twd.get("h1");
element.should("have.text", "Welcome");
element.should("contain.text", "come");
element.should("be.visible");
element.should("not.be.visible");
element.should("have.class", "header");
element.should("have.value", "test@example.com");
element.should("have.attr", "type", "submit");
element.should("be.disabled");
element.should("be.enabled");
element.should("be.checked");
element.should("be.focused");
element.should("be.empty");Function style (any element):
typescript
twd.should(screenDom.getByRole("button"), "be.visible");
twd.should(screenDom.getByRole("button"), "have.text", "Submit");URL assertions:
typescript
await twd.url().should("eq", "http://localhost:3000/dashboard");
await twd.url().should("contain.url", "/dashboard");Chai expect (for non-element assertions):
typescript
expect(array).to.have.length(3);
expect(value).to.equal("expected");
expect(obj).to.deep.equal({ key: "value" });方法风格(针对twd元素):
typescript
const element = await twd.get("h1");
element.should("have.text", "Welcome");
element.should("contain.text", "come");
element.should("be.visible");
element.should("not.be.visible");
element.should("have.class", "header");
element.should("have.value", "test@example.com");
element.should("have.attr", "type", "submit");
element.should("be.disabled");
element.should("be.enabled");
element.should("be.checked");
element.should("be.focused");
element.should("be.empty");函数风格(针对任何元素):
typescript
twd.should(screenDom.getByRole("button"), "be.visible");
twd.should(screenDom.getByRole("button"), "have.text", "Submit");URL断言:
typescript
await twd.url().should("eq", "http://localhost:3000/dashboard");
await twd.url().should("contain.url", "/dashboard");Chai expect(针对非元素断言):
typescript
expect(array).to.have.length(3);
expect(value).to.equal("expected");
expect(obj).to.deep.equal({ key: "value" });Navigation and Waiting
导航与等待
typescript
await twd.visit("/");
await twd.visit("/login");
await twd.wait(1000); // Wait for time (ms)
await screenDom.findByText("Success!"); // Wait for element
await twd.notExists(".loading-spinner"); // Wait for element to NOT existtypescript
await twd.visit("/");
await twd.visit("/login");
await twd.wait(1000); // 等待指定时间(毫秒)
await screenDom.findByText("Success!"); // 等待元素出现
await twd.notExists(".loading-spinner"); // 等待元素消失API Mocking
API模拟
TWD uses Mock Service Worker. Always mock BEFORE the request fires.
typescript
// Mock GET request
await twd.mockRequest("getUser", {
method: "GET",
url: "/api/user/123",
response: { id: 123, name: "John Doe" },
status: 200,
});
// Mock POST request
await twd.mockRequest("createUser", {
method: "POST",
url: "/api/users",
response: { id: 456, created: true },
status: 201,
});
// URL patterns with regex
await twd.mockRequest("getUserById", {
method: "GET",
url: /\/api\/users\/\d+/,
response: { id: 999, name: "Dynamic User" },
urlRegex: true,
});
// Error responses
await twd.mockRequest("serverError", {
method: "GET",
url: "/api/data",
response: { error: "Server error" },
status: 500,
});
// Wait for request and inspect body
const rule = await twd.waitForRequest("submitForm");
expect(rule.request).to.deep.equal({ email: "test@example.com" });
// Wait for multiple requests
await twd.waitForRequests(["getUser", "getPosts"]);TWD使用Mock Service Worker。务必在请求发送前进行模拟。
typescript
// 模拟GET请求
await twd.mockRequest("getUser", {
method: "GET",
url: "/api/user/123",
response: { id: 123, name: "John Doe" },
status: 200,
});
// 模拟POST请求
await twd.mockRequest("createUser", {
method: "POST",
url: "/api/users",
response: { id: 456, created: true },
status: 201,
});
// 使用正则表达式匹配URL
await twd.mockRequest("getUserById", {
method: "GET",
url: /\/api\/users\/\d+/,
response: { id: 999, name: "Dynamic User" },
urlRegex: true,
});
// 模拟错误响应
await twd.mockRequest("serverError", {
method: "GET",
url: "/api/data",
response: { error: "Server error" },
status: 500,
});
// 等待请求并检查请求体
const rule = await twd.waitForRequest("submitForm");
expect(rule.request).to.deep.equal({ email: "test@example.com" });
// 等待多个请求
await twd.waitForRequests(["getUser", "getPosts"]);Component Mocking
组件模拟
tsx
// In your component — wrap with MockedComponent
import { MockedComponent } from "twd-js/ui";
function Dashboard() {
return (
<MockedComponent name="ExpensiveChart">
<ExpensiveChart data={data} />
</MockedComponent>
);
}typescript
// In your test
twd.mockComponent("ExpensiveChart", () => (
<div data-testid="mock-chart">Mocked Chart</div>
));tsx
// 在组件中——用MockedComponent包裹
import { MockedComponent } from "twd-js/ui";
function Dashboard() {
return (
<MockedComponent name="ExpensiveChart">
<ExpensiveChart data={data} />
</MockedComponent>
);
}typescript
// 在测试中
twd.mockComponent("ExpensiveChart", () => (
<div data-testid="mock-chart">Mocked Chart</div>
));Module Stubbing with Sinon
使用Sinon进行模块存根
Tests run in the browser. ESM named exports are IMMUTABLE and cannot be stubbed.
Solution: wrap hooks/services in objects with default export.
typescript
// hooks/useAuth.ts — CORRECT: stubbable
const useAuth = () => useAuth0();
export default { useAuth };
// hooks/useAuth.ts — WRONG: cannot be stubbed
export const useAuth = () => useAuth0();typescript
// In test:
import Sinon from "sinon";
import authModule from "../hooks/useAuth";
Sinon.stub(authModule, "useAuth").returns({
isAuthenticated: true,
user: { name: "John" },
});
// Always Sinon.restore() in beforeEach测试在浏览器中运行。ESM命名导出是不可变的且无法被存根。
解决方案:将钩子/服务包裹在默认导出的对象中。
typescript
// hooks/useAuth.ts — 正确:可被存根
const useAuth = () => useAuth0();
export default { useAuth };
// hooks/useAuth.ts — 错误:无法被存根
export const useAuth = () => useAuth0();typescript
// 在测试中:
import Sinon from "sinon";
import authModule from "../hooks/useAuth";
Sinon.stub(authModule, "useAuth").returns({
isAuthenticated: true,
user: { name: "John" },
});
// 务必在beforeEach中调用Sinon.restore()Standard Test Template
标准测试模板
typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, expect } from "twd-js/runner";
describe("Feature Name", () => {
beforeEach(() => {
twd.clearRequestMockRules();
twd.clearComponentMocks();
});
it("should display the page correctly", async () => {
// 1. Setup mocks BEFORE visiting
await twd.mockRequest("getData", {
method: "GET",
url: "/api/data",
response: { items: [] },
status: 200,
});
// 2. Navigate
await twd.visit("/page");
// 3. Wait for async operations
await twd.waitForRequest("getData");
// 4. Interact
const user = userEvent.setup();
const button = screenDom.getByRole("button", { name: "Load" });
await user.click(button);
// 5. Assert
const message = await twd.get(".message");
message.should("have.text", "No items found");
});
it.only("debug this test", async () => { /* Only this test runs */ });
it.skip("skip this test", async () => { /* This test won't run */ });
});typescript
import { twd, userEvent, screenDom } from "twd-js";
import { describe, it, beforeEach, expect } from "twd-js/runner";
describe("Feature Name", () => {
beforeEach(() => {
twd.clearRequestMockRules();
twd.clearComponentMocks();
});
it("should display the page correctly", async () => {
// 1. 在访问前设置模拟
await twd.mockRequest("getData", {
method: "GET",
url: "/api/data",
response: { items: [] },
status: 200,
});
// 2. 导航到页面
await twd.visit("/page");
// 3. 等待异步操作完成
await twd.waitForRequest("getData");
// 4. 进行用户交互
const user = userEvent.setup();
const button = screenDom.getByRole("button", { name: "Load" });
await user.click(button);
// 5. 执行断言
const message = await twd.get(".message");
message.should("have.text", "No items found");
});
it.only("debug this test", async () => { /* 仅运行此测试 */ });
it.skip("skip this test", async () => { /* 跳过此测试 */ });
});Common Mistakes to AVOID
需避免的常见错误
- Forgetting on
await,twd.get(),userEvent.*,twd.visit()screenDom.findBy* - Mocking AFTER visit — always mock before or the action triggering the request
twd.visit() - Not clearing mocks — always and
twd.clearRequestMockRules()intwd.clearComponentMocks()beforeEach - Using Node.js APIs — tests run in the browser, no ,
fs, etc.path - Importing from wrong package — /
describe/itfrombeforeEach, NOT Jest/Mochatwd-js/runner - Using Cypress syntax — no ,
cy.get(). Usecy.visit(),twd.get()twd.visit() - Stubbing named exports — ESM makes them immutable. Use the default-export object pattern
- Using global describe/it — always import from
twd-js/runner
- 忘记使用:
await、twd.get()、userEvent.*、twd.visit()均为异步操作screenDom.findBy* - 在访问后才设置模拟:务必在或触发请求的操作前设置模拟
twd.visit() - 未清除模拟:务必在中调用
beforeEach和twd.clearRequestMockRules()twd.clearComponentMocks() - 使用Node.js API:测试在浏览器中运行,无法使用、
fs等Node.js APIpath - 从错误的包导入:/
describe/it必须来自beforeEach,而非Jest/Mochatwd-js/runner - 使用Cypress语法:请勿使用、
cy.get(),应使用cy.visit()、twd.get()twd.visit() - 存根命名导出:ESM命名导出是不可变的,请使用默认导出的对象模式
- 使用全局的describe/it:务必从导入
twd-js/runner