cucumber / cucumber-ruby

Cucumber for Ruby. It's amazing!
https://cucumber.io
MIT License
5.18k stars 1.11k forks source link

Deprecate "calling steps from step definitions" functionality #1362

Open aslakhellesoy opened 5 years ago

aslakhellesoy commented 5 years ago

Summary

Calling steps from step definitions is one of the features I regret having added to Cucumber. It's like a poor man's implementation of subroutines (which all languages support natively) with the following drawbacks:

Using this functionality should print:

Calling steps from step definitions is deprecated and will be removed in the next major version. For a better alternative, follow this link: XXX

The XXX link should point to a page in the documentation explaining in more details how to use fiunctions/methods with Cucumber. It can be based on the explanation I gave in https://github.com/cucumber/cucumber-js/issues/1232

marnen commented 5 years ago

You mean deprecating the step and steps methods? Please don’t do this. While I agree that it’s more generally useful to use methods to share code, there are some circumstances where Gherkin is the right tool for sharing code between steps.

One that comes up frequently for me is the notion of scoping output:

Then 'I should see "$text"' do |text|
  expect(page).to have_text text
end

Then 'I should see a cat GIF' do
  # code for locating cat GIF element
end

Then /^(.+) within (.+)$/ do |step_text, region_name|
  within selector_for(region_name) do
    step(step_text)
  end
end

...so that I can do Then I should see "hello" within the sidebar or Then I should see a cat GIF within the sidebar without much trouble. How would you advise doing this maintainably without step?

luke-hill commented 5 years ago

Use ruby's own send method if you want to metaprogram or program on the fly.

Use Helpers or Classes for specific pieces of reusable code

Use case / if logic to decide based on what text which class / helper to instantiate and use

marnen commented 5 years ago

@luke-hill:

Use ruby's own send method if you want to metaprogram or program on the fly.

Use Helpers or Classes for specific pieces of reusable code

I’m quite familiar with both these techniques, but I don’t see how either would be useful in the case I described. Do you?

Use case / if logic to decide based on what text which class / helper to instantiate and use

The Gherkin parser already does pretty much that, and I would rather reuse it than reimplement it. This idea seems like a step backwards to me.

If you need the problem stated in a more generic form: I have a modifier that I would like to be able to apply to any arbitrary Gherkin step from within the scenario. I only see one maintainable way to do that, and it’s this:

Then /^(.+) with modifier$/ do |step_text|
  with_modifier { step step_text }
end

How else would you propose to implement this, without maintaining a separate table of step definitions? I don’t understand how I can do this if step is removed, and I’d appreciate specific ideas, not just generic suggestions.

luke-hill commented 5 years ago

Helpers::ReUsableSteps.step_one Helpers::ReUsableSteps.step_two Helpers::ReUsableSteps.step_three

That's a primitive one that would allow you to call them. If they were module functions mixed into the World. You could then call your steps based on the step_text so you just send call them.

Also I would advocate having one "mega-step", that does 10 different things, and actually have 10 small steps, even if portions are re-used. It makes debugging easier. And in-fact most of our open source stuff we're un-coupling steps, i.e. removing the areas that do 2 different things for 2 different steps that do 1 thing.

for your example you could do.

Then /^(.+) with modifier$/ do |step_text|
  with_modifier { Helpers::ReUsableSteps.send(step_text) }
end
marnen commented 5 years ago

@luke-Hill No, that wouldn’t work at all. If you look again at my example, you’ll notice that step_text has the form of a Gherkin step (e.g. I should see "foo"), not a Ruby method name (e.g. step_one). That being the case, I don’t see how send would be helpful, unless you’re thinking of using method_missing to deal with receiving Gherkin text as a message...

...and if you are, then once again we need to map from Gherkin text to step definition, and so we’re right back to needing the step method.

Or did I miss something? I’d love to better understand what you had in mind, but so far it doesn’t seem like a suitable solution.

luke-hill commented 5 years ago

I don't want to go into exactly how to deal with your situation line by line, but you simply need a 1 to 1 mapping between what you capture and what you send.

It would work, because you're assuming you have to send a step which has spaces in, but you could sanitize that. There are many things you would perhaps need to do, unique to your situation.

Your step_text is simply a capture. How you translate that capture into something that can be sent you can decide (You could use a massive case, when statement for example).

Essentially the methodology you have of having a "master step" which then delegates to a multitude of other steps is something I would disuade in most circumstances, it's too hard to triage, too taxing for newcomers and relies heavily on almost a bus-factor style approach where a few siloed team members know everything. It's currently an area in aruba we are "un-DRYing" if you like to think of it that way.

