Step Definitions
Autometa steps resemble Cucumber JS steps and should be identifiable to most Gherkin IDE plugins.
The largest difference is that Autometa step callbacks are not bound
to the World
object. Instead, World
is a property of the App
object,
which will passed as the last argument to the step callback.
Because the step is not bound, it is safe to use fat arrow (=>
) functions.
Defining a step
Steps can be defined using the Given
, When
, or Then
functions. If the gherkin
file includes And
, But
or *
steps, they can be matched against any of those main 3.
It is advised to define them against the step they follow.
I.e for the steps
Given a foo
And a bar
The step And a bar
should be defined as a Given
, however this is not enforced.
import { Given } from "@autometa/runner";
Given("a foo", () => {
// ...
});
Given("a bar", () => {
// ...
});
If no matching steps are found, a report will be generated from a fuzzy search, helping to identify typos.
App
The App
object is passed as the last argument to the step callback. If there are no tables
or Cucumber Expressions, it will be the only argument. Apps type must be defined by you (see Getting Started),
or else it will be an empty object containing an empty world at the type level.
import { Given } from "@autometa/runner";
Given("a foo", (app) => {
app.webdriver.clickFoo();
});
Async
Step callbacks can be synchronous or asynchronous. If you do not want to store
the result and define the function as async
you can simply return the promise
of the underlying action:
import { Given } from "@autometa/runner";
Given("a foo", (app) => {
return app.http.createFoo({ foo: "test-bar" });
});
However if, for example, you want to store the result on the World, you can await the promise and then store the result:
import { Given } from "@autometa/runner";
Given("a foo", async (app) => {
const foo = await app.http.createFoo({ foo: "test-bar" });
app.world.foo = foo;
});
Tables
Autometa tables are inspired by the Java implementation which allows tables to be parsed differently according to step.
See Data Tables for more information on tables.
To use a table in a step, simply append the table type as the last argument to the step definition, which can be accessed as the first or second to last argument before App in the definition callback.
Given a foo
| foo | bar | baz |
| 1 | true | bobby |
This represents a 'Horizontal Table' or HTable
, where the first row represents the headers
and the rest the data. This can be accessed as follows:
import { Given, HTable } from "@autometa/runner";
Given(
"a foo",
(table: HTable, app) => {
const fooNum = table.get("foo", 0) as number;
const fooBool = table.get("bar", 0) as boolean;
const fooStr = table.get("baz", 0) as string;
const fooNumString = table.get("foo", 0, true) as string;
},
HTable
);
Datatable values will be converted to their javascript type where possible
i.e strings that represent numbers will be converted to a number, 'true' and 'false' will be treated as bools while the rest will be parsed as strings.
The original raw value can be accessed by passing true
as the third argument to table.get
const fooNumString = table.get("foo", 0, true) as string;
Jest Cucumber Style
It's also possible to use a nested jest-cucumber style to write definitions. These can still access globally defined steps but also feature or scenario specific steps.
import { Feature, Scenario, ScenarioOutline, Rule Given, HTable } from "@autometa/runner";
Feature("A feature", () => {
Given('Feature shared step', ()=>{
// ...
})
Scenario("A scenario", () => {
Given("a foo", (app) => {
app.world.foo = "foo";
});
Then("the foo should be 'foo'", (app) => {
expect(app.world.foo).toBe("foo");
});
});
ScenarioOutline("A scenario outline", () => {
Given("a foo with <foo>", (table: HTable, app) => {
app.world.foo = table.get("foo", 0) as string;
});
Then("the foo should be <foo>", (table: HTable, app) => {
expect(app.world.foo).toBe(table.get("foo", 0) as string);
});
});
Rule("A rule", () => {
Given("a foo", (app) => {
app.world.foo = "foo";
});
Then("the foo should be 'foo'", (app) => {
expect(app.world.foo).toBe("foo");
});
Scenario("A scenario", () => {
Given("a foo", (app) => {
app.world.foo = "foo";
});
Then("the foo should be 'foo'", (app) => {
expect(app.world.foo).toBe("foo");
});
});
});
}, 'path/to/feature.feature');
The path
argument at the end of the Feature
function can be relative, root-relative,
absolute, or be relative to a featuresRoot
based on the following rules:
- If the path starts with a slash, it is absolutel
- If the path starts with
./
or../
it is relative - If the path starts with neither a
/
or.
it is relative to the root of the project - If the path starts with
~
and afeaturesRoot
is defined inautometa.config.ts
, it is relative to that root.
Step definitions defined in this style have specificity. I.e. if a step is defined globally and within a feature, the feature specific step will be used. This allows for more granular control over step definitions. Likewise, if a step is defined globally, within a feature, and within a scenario, the scenario specific step will be used.