World, is an isolated scope for each scenario, exposed to the steps and most hooks as this
. It allows you to set variables in one step and recall them in a later step. All variables set this way are discarded when the scenario concludes. It is managed by a world class, either the default one or one you create. Each scenario is given an new instance of the class when the test starts, even if it is a retry run.
The world is not available to the hooks BeforeAll
or AfterAll
as each of these executes outside any particular scenario.
Here's some simple usage of the world to retain state between steps:
const { Given, Then } = require('@cucumber/cucumber')
Given("my color is {string}", function(color) {
this.color = color
})
Then("my color should not be red", function() {
if (this.color === "red") {
throw new Error("Wrong Color");
}
});
With those step definitions in place:
Scenario: Will pass
Given my color is "blue"
Then my color should not be red
Scenario: Will fail
Given my color is "red"
Then my color should not be red
ℹ️ Added in v10.8.0
Arrow functions have traditionally not played nicely with Cucumber's pattern of binding the World to this
, because of their different scoping behaviour. However, you can now use the world
object to get a handle on your World from an arrow function. Here's the equivalent of the first example in this doc:
const { Given, Then, world } = require('@cucumber/cucumber')
Given("my color is {string}", (color) => {
world.color = color
})
Then("my color should not be red", () => {
if (world.color === "red") {
throw new Error("Wrong Color");
}
});
Note that this will throw if you try to call it outside of a step or hook.
By default, the world is an instance of Cucumber's built-in World
class. Cucumber provides a number of formatting helpers that are passed into the constructor as an options object. The default world binds these helpers as follows:
this.attach
: a method for adding attachments to hooks/stepsthis.log
: a method for logging information from hooks/stepsthis.link
: a method for linking to URLs from hooks/stepsthis.parameters
: an object of parameters passed in via configuration (see below)
Your custom world will also receive these arguments, but it's up to you to decide what to do with them and they can be safely ignored.
Tests often require configuration and environment information. One of the most frequent cases is web page tests that are using a browser driver; things like viewport, browser to use, application URL and so on.
The worldParameters
configuration option allows you to provide this information to Cucumber. It takes the data in the form of an object literal, like this:
- In a configuration file
{ worldParameters: { appUrl: 'http://localhost:3000/' } }
- On the CLI
$ cucumber-js --world-parameters '{"appUrl":"http://localhost:3000/"}'
This option is repeatable, so you can use it multiple times and the objects will be merged with the later ones taking precedence.
Parameters can be modified in BeforeAll
hooks if required.
You might also want to have methods on your world that hooks and steps can access to keep their own code simple. To do this, you can write your own world implementation with its own properties and methods that help with your instrumentation, and then call setWorldConstructor
to tell Cucumber about it:
const { setWorldConstructor, World, When } = require('@cucumber/cucumber')
class CustomWorld extends World {
count = 0
constructor(options) {
super(options)
}
eat(count) {
this.count += count
}
}
setWorldConstructor(CustomWorld)
When('I eat {int} cucumbers', function(count) {
this.eat(count)
})
In the example above we've extended the built-in World
class, which is recommended. You can also use a plain function as your world constructor:
const { setWorldConstructor, When } = require('@cucumber/cucumber')
setWorldConstructor(function(options) {
this.count = 0
this.eat = (count) => this.count += count
})
When('I eat {int} cucumbers', function(count) {
this.eat(count)
})
Let's walk through a typical scenario, setting up world that manages a browser context. We'll use the ES6 module syntax for this example. First, let's set up our custom world. Class files should not be loaded as steps - they should be imported. So in this example we'll presume it is in a classes folder next to the steps folder.
// CustomWorld.js
import { World } from '@cucumber/cucumber';
import seleniumWebdriver from "selenium-webdriver";
/*
* The only method to be inherited from the default world is
* the constructor, so if you want to handle the options in
* an entirely customized manner you don't have to extend from
* World as seen here.
*/
export default class extends World {
driver = null;
/*
* A constructor is only needed if you have custom actions
* to take after the Cucumber parses the options or you
* want to override how the options are parsed.
*
* The options are an object with three members
* {
* log: Cucumber log function,
* attach: Cucumber attachment function,
* params: World Parameters object
* }
*/
constructor(options) {
/*
* If you don't call the super method you will need
* to bind the options here as you see fit.
*/
super(options);
// Custom actions go here.
}
/*
* Constructors cannot be asynchronous! To work around this we'll
* use an init method with the Before hook
*/
async init(scenario) {
this.driver = await seleniumWebdriver.Builder()
.forBrowser(this.params.browser)
.build();
}
}
Now we'll use a step file to setup this custom world and declare the before hook.
// setup.js
import { Before, setWorldConstructor } from '@cucumber/cucumber';
import CustomWorld from "../classes/CustomWorld.js"
setWorldConstructor(CustomWorld);
Before(async function(scenario) {
await this.init(scenario);
});
Custom world classes can also be used as a point to anchor methods used by multiple steps. In a small project it is sufficient to put these methods in the world class file.
import { Given } from '@cucumber/cucumber';
Given("I'm viewing the admin settings", async function(){
this.login("administrator");
this.page.navigateTo("/admin/settings");
});
This pattern allows for cleaner feature files. Remember that, ideally, scenarios should be between 3-5 lines and communicate what the user is doing clearly to the whole team without going into the details of how it will be done. While steps can be reused that should not come at the expense of feature clarity.
If you're using TypeScript, you can get optimum type safety and completion based on your custom world and parameters.
If you have a custom world, you'll need to tell TypeScript about the type of this
in your hook and step functions:
When('I eat {int} cucumbers', function(this: CustomWorld, count: number) {
this.eat(count)
})
ℹ️ Added in v8.1.0
If you're using world parameters (see above), Cucumber's world-related interfaces and classes support generics to easily specify their interface:
interface CustomParameters {
cukeLimit: number
}
class CustomWorld extends World<CustomParameters> {
// etc
}
If you're using a plain function as your world constructor, you'll need to define an interface for your world and type that as this
for your function:
interface CustomWorld {
count: number
eat: (count: number) => void
}
setWorldConstructor(function(this: CustomWorld, options: IWorldOptions) {
this.count = 0
this.eat = (count) => this.count += count
})
- The World provides an isolated context for your tests.
- It allows you to track test state while maintaining the isolation of each scenario.
- Every scenario gets its own instance of the world. Even on retry.
- You can use the default world or build your own.
- You can pass parameters to the world using the
worldParameters
configuration option.