@cerios/playwright-step-decorator

中文文档
2.3.0发布于 2026/11/05MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 4127

A Playwright decorator for creating step-based tests

原始使用文档
文档目录32 节
  1. Features
  2. Installation
  3. For Development
  4. Usage
  5. Import the Decorator
  6. Basic Example
  7. Passing test.step() Options
  8. How It Works
  9. Placeholders
  10. Examples
  11. 1. Named and Nested Placeholders
  12. 2. Index Placeholders
  13. 3. Mixing Named and Index Placeholders
  14. 4. Value Selector Placeholders
  15. Accurate Source Locations in Reports
  16. Example
  17. Error Handling
  18. Accessing Step Context ( getStepInfo )
  19. Example
  20. API
  21. step(description: string)
  22. step(description: string, options)
  23. step(options)
  24. getStepInfo()
  25. getStepInfo(instance)
  26. stepResult()
  27. stepResult(value)
  28. stepResult(() => value)
  29. License
  30. Author
  31. Contributing
  32. Links

🎭 Playwright Step Decorator | By Cerios

A TypeScript decorator for Playwright that makes your test steps more readable and traceable by allowing you to add dynamic, parameterized descriptions to your test steps.

Features

  • Decorator syntax for Playwright steps.
  • Dynamic descriptions: Use placeholders to inject parameter values into step descriptions.
  • Supports nested properties: Reference nested object properties in your descriptions.
  • Index-based placeholders: Reference arguments by their position.
  • Type-safe value placeholders: __key__ placeholders are filled by lambdas whose parameters are inferred from the decorated method. You get full autocompletion, and the compiler checks that every key has exactly one selector.
  • Better parameter matching: Named placeholders now handle default values and rest parameters more reliably.
  • Readable complex values: Objects and arrays are rendered as JSON in step descriptions when possible.
  • Forwards Playwright step options: Pass box, timeout, or a custom location to test.step().
  • Accurate source locations: Step locations in reports point to the actual method call site, not the decorator implementation.

Installation

npm install @cerios/playwright-step-decorator

For Development

npm install --save-dev @cerios/playwright-step-decorator

Usage

@step is a standard ECMAScript decorator for class methods. Playwright's transpiler supports this out of the box. If you compile your tests with tsc, make sure experimentalDecorators is not enabled: @step then has no effect, and no step is reported.

Symbol-named methods: the Babel version bundled with Playwright throws Cannot convert a Symbol value to a string for any decorator on a method named with a symbol, before the decorator runs. Decorate a method with a normal name and call it from the symbol-named one:

class MyTest {
    @step("Iterate over {{items}}")
    async iterate(items: string[]) {
        // ...
    }

    [run](items: string[]) {
        return this.iterate(items);
    }
}

Import the Decorator

import { step, stepResult } from "@cerios/playwright-step-decorator";

Use stepResult() for non-async @step methods that resolve no value, stepResult(value) when they resolve a non-function value, or stepResult(() => { ... }) when you want a small lambda with multiple actions. Function inputs are treated as factories, so if you need to resolve a function itself, return it from a lambda such as stepResult(() => actualFunction).

Basic Example

class MyTest {
    @step("Login as {{user.name}}")
    async login(user: { name: string }) {
        // ...login logic
    }

    @step("Click button [[0]] times")
    async clickButton(times: number) {
        // ...click logic
    }

    @step("Build greeting for {{name}}")
    buildGreeting(name: string): Promise<string> {
        return stepResult(`Hello ${name}`);
    }

    @step("Prepare greeting for {{name}}")
    prepareGreeting(name: string): Promise<string> {
        return stepResult(() => {
            const greeting = `Hello ${name}`;
            return greeting.toUpperCase();
        });
    }

    @step("Reset the form")
    resetForm(): Promise<void> {
        // No `await` inside and no `async` keyword needed (avoids the require-await lint error)
        this.dirty = false;
        return stepResult();
    }
}

Passing test.step() Options

You can pass Playwright step options as the only decorator argument, or as the second argument when you also want a custom description:

