Skip to content

Conversation

@uuf6429
Copy link
Contributor

@uuf6429 uuf6429 commented Mar 3, 2024

Replaces "example name #N" with "example name (XX)" in JUnit output.

Closes #1448

Copy link
Contributor

@acoulton acoulton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @uuf6429, I've got a few comments plus there are some merge conflicts with master to resolve.

Comment on lines -117 to +125
<testcase name="Passed &amp; Failed #1" classname="World consistency" status="failed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature">
<testcase name="Passed &amp; Failed (value: 5, result: 16)" classname="World consistency" status="failed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature">
<failure message="Then I must have 16: Failed asserting that 15 matches expected '16'."/>
</testcase>
<testcase name="Passed &amp; Failed #2" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Passed &amp; Failed #3" classname="World consistency" status="failed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature">
<testcase name="Passed &amp; Failed (value: 10, result: 20)" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Passed &amp; Failed (value: 23, result: 32)" classname="World consistency" status="failed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature">
<failure message="Then I must have 32: Failed asserting that 33 matches expected '32'."/>
</testcase>
<testcase name="Another Outline #1" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline #2" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline (value: 5, result: 15)" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline (value: 10, result: 20)" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use junit at the moment but I know when I've used Jenkins in the past there was reporting that identified changes in individual tests over time - should we be worried about a risk that renaming the tests would break people's reports / workflows?

@carlos-granados
Copy link
Contributor

@uuf6429 @acoulton what's needed to get this over the line?

@acoulton
Copy link
Contributor

acoulton commented Nov 6, 2024

@carlos-granados very little, I think we're in agreement that changing the string content of the output is not a BC break so it just needs the constructor BC break fixing and merge conflicts resolved

@uuf6429
Copy link
Contributor Author

uuf6429 commented Nov 6, 2024

I've missed the last comments on this, I'll finish the remaining parts then.

<testcase name="Another Outline #1" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline #2" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline (value: 5, result: 15)" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
<testcase name="Another Outline (value: 10, result: 20)" classname="World consistency" status="passed" time="-IGNORE-VALUE-" file="features-DIRECTORY-SEPARATOR-World.feature"/>
Copy link
Contributor Author

@uuf6429 uuf6429 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've come to realise something else. At work we create custom reports that reuse some of this functionality (title, example values etc).

We realised that the nicest solution (in our opinion) is to replace placeholders in the title - instead of add #<number> or in this case (<example values>).

Consider this feature:

feature:
    Scenario Outline: <count> items should <passOrFail>
        When I have <count> items
        Then I should <passOrFail>
    Examples:
        | count  | passOrFail |
        | 0      | pass       |
        | 1      | pass       |
        | 2      | fail       |
  • Currently, the result will be:
        <count> items should <passOrFail> #1
        <count> items should <passOrFail> #2
        <count> items should <passOrFail> #3
    
    The number is not particularly useful.
  • With this PR, it would look like:
        <count> items should <passOrFail> (count: 0, passOrFail: pass)
        <count> items should <passOrFail> (count: 1, passOrFail: pass)
        <count> items should <passOrFail> (count: 2, passOrFail: fail)
    
    This is more useful, but not very readable and easily/unavoidably verbose
  • With my latest proposal:
        0 items should pass
        1 items should pass
        2 items should fail
    
    Couldn't get any better. :)