See https://github.com/cucumber/aruba/pull/666 perhaps for a code explanation?

EDIT: This has massively gone off tangent from the original placeholder, which is to deprecate the usage of the steps and step methods. Which will be done at some point during the v4 lifecycle I imagine.

marnen commented 5 years ago

@luke-hill:

I don't want to go into exactly how to deal with your situation line by line,

I’ll admit I was sort of hoping for that. I’m very experienced with both Cucumber and Ruby, but I can’t figure out how to implement your suggestions in a way that makes sense. However, if that’s more appropriate for the mailing list or something, we can take it there.

but you simply need a 1 to 1 mapping between what you capture and what you send.

Correct. And that’s what the step method already gives me, which is why I don’t want to see it go away without a suitable replacement in place.

Your step_text is simply a capture. How you translate that capture into something that can be sent you can decide (You could use a massive case, when statement for example)

Are you seriously proposing that? The Gherkin step definitions already provide a perfectly suitable translation. A massive case statement would just reimplement that, for no benefit that I can see.

In other words: I already know how I want to translate that capture. I want to translate it the exact same way that Gherkin would if it were a step by itself. That’s why I think I need step here. Is there another way to achieve that goal without step? If there were, I’d use it, but so far I’m not seeing it.

This has massively gone off tangent from the original placeholder, which is to deprecate the usage of the steps and step methods.

As far as I can tell, this is exactly on topic for that. The whole point of my comments here is that we shouldn’t deprecate those methods, because they make possible some very useful abstractions that AFAIK can’t be implemented in any other way.

I hope I’ve explained my use case pretty clearly here, but all I’ve gotten is rather glib generic answers that won’t actually work in the situation I described as far as I can tell.

I find this extremely frustrating. If the core team is bound and determined to deprecate step, then please help me find an alternative for the one use case I know of where it seems to be indispensable.

Which will be done at some point during the v4 lifecycle I imagine.

Then if another workable solution is not provided, I’ll either have to abandon Cucumber at that time (which would be a huge pity) or maintain my own fork or plugin (which I really don’t want to put the time into doing).

luke-hill commented 5 years ago

Everyone who works on cucumber are volunteers. Whilst there is a small amount of funding available for fixing and tracking issues, looking for custom solutions is unlikely without a serious injection of funds. There is an OpenCollective account visible here: https://opencollective.com/cucumber

In short, the 2 key takeaway points here are you can re-abstract your use cases in a language specific way see https://github.com/cucumber/cucumber-js/issues/1232 for more info (Which I see you've commented on).

Also that in order to maintain this abstraction, it provides the user with no discernable benefits, and just a mountain of issues. Using a language specific abstraction would only provide benefits, and no drawbacks.

Now as to your specific use case. I can't comment on it specifically, but if I was to hazard a guess, you may be in a situation similar to what I had at a previous company and/or what we have in aruba (step is being called in lots of places).

Refactoring a large app is something which is an arduous task I admit, but keeping it as a monolith has issues. Here as I see it you have a simple choice (Both are valid options)

Again both are valid. However with the first case, you gain a (arguable), advantage that you can use the latest versions. In the same way that maintaining a Windows95 OS is viable, but don't then expect to be able to run NVidia Turing technology with 16-AA (Not sure if you get this reference, apologies if not).

If you are very experienced with Ruby, then you should know that using language specific abstractions, such as Helpers, Classes and more indepth stuff such as Singletons or Anonymous classes, all come with large stacktraces and good debugging tools. I know (From reasonable personal experience), that using steps especially ones with 5+ calls inside a single step with interpolated parts and metaprograming, only leads you to a highly coupled system.

We are not advocating for one minute that our way is the only way or the highway. What we are advocating is we believe this is the way the software "should" work. People are completely within their rights to disagree, however, we would like users to attempt to use workarounds where explained, or try to understand the logic behind our decision-making.

In this instance, the logic is simple, deprecate something which is old, and not best served in Ruby (It has already been removed from other languages). You can use a 1-1 mapping as I've mentioned to solve your issue. But short of doing the work for you (Which I'm sure you'd expect, would be perhaps crossing a line), you need to perhaps spike a few different solutions for yourself.

If you want further reading, check a lot of the aruba library between around 0.11 and 0.14.1 and then compare it to 0.14.11 and 1.0.pre versions. We're actually uncoupling steps, not re-coupling them. This sounds counter productive, but sometimes in tests, less-DRY is better