class MyTest {
    @step("Wait for checkout", { box: true, timeout: 15_000 })
    async waitForCheckout() {
        // ...wait logic
    }

    @step({ timeout: 5_000 })
    async refreshTotals() {
        // Uses the default step name: "MyTest.refreshTotals"
    }

    @step("Open modal", {
        location: { file: "tests/cart.spec.ts", line: 42, column: 7 },
    })
    async openModal() {
        // Uses the explicit location instead of the generated call site
    }

    @step("Submit order", { timeout: 30_000 })
    async submitOrder() {
        // Still auto-generates the call-site location because no custom location was supplied
    }
}

How It Works

The @step decorator wraps your promise-returning method in a Playwright test.step, using a dynamic description. Placeholders in the description are replaced with actual argument values at runtime.

Note: A decorated method does not need the async keyword. If a method body has no await, you can drop async to avoid the require-await lint error. Because the decorated call is wrapped in test.step (which is asynchronous) and callers should await it, keep the return type a Promise and return stepResult(), stepResult(value) for non-function values, or stepResult(() => { ... }) instead of marking the method async:

@step("Reset the form")
resetForm(): Promise<void> {
  this.dirty = false;
  return stepResult(); // no `async`, no require-await warning
}

@step("Build greeting for {{name}}")
buildGreeting(name: string): Promise<string> {
  return stepResult(`Hello ${name}`);
}

@step("Prepare greeting for {{name}}")
prepareGreeting(name: string): Promise<string> {
  return stepResult(() => {
      const greeting = `Hello ${name}`;
      return greeting;
  });
}

Placeholders

  • Named parameter: {{param}} Replaced with the value of the parameter named param. Named placeholders support identifier parameters, default values, and rest parameters.
  • Nested property: {{param.prop}} Replaced with the value of the property prop of parameter param.
  • Index-based: [[0]], [[1]], ... Replaced with the argument at the given index.
  • Value selector: __key__ (for example __0__ or __role__) Replaced with the result of the matching lambda in the values option. Without values, __key__ is literal text. See Value Selector Placeholders.

When a placeholder resolves to an object or array, the decorator renders it as JSON when possible so reports stay readable. Regular expressions, errors, Maps and Sets are printed in a readable form, and very large objects use a shorter, depth-limited form. Each placeholder adds at most 1,000 characters to the title; longer values end with a note such as … (500 more characters).

Nested properties also work on strings, numbers and functions, for example {{name.length}}.

Named placeholders read the parameter names from the method's source, so keep @step directly above the method when you combine it with other decorators:

class MyTest {
    @retry() // Other decorators go above @step
    @step("Login as {{user.name}}")
    async login(user: { name: string }) {
        // ...
    }
}

If another decorator sits below @step, @step sees that decorator's wrapper function rather than your parameters. A placeholder then fails with an error, or reads the wrapper's parameter if the names happen to match.


Examples

1. Named and Nested Placeholders

class Greeter {
    @step("User: {{user.name}}, Age: {{user.age}}")
    async greet(user: { name: string; age: number }) {
        // ...
    }
}
await new Greeter().greet({ name: "Alice", age: 30 });
// Step description: "User: Alice, Age: 30"

2. Index Placeholders

class Worker {
    @step("First arg is [[0]], second is [[1]]")
    async doSomething(a: string, b: number) {
        // ...
    }
}
await new Worker().doSomething("foo", 42);
// Step description: "First arg is foo, second is 42"

3. Mixing Named and Index Placeholders

class Example {
    @step("Named: {{name}}, Index: [[1]]")
    async run(name: string, value: number) {
        // ...
    }
}
await new Example().run("Bob", 77);
// Step description: "Named: Bob, Index: 77"

4. Value Selector Placeholders

Use __key__ placeholders when the step text needs something computed from the arguments, such as one item of an array, a filtered list, or a conditional label. Each key maps to a lambda in the values option. The lambda receives the method's arguments with their real types, so your editor autocompletes them and the compiler checks them:

