From 3a8c96c9bcebce941823a4cac2594a982c6ef391 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: Tue, 8 Sep 2026 16:38:00 +0530 Subject: [PATCH 1/2] #577: Fix Gherkin importer dropping a scenario after a preceding tagged one GherkinLineConsumer.readLine() only ends the current scenario (and starts looking for the next one's metadata) on a Scenario/Feature/Rule/Background/ Examples boundary line. A `@id:` tag line matches none of those, so while a scenario's steps are still being streamed into its description, an `@id:` tag for the next scenario falls into the same branch as an ordinary step line and gets appended as more of the current scenario's description instead of being read as metadata. The next scenario is then left without a pending id and is silently skipped on its Scenario: line. This only shows up when two tagged scenarios are consecutive, i.e. nothing Gherkin considers a block boundary sits between them - the documented workaround is inserting a throwaway `Rule:` line, which is exactly such a boundary. dsn~gherkin.streaming-import~1 defines "until a Gherkin block boundary" as the streaming condition, and a tag line is a boundary in that sense: it is where dsn~gherkin.id-detection~1's "immediately preceding @id: tag" for the next scenario begins. So a tag line encountered while still streaming a scenario now ends that scenario and is then handed to the normal metadata reader, instead of being swallowed as description text. This is why extending the BOUNDARY regex with `@` doesn't work as a fix on its own: that branch returns immediately after clearing metadata, so the tag itself would still never be read as an id. Adds a regression test reproducing the issue's exact repro shape (two consecutive tagged scenarios, no boundary between them), asserting both import with the correct ids and neither description is corrupted by the leaked tag line. --- .../importer/gherkin/GherkinLineConsumer.java | 11 ++++++++ .../importer/gherkin/GherkinImporterTest.java | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java index 1c0fb7de..5bf5ee4b 100644 --- a/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java +++ b/importer/gherkin/src/main/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinLineConsumer.java @@ -72,6 +72,17 @@ public void readLine(final int lineNumber, final String line) } if (this.importingScenario) { + if (line.trim().startsWith("@")) + { + // A tag line is a Gherkin block boundary [dsn~gherkin.streaming-import~1] just as much as + // Scenario/Feature/Rule/Background/Examples are: it starts the metadata for the next + // scenario [dsn~gherkin.id-detection~1]. Without ending the current scenario here, this + // line would be swallowed as its description instead, and the next scenario would never + // get a pending id. + endScenario(); + readMetadata(lineNumber, line); + return; + } if (!line.trim().startsWith("#") && !line.trim().isEmpty()) { this.listener.appendDescription(line + System.lineSeparator()); diff --git a/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java b/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java index 5c46b47d..7e068126 100644 --- a/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java +++ b/importer/gherkin/src/test/java/org/itsallcode/openfasttrace/importer/gherkin/GherkinImporterTest.java @@ -300,6 +300,31 @@ void testImportsScenariosWithDuplicateIds() hasProperty("title", is("Second login")))); } + // [utest->dsn~gherkin.streaming-import~1] + // [utest->dsn~gherkin.id-detection~1] + @Test + void testImportsConsecutiveTaggedScenariosWithoutAnInterveningBoundary() + { + final List items = importText(""" + @id:scn~first~1 + # Needs: itest + Scenario: First scenario + Given a precondition + + @id:scn~second~1 + # Needs: itest + Scenario: Second scenario + Given another precondition + """); + + assertAll( + () -> assertThat(items, contains( + hasProperty("id", hasToString("scn~first~1")), + hasProperty("id", hasToString("scn~second~1")))), + () -> assertThat(items.get(0).getDescription(), is("Given a precondition")), + () -> assertThat(items.get(1).getDescription(), is("Given another precondition"))); + } + // [utest->dsn~gherkin.comment-coverage-tags~1] @Test void testImportsCommentCoverageTagsButIgnoresExecutableCoverageTags() From d3db4c78e4e962a382234ce9e67411f083cd72c7 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: Wed, 9 Sep 2026 03:24:06 +0530 Subject: [PATCH 2/2] #577: Add change log entry --- doc/changes/changes_4.10.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changes/changes_4.10.0.md b/doc/changes/changes_4.10.0.md index 578880bf..35f12da0 100644 --- a/doc/changes/changes_4.10.0.md +++ b/doc/changes/changes_4.10.0.md @@ -17,6 +17,7 @@ Each release now includes an SPDX 3 SBOM for the product JAR and a SHA-256 check ## Bugfixes * #582: Fixed the Markdown importer silently dropping all specification items after a fenced code block that directly follows a section title. +* #577: Fixed the Gherkin importer dropping a scenario when it directly follows another tagged scenario, with no boundary line between them. ## Documentation