Skip to content

Conversation

@kieran-ryan
Copy link
Member

🤔 What's changed?

  • Support the parse step definition matcher of pytest-bdd - which is also used by behave; both Python frameworks

⚡️ What's your motivation?

🏷️ What kind of change is this?

  • ⚡ New feature (non-breaking change which adds new behaviour)

♻️ Anything particular you want feedback on?

📋 Checklist:

  • I agree to respect and uphold the Cucumber Community Code of Conduct
  • I've changed the behaviour of the code
    • I have added/updated tests to cover my changes.
  • Users should know about my change
    • I have added an entry to the "Unreleased" section of the CHANGELOG, linking to this pull request.

This text was originally generated from a template, then edited by hand. You can modify the template here.

@kieran-ryan kieran-ryan added the ⚡ enhancement Request for new functionality label Apr 23, 2024
@kieran-ryan kieran-ryan self-assigned this Apr 23, 2024
@neskk
Copy link
Contributor

neskk commented Sep 21, 2024

Hello @kieran-ryan,

Found some time to help test this PR.

  • I've added some pytest-bdd parser step definitions to the StepDefinitions.py test file, but I have the ExpressionBuilder tests failing.
  79 passing (1s)
  22 failing

  1) ExpressionBuilder
       with NodeParserAdapter
         builds parameter types and expressions from tsx source:

      AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected

+ []
- [
-   'a {uuid}',
-   'a {date}',
-   'a {planet}',
-   /^a regexp$/,
-   "the bee's knees"
- ]
  • Since my node/vs-code development experience is very limited, I don't know what's the best procedure to test this or contribute with unit-tests. I would appreciate some help. Will the support for this kind of pytest-bdd syntax be hard to merge?

@neskk
Copy link
Contributor

neskk commented Dec 12, 2024

Hello @vianney-g, did you manage to test this locally? Are you using pytest-bdd parser syntax:

@given(parsers.parse("a stub with {stub_id} exists"))
@given(parsers.parse("a stub exists with:" + os.linesep + "{stub_str}"))

I wish to have support for pytest-bdd parser syntax: https://pytest-bdd.readthedocs.io/en/stable/#step-arguments, including support for custom param types.

I can try to run some experiments locally, I just need to grab your changes and install the extension on my dev environment.

@vianney-g
Copy link

Hello @vianney-g, did you manage to test this locally? Are you using pytest-bdd parser syntax:

@given(parsers.parse("a stub with {stub_id} exists"))
@given(parsers.parse("a stub exists with:" + os.linesep + "{stub_str}"))

I wish to have support for pytest-bdd parser syntax: https://pytest-bdd.readthedocs.io/en/stable/#step-arguments, including support for custom param types.

I can try to run some experiments locally, I just need to grab your changes and install the extension on my dev environment.

hi @neskk , ty for your interest.

I was just facing the bug with pytest_bdd and I was wondering how to fix it. Then I discovered your PR. Then I realized it may not match all the cases. I think the query should match the following steps definitions:

import pytest_bdd
from pytest_bdd import given, parsers, when, then
from pytest_bdd.parsers import parse


@pytest_bdd.given(parse("a stub with stub_id exists"))
@given(parse("a stub with {stub_id} exists"))
@when(parsers.parse("a stub with {stub_id} exists"))
@then(parsers.parse("a stub with {stub_id} exists" + " then"))
def test_stub_exists(stub_id):
    assert stub_id == "stub_1"
    return stub_id


@not_a_step(parse("a stub with {stub_id} exists"))
def test_not_match(stub_id):
    assert stub_id == "stub_1"
    return stub_id

Please forget my suggestions above. I made some tests locally, and I think the following approach is better to match all the cases (I am not a pro of treesitter queries, I am just playing with):