type Contact = { name: string; email: string; primary: boolean };
type User = { name: string; role: string; contacts: Contact[] };

class ContactsPage {
    @step("Add contacts for __0__ (second: __second__, primary: __primary__)", {
        values: {
            0: user => user.name, // user: User
            second: (_user, contacts) => contacts[1].email, // contacts: Contact[]
            primary: user =>
                user.contacts
                    .filter(c => c.primary)
                    .map(c => c.name)
                    .join(", "),
        },
    })
    async addContacts(user: User, contacts: Contact[]) {
        // ...
    }
}
// Step description: "Add contacts for Alice (second: [email protected], primary: Bob, Dave)"

__key__ placeholders are only active when you pass the values option. Without it, text such as __NEXT_DATA__ prints as is.

When you pass values with a literal description, the compiler enforces:

  • every __key__ in the description has a selector in values,
  • values contains no keys that aren't in the description,
  • each lambda's parameters match the decorated method's parameters. Explicit annotations such as (user: User) => ... are allowed, but they must match.

Notes:

  • Keys can be numeric (__0__ → { 0: ... }) or names (__role__ → { role: ... }). Keys are any text between __ and __ without whitespace, braces or square brackets. Other spans, such as a __ b __ c or report__{{date}}__final, stay as literal text.
  • To print literal __text__ in a description that also uses values, return it from a selector: { literal: () => "__text__" }. A parameter name that contains a complete __key__, such as {{a__b__c}}, can't be used alongside values, so use an index placeholder for it.
  • Lambdas receive the arguments exactly as passed; omitted optional arguments are undefined.
  • Lambdas must be synchronous, because they run before the step starts. A lambda that returns a promise causes an error.
  • A lambda that returns null or undefined prints null or undefined. For nullable parameters, use optional chaining (user => user?.name), which the compiler requires when the parameter's type allows null or undefined.
  • The returned value is formatted like any other placeholder (strings as-is, objects as JSON).
  • If the description is a plain string variable instead of a literal, keys can't be checked at compile time. A missing selector then throws at runtime.

Accurate Source Locations in Reports

The decorator automatically captures the call site location of your decorated methods and passes it to Playwright's test.step(). This means that:

  • HTML reports show the correct file and line number where the method was called
  • Trace viewer points to the actual test code, not the decorator implementation
  • CI logs display accurate source locations for easier debugging

This feature uses Playwright's built-in location parameter to ensure step locations in reports are as helpful as possible. If you provide location in the decorator's second argument, that explicit value is used. Otherwise, the decorator falls back to the generated call-site location automatically.

Example

When you call a decorated method in your test:

// my-test.spec.ts
test("example", async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.login(user); // Line 10
});

The Playwright report will show the step location as my-test.spec.ts:10, not the location of the decorator implementation.


Error Handling

When the class is defined: a description that can never work throws as soon as the file containing the class loads, so a typo is found even if the method is never called. This happens when:

  • A named parameter in the description does not exist in the function signature.
  • Named placeholders are used with destructured or otherwise unsupported parameter syntax. The error suggests using index placeholders instead.
  • An index placeholder is beyond the declared parameters. Methods with rest parameters or that use arguments are checked at call time instead.
  • A __key__ placeholder has no selector in values. This is also a compile-time error for literal descriptions.

Because the class name isn't known yet, these errors name only the method.

When the method is called: if a description can't be resolved for a particular call, the promise returned by the decorated method rejects and the step doesn't start. This happens when:

  • A nested property does not exist on the parameter. If the parameter (or an intermediate property) is null or undefined, the placeholder prints null or undefined instead. The same goes for a property missing from an omitted object default.
  • An index placeholder is beyond the arguments that were passed.
  • An omitted argument has a default value that can't be printed. Literals can be printed, including objects and arrays made only of literals, such as { timeout: 5, tags: ["a"] }. Anything that needs evaluating can't, such as Mode.Auto, { timeout: DEFAULT_TIMEOUT } or { ...base }.
  • A values selector throws. The rejection is a descriptive error with the original error as cause.
  • A values selector returns a promise.

