
Product
Introducing Repository Access Permissions and Custom Roles
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.
@cerios/playwright-step-decorator
Advanced tools
A Playwright decorator for creating step-based tests
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.
box, timeout, or a custom location to test.step().npm install @cerios/playwright-step-decorator
npm install --save-dev @cerios/playwright-step-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).
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();
}
}
test.step() OptionsYou 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
}
}
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
asynckeyword. If a method body has noawait, you can dropasyncto avoid therequire-awaitlint error. Because the decorated call is wrapped intest.step(which is asynchronous) and callers shouldawaitit, keep the return type aPromiseand returnstepResult(),stepResult(value)for non-function values, orstepResult(() => { ... })instead of marking the methodasync:@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; }); }
{{param}}
Replaced with the value of the parameter named param. Named placeholders support identifier parameters, default values, and rest parameters.{{param.prop}}
Replaced with the value of the property prop of parameter param.[[0]], [[1]], ...
Replaced with the argument at the given index.If a method uses destructured parameters, prefer index placeholders such as [[0]]. The decorator cannot reliably map destructured bindings like { name } back to a placeholder name such as {{name}}.
When a placeholder resolves to an object or array, the decorator renders it as JSON when possible so reports stay readable.
@step("User: {{user.name}}, Age: {{user.age}}")
async function greet(user: { name: string, age: number }) {
// ...
}
await greet({ name: 'Alice', age: 30 });
// Step description: "User: Alice, Age: 30"
@step("First arg is [[0]], second is [[1]]")
async function doSomething(a: string, b: number) {
// ...
}
await doSomething('foo', 42);
// Step description: "First arg is foo, second is 42"
@step("Named: {{name}}, Index: [[1]]")
async function example(name: string, value: number) {
// ...
}
await example('Bob', 77);
// Step description: "Named: Bob, Index: 77"
The decorator automatically captures the call site location of your decorated methods and passes it to Playwright's test.step(). This means that:
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.
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.
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.
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.Promise.all(...) calls on the same instance keep their own TestStepInfo.step(description: string){{param}}, {{param.prop}}, or [[index]].getStepInfo()TestStepInfo for the active decorated method call.getStepInfo(instance)TestStepInfo for the active decorated method call.stepResult()Promise<void> for non-async @step methods that do not return a value.stepResult(value)Promise<Awaited<T>> for non-async @step methods that return a value.stepResult(() => value)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).
MIT
Ronald Veth - Cerios
Pull requests are welcome! Please open an issue first to discuss what you would like to change.
FAQs
A Playwright decorator for creating step-based tests
We found that @cerios/playwright-step-decorator demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.