I hope this has explained things better. But if not I suggest doing some reading into some of the points, checking out the code areas I've suggested or maybe (Ability in other languages not known), checking Java or other versions of cucumber to see how other people have conquered this issue (It's not just a ruby issue)

marnen commented 5 years ago

@luke-hill:

Everyone who works on cucumber are volunteers.

I’m aware of this, of course. And thanks for your time and energy!

you may be in a situation similar to what I had at a previous company and/or what we have in aruba (step is being called in lots of places).

Nope. I’m in exactly the situation I said I was in: I have one place in the codebase where I apparently need to use step, for reasons that I’ve explained elsewhere in this thread. I don’t like step that much myself, and I’d be happy if I could get completely rid of it, but I don’t see how.

I have a feeling we’re talking past each other in this regard, because you keep suggesting solutions that are not relevant to my use case, and you have made several guesses about my use case that have nothing to do with anything I’ve said about it. I’ll be happy to clear up any further confusion about what I’m trying to do, but I don’t know how else to say what I’ve already said.

If you are very experienced with Ruby, then you should know that using language specific abstractions, such as Helpers, Classes and more indepth stuff such as Singletons or Anonymous classes, all come with large stacktraces and good debugging tools.

I’m aware of that. It is not relevant to my question, because (1) I have never found debugging these steps to be a problem and (2) as far as I can tell, those abstractions do not provide me what I need here. step does.

I know (From reasonable personal experience), that using steps especially ones with 5+ calls inside a single step with interpolated parts and metaprograming, only leads you to a highly coupled system.

That’s not what I’m doing. That’s not something I’d ever do. Please read again my description of the technique I am using. I am making one call to step. One. And I’d love to get rid of it, but I haven’t found a way to, and your suggested workarounds are starting to seem to confirm that there isn’t one.

We are not advocating for one minute that our way is the only way or the highway.

From my perspective, that sure does seem like what is going on here. You (the core team, not you personally) are trying to remove something that works well and doesn’t have a good alternative, because it seems like it “shouldn’t” be in there.

You can use a 1-1 mapping as I've mentioned to solve your issue.

step is the 1-1 mapping I need. How can I replace it without building a complete duplicate of the Gherkin step definitions table?

But short of doing the work for you (Which I'm sure you'd expect, would be perhaps crossing a line), you need to perhaps spike a few different solutions for yourself.

I’m not asking you to do my work for me. But: when you deprecate a feature that people have come to rely on, the onus is on you to suggest an alternative approach that actually replaces the deprecated feature. Certainly I’d do that if anyone asked a similar question about a deprecation in any of the libraries I maintain.

The crux of my problem is that I want to do the following:

  1. Capture some text.
  2. Map the captured text to a block of code in exactly the same way that Gherkin does.
  3. Execute that block in a particular context.

1 and 3 are trivial. I cannot see how to do 2 without step. This is the part for which I am once again asking for a suggestion, or recommending that step be taken off the deprecation list.

luke-hill commented 5 years ago

1) Capture the text in the same way 1.1) Either during 1, or before 2, have some transformer, which maps the text to the helper / class / singleton you want. Examples could be rolling your own, using activesupport, using a class extension / monkeypatch or a wrapper method i.e. sanitize(gherkin capture) 2) Once transformed, map this text and call your new functionality, either using send or directly having the new methods defined 3) If you're looking to pass a block through the caller chain, then the block must be defined in your steps. So either you need to re-define your block in the new method signature, or pass it through using the &block / block_given? methods which will help here.

badeball commented 5 years ago

I'm sad to see this go away, for the same reason as @marnen has outlined.

Either during 1, or before 2, have some transformer, which maps the text to the helper / class / singleton you want.

As @marnen has already attempted to explain - this is what we're already doing with Cucumber. You seem to be proposing we implement another mapping between natural text and code, mimicking that we already have (with Given / When / Then + Regexp), which seems so non-sensical to me.

I can appreciate the argument that this feature is hard to maintain, but you're not succeeding in proposing an alternative solution.

luke-hill commented 5 years ago

What you currently do is the following.

1) Capture text 2) Use a mapping - "regex string match" 3) From this 1-1 match, fire a new method (These are the cucumbers internals which are not exposed, and how they are work is confusing to explain, not 100% clear and completely abstract from the MRI or JRuby)

What I am proposing is this

1) Capture text 2) Use a mapping (Lets say for arguments snake case, because I've tried to hand-hold a bit and not had much luck 3) From this 1-1 match, fire a new method (These are now 100% traceable and better than anything previously).

Given I'm a healthy contributor, I'm aware this sounds bad, but you need to trust me that the new methodology is better. I'm speaking as someone who had at a previously company a healthy amount of step usages, and the stacktraces in the cucumber html reports were always a little bit messy.

Also I've attached a rudimentary code example.

Previously

# code omitted
step captured_regex
# code omitted

Now

# code omitted
Kernel.public_send MyHelper::Whatever::OtherNamespace.snake_case captured_regex
# code omitted

EDIT: There are also about 3 or 4 other ways of doing it, if you don't want to mix in the method to the global NS

Obviously I've made up a long namespace to illustrate it could be whatever.

I'm not keen on this backwards and forwards motion because it's all theoretical, could you maybe have a go at doing it, or perhaps illustrate with a git repo why it wouldn't work. Sometimes in learning (Using something called the VAK model), people are not understanding one particular way of explaining, so maybe an alternative is in order?

marnen commented 5 years ago

@luke-hill:

but you need to trust me that the new methodology is better

No, we don’t need to trust you. You need to tell us why it is better. Right now, it seems like extra work for no benefit, at least for our use case.

I'm speaking as someone who had at a previously company a healthy amount of step usages, and the stacktraces in the cucumber html reports were always a little bit messy.

That has not been my experience with my step usage patterns.

or perhaps illustrate with a git repo why it wouldn't work.

As @badeball and I have said, it wouldn’t work because it duplicates the mapping that Cucumber already provides, and because it’s not general. That is, with your snake_case approach, I have to define a new method every time I have new captured text to map. That defeats the purpose of doing this in the first place. Remember, I literally want to be able to do Then any arbitrary step with modifier and have it use the already existing Cucumber mappings for Then any arbitrary step. You are not providing a solution that has that flexibility.

Again, my canonical case is the one I already described, that of Then I should see "foo" within the sidebar. If you’d like to see it in action, take a look at https://github.com/marnen/erogatio/blob/master/features/step_definitions/web_steps.rb#L27 and https://github.com/marnen/erogatio/blob/master/features/enter_payment_for_work_units.feature#L20. Note in particular the use of two different steps in When/Then <step> within the work unit in that scenario. In principle, I could use any step there without additional coding, and that’s the important feature that your solution (AFAIK) fails to give me.

luke-hill commented 5 years ago

I think I've landed on the crux of the issue. In that you're looking to do the exact thing we're looking to discourage here. So my earlier comments that were ignored I now realise were done either accidentally or because you disagree with them (Which is fine).

So just to reiterate (This will be for the last time, because we're going in circles). You're advocating the usage of something that is being deprecated. The reason it is being deprecated is as aslak has previously mentioned and is linked to in a couple of articles (As well as the notional lack of it now in all other major cucumber flavours).

If you wish to continue writing 1 'mega-step' this is not too dissimilar to my original POV which was that I had "worked at a company with 1 step that called 5 steps", because in essence you have something similar to that in your codebase, just a bit more varied (Steps that can either perform actions or assert instead of steps that combine other steps which do actions).

aslak also put a quite clean JS code-snippet, and I've put a reasonably concise ruby snippet. There could be other ways you could do it.

As for the original comment, kinda rude tbh. If you don't want to agree with me that's fine. But you can't (or shouldn't), half cut and paste things out of context.

I explained why the new technology was better, by alluding to stacktraces, as well as the fact it uses the MRI under the hood, instead of having cucumber replicate that. One alternative way (I'm losing track of how many variants to give you), is perhaps to think of it as JRuby. It does stuff that isn't normal for Ruby and regular ruby doesn't support. So they decided to make a branch offshoot.

In theory (Although I wouldn't advise this), you could make a new gem that allowed this behaviour, monkeypatched and swallowed all warnings, and overrode the behaviour in cucumber-ruby v4. However I would advise against this as it wouldn't be future-proof.

Also I've seen across a few of your posts a reference to "your" use case, which is all well and good, but cucumber is currently the number 1 used BDD tool across tech teams world-wide - with just a few of the users here: https://cucumber.io/

Some of these companies (Such as CodeFirst), https://opencollective.com/codefirst actively contribute to the maintenance of cucumber, and as such if they had a request it could perhaps be prioritised dependent on needs e.t.c.

If you can think of a way that this behaviour can be maintained either in this gem or an offshoot gem, without going against the tenets of what we've explained, then please feel free to do so. I'm not going to comment on this any more, because I feel I've tried in a few different ways to explain in quite good detail what to do. I have an extensive background in education but sometimes I'm not able to explain something to a particular group/class/person.

I would therefore point you to some of the links I've used before

There are some good resources as well about BDD being the living documentation. @sebrose probably has a wealth more links he could possibly share. But from your use cases and the direction you've been moving in, I'm not sure I'm the best person to help, as it seems as if the reason for you wanting to code in a specific way without using a better methodology may be better answered with a holistic company-based query (Which Seb is much more qualified and experienced than I)

marnen commented 5 years ago

@luke-hill:

I think I've landed on the crux of the issue. In that you're looking to do the exact thing we're looking to discourage here.

OK. You’ve seen my use case now. What would you advise doing for that use case (that is, where the argument to step isn’t hard-coded)? I’m not interested in repetitions of the general advice that you’ve already given me: I’m sure you’re tired of repeating it, and it doesn’t work here anyway.

To reiterate, what I think I need is a general step modifier mechanism which has the following properties:

  1. It can modify any arbitrary Cucumber step
  2. It requires no additional coding to modify a new step

I’d love to know how you’d approach that. Right now, as I see it, you’ve talked all around the problem and not given me a usable solution.

In other words, if you’re trying to discourage it, please give me a concrete suggestion as to what to do instead.

If you wish to continue writing 1 'mega-step'

I don’t think that’s what I’m doing. Of course step can be abused, but if you remove everything that can be abused, you wind up with Java. :)

I've put a reasonably concise ruby snippet.

As far as I can tell, none of your snippets help me figure out how to implement within the constraints I gave without using step. They may well address other uses of step (e.g. with hard-coded strings), but not mine.

I explained why the new technology was better, by alluding to stacktraces, as well as the fact it uses the MRI under the hood, instead of having cucumber replicate that.

Yes, but unfortunately that’s not better in any way that I care about. What I care about most is being able to call existing steps (with modifiers) using the same mappings that Cucumber already does. To put it in your terms, I want to use the existing Gherkin mappings under the hood, instead of having MRI replicate that.

In theory (Although I wouldn't advise this), you could make a new gem that allowed this behaviour

If step is removed from Cucumber, I think adding it back in a plugin will be my only option if I want to continue using Cucumber. I don’t want to do that unless I can’t avoid it, though, and so I’d really welcome other ideas.

I'm not going to comment on this any more

I understand that we’re both getting a bit frustrated here, but at this point I’m no wiser than when I started this discussion as to how to achieve my goal without step.

I would therefore point you to some of the links I've used before

  • aruba changelogs and unDRYing

I’ll look at those, though I’m skeptical of anything that advocates unDRYing. :)

  • JS code examples & aslaks explanation of using a languages own methodologies (functions/methods)
  • Explanations above

These are probably great for replacing other use cases of step (specifically those where the Gherkin text is hard-coded in the step definition), but not mine as far as I can see. That’s because as far as I can see, they fail to address the fundamental issue I’m dealing with here: that of mapping from an arbitrary Gherkin step (not hard-coded) to Ruby code. Am I wrong?

the reason for you wanting to code in a specific way without using a better methodology

I’d be happy to use a better methodology if I could only figure out how to get it to work for what I want to accomplish. If you can’t help me, I hope @sebrose or someone else can.

enkessler commented 5 years ago

Then if another workable solution is not provided, I’ll either have to abandon Cucumber at that time (which would be a huge pity) or maintain my own fork or plugin (which I really don’t want to put the time into doing).

If step is removed from Cucumber, I think adding it back in a plugin will be my only option if I want to continue using Cucumber. I don’t want to do that unless I can’t avoid it, though, and so I’d really welcome other ideas.

@marnen If the removal of a single feature from a tool, a feature which has been deemed an anti-pattern almost as long as it has been around, is enough to make you stop using the tool entirely, then you might want to reconsider why you are using the tool in the first place.

marnen commented 5 years ago

@enkessler I believe I’m using this feature in a way that is not actually representative of the antipatterns that it’s often associated with.

I don’t believe I’m using Cucumber for the wrong reasons, but the fact remains that, in a very narrow set of circumstances, I rely on this feature. If the feature goes away, I won’t want to stop using Cucumber, but I may well have no choice, if at that point Cucumber will no longer be adequate for my purposes.

Or so I think. If you think that my use of step (as described in this thread, with non-hard-coded Gherkin strings) is bad, I’d really welcome a suggestion of how I could accomplish the same goal (arbitrary steps with modifiers) in a better way. I’ve tried hard to come up with one and so far I can’t.

marnen commented 5 years ago

I should mention for completeness' sake that I've been considering an option that turns the logic inside out:

Then /^I should see "(.+?)"(?: within "(.+?)")?$/ do |text, selector|
  with_selector(selector) do |page|
    expect(page).to have_text text
  end
end

Then /^I should see a cat GIF(?: within "(.+?)")?$/ do |selector|
  with_selector(selector) do |page|
    # code for locating cat GIF element
  end
end

private

def with_selector(selector, &block)
  within(selector || 'html', &block) # or something like that
end

...but I really don't like it: it requires rewriting every step that I ever use with the modifier, and has other maintainability issues as well. So far this is the best way I've found around using step, unfortunately.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in a week if no further activity occurs.

tooky commented 4 years ago

I've had the feeling that this should be pulled out into a plugin for a while now.

It would also be my preferred method to deprecate.

  1. Cucumber Ruby 5.0 - extract step/steps to plugin, include plugin in cucumber, deprecation warning
  2. Cucumber Ruby 6.0 - remove steps from core functionality, users can use plugin if they wish
marnen commented 4 years ago

@tooky That sounds reasonable to me if it must be removed from core.

enkessler commented 4 years ago

@tooky Couldn't Cucumber stay in the 4.x version as long as the new plug-in was automatically used by Cucumber? It's not really breaking behavior until the user has to start pulling in the additional library themselves.

tooky commented 4 years ago

Yes I think that’s true. My thought was I’d rather not suddenly get deprecation warnings on a minor or patch upgrade.

On 15 Jun 2020, at 11:20, Eric Kessler notifications@github.com<mailto:notifications@github.com> wrote:

@tooky Couldn't Cucumber stay in the 4.x version as long as the new plug-in was automatically used by Cucumber? It's not really breaking behavior until the user has to start pulling in the additional library themselves.

mattwynne commented 3 years ago

@marnen thanks for patiently explaining your use-case.

Having read this thread, I was about to suggest something along these lines when I saw that you already had:

I should mention for completeness' sake that I've been considering an option that turns the logic inside out:

Then /^I should see "(.+?)"(?: within "(.+?)")?$/ do |text, selector|
  with_selector(selector) do |page|
    expect(page).to have_text text
  end
end

Then /^I should see a cat GIF(?: within "(.+?)")?$/ do |selector|
  with_selector(selector) do |page|
    # code for locating cat GIF element
  end
end

private

def with_selector(selector, &block)
  within(selector || 'html', &block) # or something like that
end

...but I really don't like it: it requires rewriting every step that I ever use with the modifier, and has other maintainability issues as well. So far this is the best way I've found around using step, unfortunately.

Can you unpack those objections for me a bit more? Why do the existing steps need to be re-written? What are the other maintainability issues?

marnen commented 3 years ago

Why do the existing steps need to be re-written?

Because using this method, the within is defined (duplicatively!) in each step that uses it, rather than as a generically applicable modifier. That means that every time I want to use a new step with within, I have to rewrite its step definition.

Contrast this with what I currently do, where I write the within modifier once and it is then applicable to any existing step, without further rewriting the step definitions.

Does that answer your question? Do you need this explained further?

mattwynne commented 3 years ago

I hear ya @marnen.

The thing we're trying to avoid is where people try and evaluate Gherkin at runtime, like:

step("I am logged in as #{username}")

Instead of just extracting/calling a helper method:

login_as username

But I can see how you've made use of this feature to give you something powerful.

I wonder if we could do something that helped us to avoid the dynamic runtime Gherkin evaluation we're trying to avoid, but still have you this kind of functionality, something like:

Suffix("within {string}") do |selector, &step|
  within(selector || 'html', &step)
end

That new Suffix DSL method would effectively create a new, suffixed step definition for every existing step definition, wrapping the original one.

WDYT?

marnen commented 3 years ago

@mattwynne Thanks for the suggestion! I think your Suffix proposal is an interesting idea and could go a long way toward making all this work. (And we’d probably want Prefix and Circumfix too.)

I have a lingering fear that it’s too rigid, and I’m not sure I like the idea that it would work by creating lots of extra step definitions—couldn’t that cause a combinatorial explosion?—but it would certainly work for my use case.

mattwynne commented 2 years ago

Ref: https://github.com/cucumber-attic/gherkin2/issues/178

marnen commented 2 years ago

@mattwynne Ooh, I really love the macros idea. It seems like it would keep both reusability and readable Gherkin.