Accessing Step Context (getStepInfo)

Sometimes you want to attach files, logs, or metadata to the current Playwright step inside your decorated method. Use the getStepInfo function to access the TestStepInfo object for the current step.

Example

import { step, getStepInfo } from "@cerios/playwright-step-decorator";

class NavbarTest {
    @step("Assert the navbar items are as expected")
    async assertNavbarItems(expectedItems: string[]): Promise<void> {
        const stepInfo = getStepInfo();
        await stepInfo.attach("expected-items.json", {
            body: JSON.stringify(expectedItems),
            contentType: "application/json",
        });
        // ...your assertions...
        await stepInfo.attach("actual-items.json", {
            body: JSON.stringify(await this.navbarItem.allInnerTexts()),
            contentType: "application/json",
        });
    }
}

Note:

  • getStepInfo() and getStepInfo(this) must be called inside a method decorated with @step.
  • Step context is tracked per async invocation, so concurrent Promise.all(...) calls on the same instance keep their own TestStepInfo.
  • Throws an error if called outside a step context.

API

step(description: string)

  • description: The step description, supporting placeholders like {{param}}, {{param.prop}}, or [[index]].
  • Returns: A decorator function for promise-returning methods.

step(description: string, options)

  • options.box / options.timeout / options.location: Forwarded to Playwright's test.step().
  • options.values: An object mapping each __key__ placeholder in the description to a lambda (...methodArgs) => unknown. Passing it turns on __key__ placeholders for that description.

step(options)

  • options: Playwright step options; the step uses the default ClassName.methodName description.

Exported helper types: StepOptions, StepValueSelector<Args>, StepValues<Description, Args>, StepOptionsWithValues<Description, Args>, and ValuePlaceholderKeys<Description>.

getStepInfo()

  • Returns: The current Playwright TestStepInfo for the active decorated method call.

getStepInfo(instance)

  • instance: Retained for backwards compatibility with earlier releases.
  • Returns: The current Playwright TestStepInfo for the active decorated method call.

stepResult()

  • Returns: Promise<void> for non-async @step methods that do not return a value.

stepResult(value)

  • value: A non-function value to resolve.
  • Returns: Promise<Awaited<T>> for non-async @step methods that return a value.

stepResult(() => value)

  • callback: A zero-argument lambda that can perform multiple synchronous actions before returning a value.
  • Returns: Promise<Awaited<T>> with the callback result, rejecting if the callback throws.

If you need to resolve a function value itself, wrap it in the callback form: stepResult(() => actualFunction).


License

MIT


Author

Ronald Veth - Cerios


Contributing

Pull requests are welcome! Please open an issue first to discuss what you would like to change.


版本与发布记录

@cerios/playwright-step-decorator 共发布 13 个版本,首次发布于 2025/06/02;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.3.0latest2026/11/053 天前
2.2.22026/09/301 个月前
2.2.12026/07/104 个月前
2.2.02026/06/045 个月前
2.1.22026/06/025 个月前
2.1.12026/03/247 个月前

关于 @cerios/playwright-step-decorator 的常见问题

@cerios/playwright-step-decorator 是什么?

A Playwright decorator for creating step-based tests

如何安装 @cerios/playwright-step-decorator?

在项目目录运行 npm install @cerios/playwright-step-decorator,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@playwright/test(^1.51.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cerios/playwright-step-decorator 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cerios/playwright-step-decorator 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cerios/playwright-step-decorator 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cerios/playwright-step-decorator 可以商用吗?

@cerios/playwright-step-decorator 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cerios/playwright-step-decorator 的下载量有多少?

根据 npm 官方下载统计,@cerios/playwright-step-decorator 在 2026/10/10 – 2026/11/08 这 30 天内被下载 4,127 次,日均约 138 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cerios/playwright-step-decorator 还在维护吗?

最新版本 2.3.0 发布于 2026/11/05(3 天前);至今共发布 13 个版本,其中过去 12 个月发布了 7 个;首次发布于 2025/06/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。