Skip to content

Commit

Permalink
refactor documentation (part 1) (#1699)
Browse files Browse the repository at this point in the history
* add export of cli --help

* dont need note about sync

* update world docs

* document retry

* document profiles

* start to trim stuff from cli

* more on profiles

* document parallel

* add linsk to readmr
  • Loading branch information
davidjgoss committed Jun 18, 2021
1 parent 6e60589 commit 6b09896
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 40 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ The following documentation is for master. See below for documentation for older
* [Data Table Interface](/docs/support_files/data_table_interface.md)
* [Attachments](/docs/support_files/attachments.md)
* [API Reference](/docs/support_files/api_reference.md)
* Guides
* [Running in parallel](./docs/parallel.md)
* [Retrying failing scenarios](./docs/retry.md)
* [Profiles](./docs/profiles.md)
* [FAQ](/docs/faq.md)

#### Documentation for older versions
Expand Down
29 changes: 4 additions & 25 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,11 @@ Note that the rerun file parser can only work with the default separator for now

## Parallel

You can run your scenarios in parallel with `--parallel <NUMBER_OF_WORKERS>`. Each worker is run in a separate Node process and receives the following env variables (as well as a copy of `process.env` from the coordinator process):

* `CUCUMBER_PARALLEL` - set to 'true'
* `CUCUMBER_TOTAL_WORKERS` - set to the number of workers
* `CUCUMBER_WORKER_ID` - ID for worker ('0', '1', '2', etc.)

### Timing

When using parallel mode, the last line of the summary output differentiates between real time elapsed during the test run and aggregate time spent actually running steps:

```
73 scenarios (73 passed)
512 steps (512 passed)
0m51.627s (executing steps: 4m51.228s)
```
See [Parallel](./parallel.md).

## Profiles

In order to store and reuse commonly used CLI options, you can add a `cucumber.js` file to your project root directory. The file should export an object where the key is the profile name and the value is a string of CLI options. The profile can be applied with `-p <NAME>` or `--profile <NAME>`. This will prepend the profile's CLI options to the ones provided by the command line. Multiple profiles can be specified at a time. If no profile is specified and a profile named `default` exists, it will be applied.
See [Profiles](./profiles.md).

## Tags

Expand All @@ -157,8 +143,7 @@ A note on using in conjunction with `--retry`: we consider a test case to have f

## Retry failing tests

Use `--retry <int>` to rerun tests that have been failing. This can be very helpful for flaky tests.
To only retry failing tests in a subset of test use `--retry-tag-filter <EXPRESSION>` (use the same as in Use [Tags](#tags))
See [Retry](./retry.md)

## Transpilation

Expand Down Expand Up @@ -228,10 +213,4 @@ Note that the first `--require tests.setup.js` overrides the default require glo

## World Parameters

You can pass in parameters to pass to the world constructor with `--world-parameters <JSON>`. The JSON string must define an object. The parsed object will be passed as the `parameters` to the the world constructor. This option is repeatable and the objects will be merged with the last instance taking precedence.

Example:

```
--world-parameters '{"fancySetting":true}'
```
See [World](./support_files/world.md).
29 changes: 29 additions & 0 deletions docs/parallel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Parallel

Cucumber supports running scenarios in parallel. The main process becomes a "coordinator" and spins up several separate Node processes to be the "workers". You can enable this with the `--parallel <NUMBER_OF_WORKERS>` CLI option:

```shell
$ cucumber-js --parallel 3
```

The number you provide is the number of workers that will run scenarios in parallel.

Each worker receives the following env variables (as well as a copy of `process.env` from the coordinator process):

* `CUCUMBER_PARALLEL` - set to 'true'
* `CUCUMBER_TOTAL_WORKERS` - set to the number of workers
* `CUCUMBER_WORKER_ID` - ID for worker ('0', '1', '2', etc.)

### Timing

When using parallel mode, the last line of the summary output differentiates between real time elapsed during the test run and aggregate time spent actually running steps:

```
73 scenarios (73 passed)
512 steps (512 passed)
0m51.627s (executing steps: 4m51.228s)
```

### Hooks

When using parallel mode, any `BeforeAll` and `AfterAll` hooks you have defined will run _once per worker_.
41 changes: 41 additions & 0 deletions docs/profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Profiles

If you have several permutations of running Cucumber with different CLI options in your project, it might be a bit cumbersome to manage. *Profiles* enable you to declare bundles of configuration and reference them with a single CLI option.

Let's take the common case of having some things a bit different locally vs in CI. Here's the command we've been running locally:

```shell
$ cucumber-js --require-module ts-node/register --require 'support/**/*./ts' --world-parameters '{\"appUrl\":\"http://localhost:3000/\"}' --format progress-bar --format html:./cucumber-report.html
```

For argument's sake, we'll want these changes in CI:

- The URL for the app (maybe dynamically provisioned?)
- The formatters we want to use

To start using Profiles, we just need to create a `cucumber.js` file in our project's root. It's a simple JavaScript module that exports an object with profile names as keys and CLI options as values. We can lean on JavaScript to reduce duplication and grab things dynamically as needed. Here's what we might write to address the needs described above:

```javascript
const worldParameters = {
appUrl: process.env.MY_APP_URL || "http://localhost:3000/"
}
const common = `--require-module ts-node/register --require 'support/**/*./ts' --world-parameters '${JSON.stringify(worldParameters)}'`

module.exports = {
'default': `${common} --format progress-bar --format html:./cucumber-report.html`,
'ci': `${common} --format html:./cucumber-report.html --publish`
}
```

Now, if we just run `cucumber-js` with no arguments, it will pick up our profiles and use the `default` one.

In CI, we just need to change the command to specify the "ci" profile:

```shell
$ cucumber-js --profile ci
```

Some notes about how Profiles work:

- The `--profile` CLI option is repeatable, so you can apply multiple profiles at once.
- You can still supply options directly on the command line when using profiles, they will be appended to whatever comes from profiles.
26 changes: 26 additions & 0 deletions docs/retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Retry

If you have a flaky scenario (e.g. failing 10% of the time for some reason), you can use *Retry* to have Cucumber attempt it multiple times until either it passes or the maximum number of attempts is reached. You enable this via the `--retry <MAXIMUM_RETRIES>` CLI option, like this:

```shell
$ cucumber-js --retry 1
```

The number you provide is the number of retries that will be allowed after an initial failure.

*Note:* Retry isn't recommended for routine use, but can be a good trade-off in some situations where you have a scenario that's too valuable to remove, but it's either not possible or not worth the effort to fix the flakiness.

Some notes on how Retry works:

- Only relevant failing scenarios are retried, not the whole test run.
- When a scenario is retried, it runs all hooks and steps again from the start with a fresh [World](./support_files/world.md) - nothing is retained from the failed attempt.
- When a scenario passes on a retry, it's treated as a pass overall in the results, although the details of each attempt are emitted so formatters can access them.


## Targeting scenarios

Using the `--retry` option alone would mean every scenario would be allowed multiple attempts - this almost certainly isn't what you want, assuming you have a small set of flaky scenarios. To target just the relevant scenarios, you can provide a [tag expression](https://cucumber.io/docs/cucumber/api/#tag-expressions) via the `--retry-tag-filter <TAG_EXPRESSION>` CLI option, like this:

```shell
$ cucumber-js --retry 1 --retry-tag-filter @flaky
```
47 changes: 32 additions & 15 deletions docs/support_files/world.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
# World

*World* is an isolated context for each scenario, exposed to the hooks and steps as `this`.
The default world constructor is:
*World* is an isolated context for each scenario, exposed to the hooks and steps as `this`, enabling you to set and recall some state across the lifecycle of your scenario. A simple example:

```javascript
class World {
constructor({ attach, log, parameters }) {
this.attach = attach
this.log = log
this.parameters = parameters
}
}
const { When } = require('@cucumber/cucumber')

When('something happens', async function() {
this.foo = 'bar'
})
```

As well as being able to have arbitrary state, you get some helpers preset on the World for you:

* `this.attach`: function used for adding [attachments](./attachments.md) to hooks/steps
* `this.log`: function used for [logging](./attachments.md#logging) information from hooks/steps
* `this.parameters`: object of parameters passed in via the [CLI](../cli.md#world-parameters)

Some notes on the scope of World:

- It's scoped to a single scenario only - not shared globally between scenarios. This reinforces a Cucumber principle: that scenarios should work entirely independently of one another.
- If you're using [Retry](../retry.md), you'll get a fresh World for every attempt at your scenario, so state isn't retained between attempts.

## World Parameters

You might want to provide some configuration/environmental data to your World at runtime. You can provide this data as a JSON literal via the `--world-parameters` CLI option, like this:

```shell
$ cucumber-js --world-parameters '{"appUrl":"http://localhost:3000/"}'
```

* `attach`: function used for adding [attachments](./attachments.md) to hooks/steps
* `log`: function used for [logging](./attachments.md#logging) information from hooks/steps
* `parameters`: object of parameters passed in via the [CLI](../cli.md#world-parameters)
This option is repeatable, so you can use it multiple times and the objects will be merged with the later ones taking precedence.

You can provide your own World class with its own properties and methods that help with your instrumentation. You can extend the built-in `World` with your own class and then call `setWorldConstructor` with it:
This data is then available on `this.parameters` from your hooks and steps.

## Custom World

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 provide your own World class with its own properties and methods that help with your instrumentation, and then call `setWorldConstructor` to tell Cucumber about it. You should extend the built-in `World` class:

```javascript
const { setWorldConstructor, World } = require('@cucumber/cucumber')
Expand All @@ -29,6 +47,7 @@ class CustomWorld extends World {
.build()

constructor(options) {
// needed so `attach`, `log` and `parameters` are properly set
super(options)
}

Expand All @@ -41,5 +60,3 @@ class CustomWorld extends World {

setWorldConstructor(CustomWorld)
```

**Note:** The World constructor was made strictly synchronous in *[v0.8.0](https://github.com/cucumber/cucumber-js/releases/tag/v0.8.0)*.

0 comments on commit 6b09896

Please sign in to comment.