cucumber / cucumber-expressions Goto Github PK
View Code? Open in Web Editor NEWHuman friendly alternative to Regular Expressions
License: MIT License
Human friendly alternative to Regular Expressions
License: MIT License
The LICENSE file is currently empty; differing from other repositories in the Cucumber organisation.
Empty LICENSE file.
MIT LICENSE specified, similar to other Cucumber repositories.
Dear @cucumber/cucumber-expressions maintainers,
Thank you for your contribution to the open-source community.
This issue was automatically created to inform you a new version (15.0.0) of @cucumber/cucumber-expressions was published without a matching tag in this repo.
Our service monitors the open-source ecosystem and informs popular packages' owners in case of potentially harmful activity.
If you find this behavior legitimate, kindly close and ignore this issue. Read more
The @cucumber/language-service module is able to extract Cucumber Expressions from source code (currently Java and TypeScript, but other languages can be added with relatively little effort).
However, if a user is using @cucumber/language-service
(which is written in TypeScript) with Java, they may have Cucumber Expressions that uses parameter types that are only defined for Cucumber-JVM (such as {double}
or {bigdecimal}
).
These expressions will cause a parse error unless the registry is instantiated with those parameter types.
One way to support this is to add a defineDefaultParameterTypes(registry, platform)
function where the value of platform
would be used to decide what parameter types to define.
In order to implement this consistently on all platforms, we could add new acceptance tests in testdata/cucumber-expression/matching/*.yml
with an additional platform
property to indicate what platform
to use.
Also see #42
I have been looking over the cucumber git repo for regex "expressions" (javascript)
defineParameterType seems interesting but I do not see any simple usage examples only unit test cases.
https://github.com/cucumber/cucumber-expressions/tree/5af7076298a9c2b5455e63f1c0de90ddc9f1c1a4#javascript--typescript
The entire ParameterTypeRegistry + defineParameterType seems a bit complex when you explore it.
Showing off how to use it in a feature file would help adoption/integration.
I am currently using Regex strings directly in the feature file step definitions. I was looking here to find better ways of doing things.
This text was originally generated from a template, then edited by hand. You can modify the template here.
In many of the AST sub-classes, such as Node
there are a variety of attributes passed in. One of these is called end
or derivations thereof. This name "end" can be problematic in some languages (Such as ruby). A few examples are below
Java - https://github.com/cucumber/cucumber-expressions/blob/main/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java#L64
Ruby - https://github.com/cucumber/cucumber-expressions/blob/main/ruby/lib/cucumber/cucumber_expressions/ast.rb#L38
JS - https://github.com/cucumber/cucumber-expressions/blob/main/javascript/src/Ast.ts#L77
It seems as though we often use the hash representation methods, and not directly using the getters. Therefore I propose removing all getters and just rely on using the hash methods. Failing this maybe altering start/end to starting
and ending
This text was originally generated from a template, then edited by hand. You can modify the template here.
I'd like to have optional free-form text in my steps:
For instance:
Then error 123 is reported (because reasons)
The "because reasons"
string isn't fixed, the content of the parenthesis varies depending on the scenario.
It would be nice to either:
.*
) in optionals:error {int} is reported( \\(.*)
error {int} is reported( \\({})
I tried using an anonymous parameter:
error {int} is reported( \\(){}
and ignore the related parameter in the step definition.
but this is not possible in my case, since I also have a step for:
error {int} is reported {int} times( \\(){}
which causes an ambiguous step definition.
This is why I'd need to put the anonymous part within the optional:
error {int} is reported( \\({})
But then it is an illegal expression (parameters are not allowed in optionals).
I also tried using a comment at the end of the expression:
Then error 123 is reported #(because reasons)
but comments are only allowed as a start of a new line.
The only resort is to use a regex. But the expressions then get quite ugly and unreadable (the examples above are simplified, my real expressions are more complex).
Or move the free-form text to its own line, but it is definitely less concise and sort of breaks the scenario flow.
These two workarounds work, but are suboptimal as they unfortunately make for less readable code.
I recently used cucumber v4.5.4 (cucumber-java
), and the following expressions just worked for my use case:
error {int} is reported( \\(.*)`
error {int} is reported {int} time(s)( \\(.*)
This stopped working when migrating to cucumber v7.8.0!
In this expression:
I run cucumber-js \(installed locally/globally\)
The phrase I run cucumber-js installed locally
doesn't match.
The generated regular expression is shown as:
/^I run cucumber-js \(installed (?:locally|globally\))$/
Instead of the generated regular expression
/^I run cucumber-js \(installed (?:locally|globally\))$/
I think it should be
/^I run cucumber-js \(installed (?:locally|globally)\)$/
I guess we're gobbling up the trailing \)
as part of the word globally
cucumber-expressions javascript 15.0.1
Discovered in cucumber/cucumber-js#1926
When I try to create step as below
Then Verify "user" exist in the list
Then Verify "user" not exist in the list
with single step definition, it is not identifying. I have create stepDef as below
@Then ("Verify {string} (|not) exist in the list")
public void verifyUserExist(String username, String exist){
if(exist.isEmpty){
//check exist
}
}
showing error as "Step undefined
You can implement this step using the snippet(s) below:"
This was working with older cucumber version I hope when i check in this Issue
Cucumber version: 7.15.0
No response
No response
When creating a custom param definition, the transformer funcion does not received nested capturing groups.
As javascript developer, I expect regex capturing groups to conform Regex Ecamscript standard and capture nested groups
https://www.npmjs.com/package/@cucumber/cucumber-expressions/v/16.1.2
Create a new npm package with your prefered typescript setup (the bug is also recreated with plain javascript).
In the package.json, install @cucumber/[email protected]
dependency
Define a custom parameter with nested captured groups:
import { defineParameterType } from '@cucumber/cucumber';
defineParameterType({
name: 'foo',
regexp: /((foo))/,
transformer: function (
firstGroup: string,
secondGroup: string
): string[] {
return [firstGroup, secondGroup]
},
});
Then, write any step definition and feature involving this custom definition
import { Given, Then, When } from '@cucumber/cucumber';
Given('{foo}', function(foo: string[]): void { console.log(foo) });
When('bar', function(): void {});
Then('baz', function(): void {});
Given foo
When bar
Then baz
Then, create a minimal config testing the feature processing the custom parameter and the step definitions and have a look at the console. Instead of ['foo', 'foo']
, ['foo', undefined]
is printed instead.
It seems the capturing groups are internally represented as a tree. Cucumber relies on TreeRegexp
to create a GroupBuilder
for the purpose of capturing regex groups. These groups are represented as a tree and only the root ones are passed to the custom definition transformer, ignoring any children groups.
Having a look at the Argument
class:
public getValue<T>(thisObj: unknown): T | null {
const groupValues = this.group ? this.group.values : null
return this.parameterType.transform(thisObj, groupValues)
}
group.values
are passed to the transformer:
export default class Group {
constructor(
public readonly value: string,
public readonly start: number | undefined,
public readonly end: number | undefined,
public readonly children: readonly Group[]
) {}
get values(): string[] | null {
return (this.children.length === 0 ? [this] : this.children).map((g) => g.value)
}
}
As you can see granchildren groups are never included. I would expect nested groups to be included as Ecmascript Regex spec states
This text was originally generated from a template, then edited by hand. You can modify the template here.
This issue provides visibility into Renovate updates and their statuses. Learn more
This repository currently has no open or pending branches.
Instructions about how to define a parameter type in Python: https://github.com/cucumber/cucumber-expressions#python
This is misleading, because Behave has not yet integrated this library into its code. Cucumber Expressions are not supported in Behave.
Behave is not yetusing this library. It doesn't support Cucumber Expressions yet, nor does it have an API for defining parameter types. See behave/behave#968 for more details.
Behave v1.2.7.dev2
There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.
Location: ^*.gemspec
Error type: Invalid regular expression: ^*.gemspec
Is your feature request related to a problem? Please describe.
The various testdata/**/*.yaml
files have expected
fields that are strings containing JSON. I find this hard to read, and it also means the tests have to parse JSON after parsing YAML.
Describe the solution you'd like
I'd like the test data to just be YAML. So instad of this:
cucumber-expressions/testdata/ast/alternation.yaml
Lines 1 to 12 in 4b6ae11
We'd have this instead:
expression: mice/rats
expectation:
type: EXPRESSION_NODE
start: 0
end: 9
nodes:
- type: ALTERNATION_NODE
start: 0
end: 9
nodes:
- type: ALTERNATIVE_NODE
start: 0
end: 4
nodes:
- type: TEXT_NODE
start: 0
end: 4
token: mice
- type: ALTERNATIVE_NODE
start: 5
end: 9
nodes:
- type: TEXT_NODE
start: 5
end: 9
token: rats
Any objections if I convert the test data? (I'd also change JSON.parse(expectation.expected)
to just expectation.expected
in the tests.
My main reason for doing this is to just tidy up a bit before people start implementing the Python / .NET ports...
WDYT @mpkorstanje?
Repeated 'the' within the error message for use of an alternation inside an optional.
Through an issue raised on the Visual Studio Code extension (cucumber/vscode#142), it was observed that the error message for use of an alternation inside an optional includes a repeated 'the' in its second line:
An alternation can not be used inside an optional.
You can use '\/' to escape the the '/'
Single 'the':
An alternation can not be used inside an optional.
You can use '\/' to escape the '/'
Cucumber Expressions: 16.1.2
Cucumber Visual Studio Code extension: 1.8.0
import { When } from 'cypress-cucumber-preprocessor/steps'
const { wait } = cy
When('entro no detalhe d(a/o) {string}', () => {
wait('@apisRequests')
})
Currently, there is no way to ignore a cucumber expression parameter, even though it is possible to do so in a normal regex. Thus, I think it would be nice to be able to have access to that feature in cucmber expressions as well.
My proposed solution is simply to add the same mechanism as with a non-capturing regex group (for example (?:.*?)) like this, for example:
@Given( "{string} Database Entry with {?string} {string} has {string} = {string}" )
public void databaseEntryWithIdHasString( String tableName, String id, String columnName, String value) {
...
}
As you can see in my snippet, the cucumber expression {?string} is ignored, as it is just a string that will be used to define the name of the id. It's just a way to add more clarity to my features without the need of creating a new step definition for each and every way we can describe our column id.
Currently, I need to add it to my method signature like the snippet below and get warnings that my parameter idName is not used, which requires me to suppress the warning (we are using SonarQube and this kind of warning decreases our code quality):
@SuppressWarnings( "unused" )
@Given( "{string} Database Entry with {string} {string} has {string} = {string}" )
public void databaseEntryWithIdHasString( String tableName, String idName, String id, String columnName, String value) {
...
}
I think that in the parser that you are using to convert cucumber expressions to their actual parameter value, you can simply filter out the ones that start with a question mark, as simple as that.
Yes, I have considered using a regex instead, but I feel like that adding the feature would be pretty low-effort, and I'd rather keep using cucumber expressions, since the reason why they exist is to make the step definitions more readable (I want to keep my step definitions readable).
In some cases it might be helpful to have a way of declaring several instances of one type as an expression, e.g.
Given the customer selects product(s) {product}+
which could match something like:
Given the customer selects product bike
Given the customer selects products bike, car
Given the customer selects products bike, car, skateboard
and for a better flow maybe also with a filler:
Given the customer selects products bike and car
Given the customer selects products bike, car or skateboard
What already works is something like this:
val separators = arrayOf(",", ";", "and", "or")
@ParameterType("^(?:(?:,|;|and|or)?\s?\w+)+$")
fun products(product: String): List<Product> {
return product.split(*separators)
.map { it.trim() }
.map { Product(it) }
}
This has the disadvantage, that the regex (if specific things still should be checked) might get complex, since it also has to include all separators and the possibility of them being there or not. Also, the complete string (incl. separators) is highlighted in the feature files. Hence, having that as a feature of cucumber expressions would be nice, since it might make the used regex easier. And might also help with the highlighting in the feature files, since the separators could be displayed in another color.
I could imagine two different ways. For both ways the definition of the parameter type might be kind of the same like it is now (define how the transformation is done for one single element), but it could define separators:
@ParameterType(regex = "\\w+", separators=[",", ";", "and", "or"])
fun product(product: String): List<Product> {
return Product(it)
}
Option one: a special notation for marking an expression as "vararg", e.g. {name}+
If the + is provided, the method signature must contain a collection type:
@Given("the customer selects product(s) {product}+")
fun givenCustomerSelectsProduct(product: List<Product>) { \* ... *\ }
Option two: no special expression syntax - instead it depends on the method signature if it is a single value or a collection of values is provided.
So both would be possible:
@Given("the customer selects product(s) {product}")
fun givenCustomerSelectsProduct(product: Product) { \* ... *\ }
and
@Given("the customer selects product(s) {product}")
fun givenCustomerSelectsProduct(product: List<Product>) { \* ... *\ }
The Behave team (led by @jenisys) would like to add support for Cucumber Expressions in Behave.
The first step would be to implement Cucumber Expressions in Python.
There are four existing implementations in this repository (Go, Ruby, TypeScript and Java). The Python implementation should follow the structure of the existing implementations as closely as possible. This will make maintenance easier in the future.
Every implementation uses a shared set of test data, which are YAML files with input and expected output for different parts of the parser.
The parser consists of multiple components, and it may be easier to implement the library in the following order:
CucumberExpressionTokenizer
CucumberExpressionParser
TreeRegexp
CucumberExpression
CucumberExpressionGenerator
I would also recommend using TDD to guide the implementation. That is - start by writing the test for a component (reading in thest data from the relevant YAML file). Make the tests pass one by one, by making incremental improvements to the code.
If contributors are working in a team, it may be easier to collaborate if all contributors are committing to the same feature branch in the same repo (as opposed to using individual forks). Please add comment here if you want commit access so you can work on a branch in this repo.
Alternative text fails in this scenario:
Feature: vm compliance
Scenario: vm Sku compliance
Given I have azurerm_virtual_machine defined
Then it must have "vm_size"
And its value must be Standard_DS1_v2/Standard_DS2_v1
Terminal Output:
Failure: vm_size property in azurerm_virtual_machine.vm resource does not match with ^Standard_DS1_v2/Standard_DS2_v1$ case insensitive regex. It is set to Standard_DS1_v2.
And its value must be Standard_DS1_v2/Standard_DS2_v1
Failure:
Scenario to pass.
Terraform v1.1.7
Terraform Compliance v1.3.31
PowerShell 7.2.1
Win 11 OS, up to date.
Steps to reproduce the behavior:
Install Terraform Compliance v1.3.31
Extract plan.zip to folder, contains plan file and plan.json and z.feature
plan.zip
Run command 'terraform-compliance -p ./plan -f ./'
5.. See error in terminal output.
Is your feature request related to a problem? Please describe.
While working on #43 it struck me that all the parameter types that are java-specific (biginteger
, bigdecimal
, byte
, short
, long
, double
) don't need an explicit parameter type:
An {int}
parameter can be transformed to any of the following classes:
java.lang.Byte
java.lang.Short
java.lang.Integer
java.lang.Long
java.math.BigInteger
A {float}
parameter can be transformed to any of the following classes:
java.lang.Float
java.lang.Doublet
java.math.BigDecimal
Deciding what class to transform to would first use the class from the corresponding method parameter's Java type. If the Java type isn't available (e.g. for dynamically typed languages), the appropriate type would be chosen based on the string value - we'd try to fit it into the "biggest" type. E.g. "127" becomes java.lang.Byte
, "128" becomes java.lang.Short
etc.
A similar algorithm could be used for float
.
The main reason I want to do this is to solve the "different parameter types for different platforms" problem described in #43, but with a cleaner (IMO) solution.
Another reason I want to do this is to avoid having different parameter types for different platforms, which makes it harder to implement cross-platform tools (such as the Cucumber Language Server).
Describe the solution you'd like
The gist of the solution is described above, but I'd also like this to be backwards compatible. Using any of the deprecated types will print a warning, encouraging users to switch to {int}
or {float}
. Implementation-wise, the deprecated types would just be aliases for the {int}
or {float}
types.
Describe alternatives you've considered
#43 is an alternative I have considered.
Additional context
Add any other context or screenshots about the feature request here.
python CI broke on using pyflakes, I think, and in doing so, reported a traceback.
ImportError: cannot import name '_unicodefun' from 'click'
This issue in another Python-using project describes the problem:
dask/distributed#6013
black....................................................................Failed
- hook id: black
- exit code: 1
Traceback (most recent call last):
File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/bin/black", line 8, in <module>
sys.exit(patched_main())
File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/black/__init__.py", line 1372, in patched_main
patch_click()
File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/black/__init__.py", line 1358, in patch_click
from click import _unicodefun
ImportError: cannot import name '_unicodefun' from 'click' (/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/click/__init__.py)
flake8...................................................................Passed
Error: Process completed with exit code 1.
A passing CI for Python.
Well, this is here, in this repo, in CI.
See
https://github.com/cucumber/cucumber-expressions/runs/5733063101?check_suite_focus=true
I am working on updating a few packages in the Ruby cucumber stack in Fedora and I noticed the license file is missing from this library now, that wasn't the case with v14.
From what I understand the file should be present in the released gem as it's a copy of the software.
Not 100% sure how a proper fix should look like.
AFAICT, to build and then unpack/install the gem properly, the license has to be either on the same level with the .gemspec
file in the Ruby gem or some level below. A file cannot be referenced a level up from gemspec, so doing something like s.files << "../LICENSE"
is not going to work for unpacking the gem. Perhaps a Rakefile target that copies the LICENSE first and then builds the gem so that something like s.files << "./LICENSE"
would then be OK.
With #187 optimizations were submitted to create Cucumber expressions more efficiently when using Java. These changes are not applicable the other implementations as these have regular expression support build into the language itself.
Except .Net
Port the changes made in #187 to ExpressionFactory.cs and CucumberExpression.cs.
For regularExpressions it is already possible to parse them into groups with a public method, a similar approach should be possible for the CucumberExpressionsParser as well (maybe the Groups Aproach could be reused here...)
I have a project tested with Cucumber 7.9.0:
The Cucumber tests run in about 10.2 seconds on average : that's not so bad (we have plain Java, no Spring, no Mockito). But we wanted to have an even faster feedback loop for the developer.
Thus, we made some profiling and found that ExpressionFactory.createExpression(String)
was eating a lot of CPU (this method is called more than 64'000 times for our project).
We improved the code with two different variants and made some benchmark using JMH microbenchmark framework :
The benchmark results are the following :
Benchmark Mode Cnt Score Error Units
ExpressionFactoryBenchmark.createExpression0 thrpt 25 234230,747 Β± 4693,108 ops/s
ExpressionFactoryBenchmark.createExpression1 thrpt 25 347290,522 Β± 4297,255 ops/s
ExpressionFactoryBenchmark.createExpression2 thrpt 25 356142,244 Β± 9833,928 ops/s
When using the createExpression2 variant on our project, the cucumber tests run in 9.0 seconds on average. That's a 1.2 second improvement (12%).
Thus, we suggest to replace current ExpressionFactory.createExpression(String)
implementation by the one from our createExpression2.
You can find in annex the three different variants, unit testing to ensure that all three variants behave the same and the JMH benchmark code.
I'm using Cucumber 7.9.0 with the following Maven dependencies:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
<exclusions>
<exclusion><!-- version 1.1.2 from cucumber conflicts with version 1.1.0 from junit -->
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
<exclusions>
<exclusion><!-- conflicts with the version from junit-platform-suite -->
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
The JMH micro benchmark is provided in ExpressionFactoryBenchmark
.
Steps to reproduce the behavior:
Creating step definitions like below are not matched to the feature.
It works when making it a regular expression (instead of cucumber expression) as mentioned in Docs.
Step definitions should be matched correctly to the feature without having to use a regular expression.
WebdriverIO 7.16.13
Cucumber 7.3.2
Cucumber-expressions 15.0.2
TypeScript 4.5.5
Not working:
Given("The user selects {string} from the (country|language) dropdown", function(toBeSelectedValue: string, dropdownType: string){
console.log(toBeSelectedValue)
console.log(dropdownType)
})
Working:
Given(/^The user selects (.*) from the (country|hospital) dropdown$/, function(toBeSelectedValue: string, dropdownType: string){
console.log(toBeSelectedValue)
console.log(dropdownType)
})
Feature file:
Feature: Select dropdown value
Scenario: A value is selected
Given The user selects "Norway" from the country dropdown
Currently optional text is only good for a plural s and the like.
So I have {int} cucumber(s) in my belly
matches I have 1 cucumber in my belly
.
I want to implement even less variants of identical test steps by having a more powerful optional text.
I should not see the population (anymore)
to match I should not see the population
.I should be (back) on the start page
to match I should be on the start page
.sehe ich (wieder) den/die Breadcrumb(s) {string}
to match sehe ich den Breadcrumb "Start"
.Ugly workarounds:
I am on the start page
(space at the end)I am on the start page( again)
To be honest, I am somewhat surprised this doesn't work out of the box. See also the original discussion in another project.
This text was originally generated from a template, then edited by hand. You can modify the template here.
I am considering using the golang implementation of cucumber-expressions in https://github.com/regen-network/gocuke and noticed that bigdecimal
is mapped to https://pkg.go.dev/math/big#Float.
The documentation for big.Float
states that this is structured as sign Γ mantissa Γ 2**exponent
. This is not a decimal number! It is a base-2 floating point number, not base-10 as required for decimals.
Java does provide a proper BigDecimal where the documentation clearly states that the structure is unscaledValue Γ 10-scale
.
The mapping of bigdecimal
to big.Float
should be removed in the golang package and consumers should be allowed to register a correct decimal implementation (none is provided by the standard library).
Also in the golang implementation, it seems like it's an error to override a built-in type mapping. It might be worth reconsidering this in case users want to swap out other type mappings.
Two correct golang decimal implementations are: https://pkg.go.dev/github.com/cockroachdb/apd/v3 and https://pkg.go.dev/github.com/ericlagergren/decimal/v3.
This text was originally generated from a template, then edited by hand. You can modify the template here.
I triggered a release of 15.1.0 and the python release failed
π¦ π
The "try" as part of cucumber-expression javascript implementation oversize it and add a lot of dependency to cucumber-expression itself. It could live on its own, being inside its own repo, or within a dedicated folder within this repo
Refs. #104 (comment)
I would put it its in own repo actually, thus it would not interfere with cucumber-expression at all
This text was originally generated from a template, then edited by hand. You can modify the template here.
I want to provide syntax highlighting (LSP semantic tokens) for the parts of a Gherkin step that is optional in the corresponding Cucumber Expression.
Use named capture groups for optionals, as explained in cucumber/language-service#57
The return type of match
would change from Argument[] | null
to Match | null
:
export type Match = {
arguments: readonly Argument[]
optionals: readonly Optional[]
}
export type Optional = {
group: Group
}
If we want to support this, I can't think of any other option.
Attempting to use a negative number in a step that expects a{double}
, in a test case written in Norwegian, doesn't work.
Minimal example:
# language: no
Egenskap: Betalingseksempel
Scenario: Negativ rest
Gitt at du har 10,04 kr
NΓ₯r du betaler 12,15 kr
SΓ₯ skal du ha -2,11 kr igjen
package com.example.stepdefs;
import io.cucumber.java.no.Gitt;
import io.cucumber.java.no.NΓ₯r;
import io.cucumber.java.no.SΓ₯;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PaymentStepDefs {
private double balance;
@Gitt("at du har {double} kr")
public void initialBalance(double balance) {
this.balance = balance;
}
@NΓ₯r("du betaler {double} kr")
public void makePayment(double payment) {
this.balance = this.balance - payment;
}
@SΓ₯("skal du ha {double} kr igjen")
public void assertRemainingBalance(double expected) {
assertEquals(expected, this.balance);
}
}
Cucumber is unable to parse the number:
io.cucumber.core.exception.CucumberException: Could not convert arguments for step [skal du ha {double} kr igjen] defined at 'com.example.stepdefx.PaymentStepDefs.assertRemainingBalance(double)'.
at io.cucumber.core.runner.PickleStepDefinitionMatch.couldNotConvertArguments(PickleStepDefinitionMatch.java:112)
at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:56)
at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10)
at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:84)
at io.cucumber.core.runner.TestStep.run(TestStep.java:56)
at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:51)
at io.cucumber.core.runner.TestCase.run(TestCase.java:84)
at io.cucumber.core.runner.Runner.runPickle(Runner.java:75)
at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.lambda$runTestCase$4(CucumberEngineExecutionContext.java:112)
at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:137)
at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23)
at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:137)
at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.runTestCase(CucumberEngineExecutionContext.java:109)
at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:168)
at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:90)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: io.cucumber.cucumberexpressions.CucumberExpressionException: Failed to parse number
at io.cucumber.cucumberexpressions.NumberParser.parse(NumberParser.java:41)
at io.cucumber.cucumberexpressions.NumberParser.parseDouble(NumberParser.java:21)
at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.doTransform(BuiltInParameterTransformer.java:80)
at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.transform(BuiltInParameterTransformer.java:22)
at io.cucumber.cucumberexpressions.ParameterTypeRegistry$8.transform(ParameterTypeRegistry.java:114)
at io.cucumber.cucumberexpressions.ParameterTypeRegistry$8.transform(ParameterTypeRegistry.java:111)
at io.cucumber.cucumberexpressions.ParameterType$TransformerAdaptor.transform(ParameterType.java:299)
at io.cucumber.cucumberexpressions.ParameterType.transform(ParameterType.java:259)
at io.cucumber.cucumberexpressions.Argument.getValue(Argument.java:42)
at io.cucumber.core.stepexpression.ExpressionArgument.getValue(ExpressionArgument.java:17)
at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:47)
... 15 more
Caused by: java.text.ParseException: Unparseable number: "-2,11"
at java.base/java.text.NumberFormat.parse(NumberFormat.java:434)
at io.cucumber.cucumberexpressions.NumberParser.parse(NumberParser.java:39)
... 25 more
The text should be parsed to double value -2.11
.
Cucumber-java 12.15
I've been looking at how cucumber-expressions could be adopted in Behat.
One feature of our existing two pattern syntaxes (regex and turnip) is that they can name the arguments.
e.g.
@When /^I eat (?<count>[0-9]+) (?<fruit>.*)$/
@When I eat :count :fruit
This allows Behat to match arguments based on name as well as position, letting users transpose the argument order if necessary.
That can be useful if multiple patterns are attached to one step, something allowed in Behat:
/**
* @When I eat :count :fruit
* @When :count of the :fruit are eaten
* @When I don't eat any :fruit
*/
public function myStepDef(string $fruit, int $count=0): void
The new feature would be for Cucumber Expressions to capture argument names:
{fruit:string}
)Then it would be up to the Cucumber implementation to either use the names for matching to the step definition, or to ignore them and use the order directly as currently happens.
I'm implementing Cucumber Expressions
parser in Rust: WIP. And grammar described in the ARCHITECTURE.md
really bothers me for couple of reasons.
alternation
definitionCucumber Expressions
and I don't really see reasons to do it.So implementing Cucumber Expression
parser according to the docs leads to unnecessary performance overhead for no obvious reasons.
Can't we just provide exact grammar for Cucumber Expressions
without Note:
section?
If I didn't miss anything provided grammar should be exact Cucumber Expressions
cucumber-expression = (alternation
| optional
| parameter
| text)*
text = (- text-to-escape) | ('\', text-to-escape)
text-to-escape = '(' | '{' | '/' | '\'
alternation = single-alternation, ('/', single_alternation)+
single-alternation = ((text-in-alternative+, optional*)
| (optional+, text-in-alternative+))+
text-in-alternative = (- alternative-to-escape) | ('\', alternative-to-escape)
alternative-to-escape = ' ' | '(' | '{' | '/' | '\'
optional = '(', text-in-optional+, ')'
text-in-optional = (- optional-to-escape) | ('\', optional-to-escape)
optional-to-escape = '(' | ')' | '{' | '/' | '\'
parameter = '{', name*, '}'
name = (- name_to_escape) | ('\', name-to-escape)
name-to-escape = '{' | '}' | '(' | '/' | '\'
Because of this not all acceptance tests are executed. This pattern may be repeated elsewhere.
On my project with about 150 stepdefs and about 400 test scenarios, the IntelliJ profiler says the CucumberExpression.<init>
method takes 25.9% of the total CPU time. This is because the method is called for all step defs and for all test scenarios. I think the performance could be better.
I expect CucumberExpression.<init>
to avoid unnecessary processing (contributes to #2035).
I understand that cucumber-java8
can introduce dynamic behavior which requires parsing the expressions for each test scenario. However, I think we can safely cache everything that is constant and does not depend on cucumber-java8
. I identitifed the following performance improvement points in CucumberExpression
:
TreeRegex
creation: in CucumberExpression
constructor, this object serves to get some "metadata" about a regular expression itself (i.e. not depending on context). Thus, two identical regular expressions will lead to the same TreeRegp
, so the creation is cacheable.
The original code:
this.treeRegexp = new TreeRegexp(pattern);
could be replaced by (treeRegexps
is a static Map<String, TreeRegexp>
):
this.treeRegexp = treeRegexps.computeIfAbsent(pattern, TreeRegexp::new);
calls to escapeRegex
in the rewriteToRegex
method are done on the Node.text()
content: two identical Node.text()
will lead to the same escaped result, independently of the context. Thus, the result of escapeRegex
is cacheable.
The original code:
return escapeRegex(node.text());
can be replaced by (escapedTexts
is a static Map<String, String>
):
return escapedTexts.computeIfAbsent(node.text(), CucumberExpression::escapeRegex);
These two optimization points lead to four combinations to be benchmarked (original version is createExpression0
). The benchmark consists in creating 400 times five different expressions:
Benchmark | cached calls to escapeRegex | cached TreeRegex creation | ops/s |
---|---|---|---|
CucumberExpressionBenchmark.createExpression0 | no | no | 153,024 Β± 13,800 |
CucumberExpressionBenchmark.createExpression1 | yes | no | 181,960 Β± 12,133 |
CucumberExpressionBenchmark.createExpression2 | no | yes | 186,236 Β± 11,232 |
CucumberExpressionBenchmark.createExpression3 | yes | yes | 219,890 Β± 12,365 |
Caching the TreeRegex
creation lead to 22% performance improvement and using both methods lead to 44% performance improvement.
On a real project with about 150 stepdefs and 400 test scenarios, the IntelliJ Profiler runs is about 7700 ms and says that CucumberExpression.<init>
is:
I suggest to use the variant createExpression3 and I would be happy to propose a PR.
Cucumber 7.10.1
The benchmark with the four variants is in
cucumberexpressions.zip
Steps to reproduce the behavior:
Create a Maven project with the following dependencies:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.36</version>
<scope>test</scope>
</dependency>
Run the benchmark
Hello,
It would be nice if Cucumber Expressions support non-capturing groups.
My intension is to have a mapping for the following step:
When I order 2 apples
and the step definitions mapping to be something like that
@When("I order {int} [fruit]")
public void orderFruits(int fruitNumber){
........
}
Where the square brackets will denote non-capturing group and the expression [{context_description}] would match any text, but wont pass it as a parameter to the step deifinition method
The source map reference the TypeScript source code but they are not included in the package.
e.g. in ./dist/esm/src/index.js.map
:
{...,"sources":["../../../src/index.ts"],...}
But since the source are not included in the package, when bundling with webpack, it fails with:
WARNING in ../../node_modules/.pnpm/@[email protected]/node_modules/@cucumber/cucumber-expressions/dist/esm/src/index.js
Module Warning (from ../../node_modules/.pnpm/[email protected][email protected]/node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from '...\node_modules\.pnpm\@[email protected]\node_modules\@cucumber\cucumber-expressions\src\index.ts' file: Error: ENOENT: no such file or directory, open '...\node_modules\.pnpm\@[email protected]\node_modules\@cucumber\cucumber-expressions\src\index.ts'
Include the TypeScript source code in the package. i.e.:
// package.json
{
"files": [
...,
"src" // <-- add this
]
}
@cucumber/cucumber-expressions
: 16.1.2
This involves webpack, will need to find time to create a repro.
But the issue itself should be simple enough to understand directly.
We want to make sure this project is convenient to hack on for new contributors.
We currently have the Makefiles imported from the monorepo, which can build all language versions at once. Do we still need those?
Should we provide a Docker environment (or multiple per-language environments) to help contributors build the projects?
The CONTRIBUTING.md
file is empty.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.