I think the 3rd would be the best overall approach, but I've a feeling this is not supported in gherkin though (although I guess it's not contradicting it either). What do you think @stof, @ciaranmcnulty?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of the third approach (it's similar to how e.g. it.each( works in jest / vitest).

Off the top of my head I don't know how feasible it is with gherkin syntax - although presumably could find a placeholder string that didn't contain anything gherkin considers to be special characters.

The other question would be what you do if the title doesn't contain placeholders - would you try to detect that and append the values as in this PR, or would you stick with the current #0 etc suffix, or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other question would be what you do if the title doesn't contain placeholders - would you try to detect that and append the values as in this PR, or would you stick with the current #0 etc suffix, or something else?

I was hoping I wouldn't have to answer that. 😆

When I think about it, it might make sense to add the numbering when the title already exists - it would be backward compatible and somewhat unsurprising.

    private $outlineTitles = [];

    private function buildExampleName(ExampleNode $scenario): string
    {
        $currentOutline = $this->outlineStoreListener->getCurrentOutline($scenario);

        $title = $this->replacePlaceholders($scenario->getOutlineTitle(), $scenario->getTokens());

        $number = $this->outlineTitles[$title] ?? 0;

        $this->outlineTitles[$title] = $number + 1;

        if ($number > 0) {
            $title .= " #$number";
        }

        if ($currentOutline === $this->lastOutline) {
            $this->outlineTitles = [];
        }

        return $title;
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could work quite well, so it becomes a definitely-BC update path:

  • If you do nothing, you'll get the same test names as before (including that the numbers are inconsistent between initial and re-run?)
  • But to get consistent - and clearer - naming, you just have to reword your scenario outlines to include placeholders

That's a really nice idea if you can find a syntax for it that works in gherkin. Using <title> would seem logical as it follows the pattern of how the parameters are referenced in the steps within the outline. But might be risky (now or in future) because of that.

I don't think {} has any special meaning in gherkin, so that might be a possibility...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so it becomes a definitely-BC update path

Exactly.

But might be risky (now or in future) because of that.

If I didn't misunderstand, it is supported/implemented in cucumber already: cucumber/common#2149 and cucumber/cucumber-js#267. The request here seems to lend credence to that too: https://youtrack.jetbrains.com/issue/IDEA-320963/Cucumber-example-variable-usage-in-scenario-outline-title

On the whole, I think I'd go for the <...> syntax since it seems to also be what people there were expecting.

I see mixed signals in Behat though: #1024

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh perfect, if cucumber is already using the tags like that for this purpose then that's exactly what we need. There's also an example of it in cucumber/gherkin.

If I've understood right, most of the mixed signals in #1024 look to me like they're specific to the pretty output format (where I can see there's no logical place to do the substitution in the current output, because it only prints the Scenario Outline block once and just renders the results into and below the Examples: table).

The suggestion this should be implemented in Gherkin is probably right, but:

  • I'm not sure why this comment is linking to OutlineNode::getKeyword as I'm pretty sure that will (correctly) always just return Scenario Outline.
  • It can't change the return of OutlineNode::getTitle() because that needs to be the title that represents all the examples e.g. without replacing any placeholders, so that e.g. the pretty formatter can use it as above.

I actually think the logical place to do this is inside ExampleNode. That is the class that's responsible for replacing placeholders in the step definitions, so replacing placeholders in the title there as well feels sensible.

ExampleNode already has a getOutlineTitle which currently just returns the parent title as-is. I can't find any usages of that method in behat - it could of course be used by other consumers. I don't know if we'd consider changing the string content a BC break - the safest option of course would be to add a new getOutlineTitleWithPlaceholders (with a better name 😆) and perhaps deprecate the existing getOutlineTitle.

The other advantage of doing it in ExampleNode is that when it's created by OutlineNode we presumably know which index it is in the gherkin file - I think filtering what will actually run comes later than parsing. So we could pass that into ExampleNode as well and use that to append a consistent number to the end of the title which would also then solve the JUnit bug even if people don't put placeholders into their titles.

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also feel the best place to put it would be in ExampleNode. I'm a bit hesitant with the semantics though.

  • getTitle - this feels like it should return the definitive title, e.g. to be shown in reports and so on. In the ideal world, I think this should be the one replacing placeholders and adding numbers for deduplication.
  • getOutlineTitle - Internally (e.g. even in gherkin), it seems that there shouldn't be a concept of a scenario outline - instead they should be flattened into regular scenarios. With that in mind, I think the "outline title" should be the original (parent?) title - since that parent is the "scenario outline".
  • getOutlineTitleWithPlaceholders (& co.) - I would go for such a method if we want to be absolutely BC safe. That said, I do feel that the majority of end users would rather have the BC break than the current approach, but that's my hypothesis.

PS: with regards to getTitle, if it did behave like that, #1470 might have been avoided. I'd appreciate @cavo789's thoughts on this as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The challenge is getTitle is the title of the "example" and is currently the content of the table row for the example e.g. 1 | 5 | 4 and existing formatters rely on it

Regarding collapsing scenario and scenario outline - that could also be valid : gherkin now treats them as synonyms. But again existing formatters display them differently.

Maybe we should move this to an issue in gherkin to keep the discussion visible & clear?

Copy link
Contributor Author

@uuf6429 uuf6429 Nov 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR to fix conflicts and apply the last review suggestions, so that is can be "frozen" while a discussion can take place in behat/gherkin. Done: Behat/Gherkin#270

@uuf6429 uuf6429 marked this pull request as draft November 9, 2024 14:52
@acoulton
Copy link
Contributor

Thanks again for the work & discussion on this @uuf6429 - I have replaced it with #1682 based on the new API you contributed to Behat/Gherkin and so I will close this. I've credited you on #1682 and we'll mention you in the release notes when it ships.

@acoulton acoulton closed this Oct 27, 2025
acoulton added a commit that referenced this pull request Oct 28, 2025
…it (#1682)

Previously, when a feature file contained scenarios with `Example` nodes
the junit report would append an index to each testcase name to
differentiate them (e.g. `<testcase name="Some scenario outline #1"`,
`<testcase name="Some scenario outline #2`, etc).

These indices were dynamically generated based on the examples that
actually ran. As a result, on a `--rerun` the names would start from
`#1` in sequence and be inconsistent with the numbering of the first
execution. This makes it difficult to compare reports between each test
run, for example to link a passing rerun to the previous failure.

With this change, examples are numbered consistently based on the order
they appear in the feature file. This fixes #1448.

Additionally, we also now support placeholders in the Scenario Outline
title. This allows users to include some or all of the values from the
Examples: table in each `<testcase` name for additional clarity. This
matches the behaviour of the official cucumber runners.

This PR builds on work done by @uuf6429 in #1459 and in particular the
implementation of the numbering & placeholder replacement that he
contributed in Behat/Gherkin#270 and Behat/Gherkin#271.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reruns of Scenario outlines lead to potentially confusing scenario names

3 participants