(decorator 
  (call
    arguments: 
    (argument_list
      (call
	arguments:
	(argument_list
	  [(_) @pattern]
	  ) @args
	) @parse
      )
    ) @decorator
  (#match? @decorator "given|when|then")
  (#match? @parse "parse")
  )

image

@neskk
Copy link
Contributor

neskk commented Dec 13, 2024

I never looked into treesitter and the TypeScript/node.JS environment is still a bit foreign to me.
This MR is from @kieran-ryan, but I haven't seen him around for a while.
I have some more test cases we should use to validate correct parsing, I will try to test them locally and post my result with your changes.

@vianney-g
Copy link

You can adapt the python test file to reflect the different approaches. This patch works for me:

--- a/src/language/pythonLanguage.ts
+++ b/src/language/pythonLanguage.ts
@@ -95,46 +95,28 @@ export const pythonLanguage: Language = {
     `(decorated_definition
       (decorator
         (call
-          function: (identifier) @method
           arguments: (argument_list (string) @expression)
-        )
+        ) @method
       )
       (#match? @method "(given|when|then|step)")
     ) @root`,
     // pytest-bdd
-    `(decorator
-      (call
-        function: (identifier) @annotation-name
-        arguments: (argument_list
-          (call
-            function: (attribute) @parser
-            arguments: (argument_list
-              [
-                (string) @expression
-              ]
-            )
-          )
-        )
-      )
-      (#match? @annotation-name "given|when|then|step")
-      (#match? @parser "parser")
-    ) @root`,
-    `(decorator
-      (call
-        function: (identifier) @annotation-name
-        arguments: (argument_list
-          (call
-            function: (attribute) @parser
-            arguments: (argument_list
-              [
-                (binary_operator) @expression
-              ]
-            )
-          )
-        )
+    `(decorated_definition
+      (decorator
+        (call
+          arguments:
+          (argument_list
+            (call
+              arguments:
+              (argument_list
+                [(_) @expression]
+              )
+            ) @args
+          ) @parse
       )
-      (#match? @annotation-name "given|when|then|step")
-      (#match? @parser "parser")
+    ) @annotation-name
+    (#match? @annotation-name "given|when|then|step")
+    (#match? @parse "parse")
     ) @root`,
   ],
   snippetParameters: {
diff --git a/test/language/testdata/python/StepDefinitions.py b/test/language/testdata/python/StepDefinitions.py
index cc61457..406e17e 100644
--- a/test/language/testdata/python/StepDefinitions.py
+++ b/test/language/testdata/python/StepDefinitions.py
@@ -1,19 +1,23 @@
 """Port of givens for testdata."""

 from behave import step, given, when, then
+from pytest_bdd.parser import parse
+from pytest_bdd import parser

+import pytest_bdd

-@step("a {uuid}")
+
+@step(parse("a {uuid}"))
 def step_given(context, uuid):
     assert uuid


-@given("a {date}")
+@given(parser.parse("a {date}"))
 def step_date(context, date):
     assert date


-@when("a {planet}")
+@pytest_bdd.when("a {planet}")
 def step_planet(context, planet):
     assert planet

@neskk
Copy link
Contributor

neskk commented Dec 16, 2024

Thanks for sharing the patch with your changes @vianney-g. The diff is against the main branch or the branch of this MR?

Is your test flow like, run npm install, npm test, and then open a VSCode instance on a pytest-bdd project?
I'm having an issue (Win11 node v.18.20.5) getting the library to install (npm install):

Compiling node_modules\tree-sitter-go inside docker
Failed to build wasm for node_modules\tree-sitter-rust: Command failed: node_modules/.bin/tree-sitter build-wasm node_modules\tree-sitter-rust --docker
'node_modules' is not recognized as an internal or external command,
operable program or batch file.

@vianney-g
Copy link

Hey @neskk ! This is a patch for the branch of this PR.

I can run the tests with npm test, and it works. The compilation step ran successfully on my side. I am on my phone now, I will check again with my laptop if I did something special to make it work.

@vianney-g
Copy link

So, I confirm that everything worked well on my side with npm install && npm test. I am sorry, but I don't understand what is going wrong on your side. I am on node v18.17.1 on a Linux machine.

@neskk
Copy link
Contributor

neskk commented Jan 8, 2025

Happy New Year @vianney-g,
I finally managed to run the tests using WSL to emulate Linux environment.
I can confirm that your changes seem to work fine in most situations.
One situation where the parsing fails is when the step-def text is a concatenation of strings, e.g.:

@then(parse("the" + "bee's" + "knees"))
def step_bees(context, expression):
    """Test Re."""
    assert expression

Currently the returned text is: the" + "bee's" + "knees, AND, only works when used with the parsers.parse( ) syntax.
I think the original implementation does not attempt to support string concatenation, and for simplification I think we should leave it for a separate MR.

My suggestion is to use the new format, but instead of catching any expressions inside the parse call: [(_) @expression] we use (string) @expression.

I realized you made the changes in the first query (non pytest-bdd) to allow something like @pytest_bdd.when(), but I also think it's probably not a great pattern to support.

Can you open a MR to push these changes and see if we can get them merged, or you want me to do it?

@neskk
Copy link
Contributor

neskk commented Jan 8, 2025

I just realized that custom params are also not supported:

from parse import with_pattern
from pytest_bdd import parsers, step, given, when, then
from pytest_bdd.parsers import parse

@with_pattern("GET|HEAD|POST|get|head|post")
def http_verb(text: str) -> str:
    return text

custom_types = {
    "valid_http_verb": http_verb,
}

@then(parse("make" + "{http_verb:valid_http_verb} request", custom_types))
def step_bees(context, http_verb):
    """Test Re."""
    assert http_verb

Running unit-tests it only captures:
'ustom_type'

@neskk
Copy link
Contributor

neskk commented Jan 14, 2025

Hello @kieran-ryan and @vianney-g
I think I have refined the tree-sitter queries to support both formats (plain/cucumber expressions, and PyPi Parse (pytest-bdd).
I ended up reading a bit of Tree-sitter documentation and I think I managed to develop a nice approach.
I also added support for string concatenation (binary_operator). It is now working both for the standard and parser syntax.

@kieran-ryan (updated) I just submitted PR #236 with the adjusted syntax.

@neskk
Copy link
Contributor

neskk commented May 14, 2025

@luke-hill I think we can close this MR as #236 was based of this and superseeds it. Thanks ;)

@luke-hill luke-hill closed this May 14, 2025
@luke-hill luke-hill deleted the feat/pytest-bdd branch May 14, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡ enhancement Request for new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pytest-BDD parse matcher step definitions

4 participants