Skip to content

Add new stage runtime API#1431

Open
baev wants to merge 7 commits into
mainfrom
stages
Open

Add new stage runtime API#1431
baev wants to merge 7 commits into
mainfrom
stages

Conversation

@baev
Copy link
Copy Markdown
Member

@baev baev commented Mar 31, 2026

Context

This PR adds stage(name) as a new runtime API for integrations.

stage() is a lightweight way to describe the major phases of a test without wrapping the code in step() callbacks. It is intended for cases where a test naturally flows through preparation, SUT execution, and verification, and where values created in one phase should remain in normal local variables for the next phase.

Instead of restructuring the test just to create reporting boundaries, stage() lets us mark a phase and treat everything that follows as part of that stage until the next stage starts or the current execution context ends.

import { stage } from "allure-js-commons";

it("creates an order", async () => {
  stage("prepare data");
  const customer = await createCustomer();
  const cart = await createCart(customer);

  stage("submit order");
  const order = await submitOrder(cart);

  stage("verify result");
  expect(order.status).toBe("created");
});

In the report, this produces three top-level steps:

  • prepare data
  • submit order
  • verify result

Any runtime steps logged between stage boundaries become nested under the active stage. Nested stages are also supported, so a stage() started inside a step() becomes a child of that step.

High-level behavior:

  • stage() is syntax sugar for semantic test phases
  • the active stage stays open until another stage starts or the enclosing step/test/fixture ends
  • runtime steps and log steps emitted in that window are nested under the active stage
  • stages preserve normal test code structure and variable flow
  • integration coverage was added across the runtime-step suites to validate this behavior consistently

Checklist

@github-actions github-actions Bot added theme:api Javascript API related issue theme:mocha Mocha related issue theme:jest Jest related issue theme:cucumberjs CucumberJS related issue theme:jasmine Jasmine related issue theme:playwright theme:codeceptjs theme:vitest labels Mar 31, 2026
@baev baev requested review from delatrie, epszaw and formaceft-93 and removed request for epszaw March 31, 2026 14:32
@baev baev added the type:new feature New feature or request label Mar 31, 2026
Copy link
Copy Markdown
Collaborator

@delatrie delatrie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ReporterRuntime.stopStep method doesn't stop stage steps. It's not a problem when we're handling API steps (lambda/log) because the stage steps are stopped by #handleStopStepMessage. But some integrations call stopStep directly to introduce framework-specific steps. If such a step contains a stage, it breaks the resulting step structure. CodeceptJS, Cucumber.js, and Playwright are affected.

A sidenote: Cypress is not affected because it handles steps differently (at browser side).

Case 1: CodeceptJS

codecept.conf.js:

exports.config = {
  name: "codeceptjs",
  tests: "./specs/*.spec.js",
  output: "./output",
  plugins: {
    allure: {
      resultsDir: "./allure-results",
      require: "allure-codeceptjs",
    },
  },
  helpers: {
    MyHelper: {
      require: "./helpers.js",
    },
  },
};

helpers.js:

const Helper = require("@codeceptjs/helper");
const { stage } = require("allure-js-commons");

module.exports = class extends Helper {

  async load() {
    await stage("Loading the data 1");
    await stage("Loading the data 2");
    await Promise.resolve();
  }

  async navigate() {
    await stage("Open the tab");
    await stage("Open the dialog");
    await Promise.resolve();
  }
};

./specs/sample.spec.js:

Feature("sample feature");
Scenario("sample test", async ({ I }) => {
  I.load();
  I.navigate();
});
Image

Case 2: Cucumber.js

cucumber.js:

module.exports = {
  default: {
    format: ["allure-cucumberjs/reporter:./out/ignore"],
  },
};

features/sample.feature:

Feature: sample feature
	Scenario: sample scenario
		Given foo
		When bar
		Then baz

features/support/sample.js:

const { Given, When, Then } = require("@cucumber/cucumber");
const { stage } = require("allure-js-commons");

Given("foo", async function () {
  await stage("Loading the data");
});
When("bar", async function () {});
Then("baz", async function () {});

features/support/world.js:

require("allure-cucumberjs")
Image

Case 3: Playwright

In theory, Playwright is also affected, but in practice, this effect is masked by a step synchronization issue. Gonna create a separate issue for this (if not yet exists).

Edited: #1444.

import { test } from '@playwright/test';
import { stage, step } from "allure-js-commons";

test("steps", async () => {
  await step("outer", async () => {
    await stage("inner stage");
  });
  await step("sibling", async () => {});
});
Image

import { logStep, stage, step } from "allure-js-commons";

test("steps", async () => {
stage("stage 1");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stage("stage 1");
await stage("stage 1");

await logStep("a");
await step("b", async () => {
await logStep("b 1");
stage("b 2");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stage("b 2");
await stage("b 2");

await logStep("b 2 nested");
});

stage("stage 2");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
stage("stage 2");
await stage("stage 2");

test("steps", async () => {
stage("stage 1");
await logStep("a");
await step("b", async () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about removing this lambda step and asserting the steps structure directly? This will simplify the test significantly. Basically, the current test splits this lambda step away anyway, but in a more complex way.

Or just assert the current (invalid) structure with the lambda step as the first step. This way, the test will fail if/when we fix the synchronization issue with Playwright steps, so we don't forget to update it.

In any case, I'll mention this test in an issue for Playwright steps to fix once the issue is resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

theme:api Javascript API related issue theme:codeceptjs theme:cucumberjs CucumberJS related issue theme:jasmine Jasmine related issue theme:jest Jest related issue theme:mocha Mocha related issue theme:playwright theme:vitest type:new feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants