diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2eddab3..0cff427 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+- Support configurable testsuite name, testcase classname, and testcase name. ([#105](https://github.com/cucumber/junit-xml-formatter/pull/105))
+
## [0.9.0] - 2025-09-11
### Changed
- Update dependency cucumber/query to 14.0.1
diff --git a/java/src/main/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriter.java b/java/src/main/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriter.java
index 54dd98a..891ee03 100644
--- a/java/src/main/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriter.java
+++ b/java/src/main/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriter.java
@@ -9,6 +9,7 @@
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
+import static io.cucumber.query.NamingStrategy.ExampleName.NUMBER_AND_PICKLE_IF_PARAMETERIZED;
import static io.cucumber.query.NamingStrategy.FeatureName.EXCLUDE;
import static io.cucumber.query.NamingStrategy.Strategy.LONG;
import static java.util.Objects.requireNonNull;
@@ -22,24 +23,75 @@
*/
public final class MessagesToJunitXmlWriter implements AutoCloseable {
+ private static final String DEFAULT_TEST_SUITE_NAME = "Cucumber";
private final OutputStreamWriter out;
private final XmlReportData data;
private boolean streamClosed = false;
public MessagesToJunitXmlWriter(OutputStream out) {
- this(NamingStrategy.ExampleName.NUMBER_AND_PICKLE_IF_PARAMETERIZED, out);
+ this("Cucumber", null, createNamingStrategy(NUMBER_AND_PICKLE_IF_PARAMETERIZED), out);
}
+ @Deprecated
public MessagesToJunitXmlWriter(NamingStrategy.ExampleName exampleNameStrategy, OutputStream out) {
- this(createNamingStrategy(requireNonNull(exampleNameStrategy)), out);
+ this("Cucumber", null, createNamingStrategy(requireNonNull(exampleNameStrategy)), out);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private String testSuiteName = DEFAULT_TEST_SUITE_NAME;
+ private String testClassName;
+ private NamingStrategy testNamingStrategy = NamingStrategy.strategy(LONG)
+ .featureName(EXCLUDE)
+ .exampleName(NUMBER_AND_PICKLE_IF_PARAMETERIZED)
+ .build();
+
+ private Builder() {
+
+ }
+
+ /**
+ * Sets the value for the {@code } attribute. Defaults to {@value DEFAULT_TEST_SUITE_NAME}.
+ */
+ public Builder testSuiteName(String testSuiteName) {
+ this.testSuiteName = requireNonNull(testSuiteName);
+ return this;
+ }
+
+ /**
+ * Sets the value for the {@code } attribute. Defaults to the name of the
+ * feature.
+ */
+ public Builder testClassName(String testClassName) {
+ this.testClassName = testClassName;
+ return this;
+ }
+
+ /**
+ * Set the naming strategy used for the {@code attribute}. Defaults to the
+ * {@link NamingStrategy.Strategy#LONG} strategy with {@link NamingStrategy.FeatureName#EXCLUDE} and
+ * {@link NamingStrategy.ExampleName#NUMBER_AND_PICKLE_IF_PARAMETERIZED}.
+ */
+ public Builder testNamingStrategy(NamingStrategy namingStrategy) {
+ this.testNamingStrategy = requireNonNull(namingStrategy);
+ return this;
+ }
+
+ public MessagesToJunitXmlWriter build(OutputStream out) {
+ return new MessagesToJunitXmlWriter(testSuiteName, testClassName, testNamingStrategy, requireNonNull(out));
+ }
}
private static NamingStrategy createNamingStrategy(NamingStrategy.ExampleName exampleName) {
- return NamingStrategy.strategy(LONG).featureName(EXCLUDE).exampleName(exampleName).build();
+ return NamingStrategy.strategy(NamingStrategy.Strategy.LONG).featureName(NamingStrategy.FeatureName.EXCLUDE).exampleName(exampleName).build();
}
- private MessagesToJunitXmlWriter(NamingStrategy namingStrategy, OutputStream out) {
- this.data = new XmlReportData(namingStrategy);
+ private MessagesToJunitXmlWriter(String testSuiteName, String testClassName, NamingStrategy testNamingStrategy, OutputStream out) {
+ this.data = new XmlReportData(testSuiteName, testClassName, testNamingStrategy);
this.out = new OutputStreamWriter(
requireNonNull(out),
StandardCharsets.UTF_8
diff --git a/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportData.java b/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportData.java
index 61bbea3..ef4ddd6 100644
--- a/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportData.java
+++ b/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportData.java
@@ -28,21 +28,25 @@
import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
+import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
class XmlReportData {
+ private static final long MILLIS_PER_SECOND = SECONDS.toMillis(1L);
private final Repository repository = Repository.builder()
.feature(INCLUDE_GHERKIN_DOCUMENTS, true)
.build();
private final Query query = new Query(repository);
- private final NamingStrategy namingStrategy;
-
- private static final long MILLIS_PER_SECOND = SECONDS.toMillis(1L);
-
- XmlReportData(NamingStrategy namingStrategy) {
- this.namingStrategy = namingStrategy;
+ private final String testSuiteName;
+ private final String testClassName;
+ private final NamingStrategy testNamingStrategy;
+
+ XmlReportData(String testSuiteName, String testClassName, NamingStrategy testNamingStrategy) {
+ this.testSuiteName = requireNonNull(testSuiteName);
+ this.testClassName = testClassName;
+ this.testNamingStrategy = requireNonNull(testNamingStrategy);
}
void collect(Envelope envelope) {
@@ -74,20 +78,27 @@ private Pickle getPickle(TestCaseStarted testCaseStarted) {
.orElseThrow(() -> new IllegalStateException("No pickle for " + testCaseStarted.getId()));
}
- String getPickleName(TestCaseStarted testCaseStarted) {
+ String getTestName(TestCaseStarted testCaseStarted) {
Pickle pickle = getPickle(testCaseStarted);
return query.findLineageBy(pickle)
- .map(lineage -> namingStrategy.reduce(lineage, pickle))
+ .map(lineage -> testNamingStrategy.reduce(lineage, pickle))
.orElseGet(pickle::getName);
}
- String getFeatureName(TestCaseStarted testCaseStarted) {
+ String getTestClassName(TestCaseStarted testCaseStarted) {
+ if (testClassName != null) {
+ return testClassName;
+ }
return query.findLineageBy(testCaseStarted)
.flatMap(Lineage::feature)
.map(Feature::getName)
.orElseGet(() -> this.getPickle(testCaseStarted).getUri());
}
+ String getTestSuiteName() {
+ return testSuiteName;
+ }
+
List> getStepsAndResult(TestCaseStarted testCaseStarted) {
return query.findTestStepFinishedAndTestStepBy(testCaseStarted)
.stream()
@@ -138,7 +149,7 @@ TestStepResult getTestCaseStatus(TestCaseStarted testCaseStarted) {
.orElse(SCENARIO_WITH_NO_STEPS);
}
- public Optional getTestRunStartedAt() {
+ Optional getTestRunStartedAt() {
return query.findTestRunStarted()
.map(TestRunStarted::getTimestamp)
.map(Convertor::toInstant)
diff --git a/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportWriter.java b/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportWriter.java
index 040b27d..dd3f407 100644
--- a/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportWriter.java
+++ b/java/src/main/java/io/cucumber/junitxmlformatter/XmlReportWriter.java
@@ -47,7 +47,7 @@ private void writeTestsuite(EscapingXmlStreamWriter writer) throws XMLStreamExce
}
private void writeSuiteAttributes(EscapingXmlStreamWriter writer) throws XMLStreamException {
- writer.writeAttribute("name", "Cucumber");
+ writer.writeAttribute("name", data.getTestSuiteName());
writer.writeAttribute("time", String.valueOf(data.getSuiteDurationInSeconds()));
Map counts = data.getTestCaseStatusCounts();
@@ -85,8 +85,8 @@ private void writeTestcase(EscapingXmlStreamWriter writer, TestCaseStarted testC
}
private void writeTestCaseAttributes(EscapingXmlStreamWriter writer, TestCaseStarted testCaseStarted) throws XMLStreamException {
- writer.writeAttribute("classname", data.getFeatureName(testCaseStarted));
- writer.writeAttribute("name", data.getPickleName(testCaseStarted));
+ writer.writeAttribute("classname", data.getTestClassName(testCaseStarted));
+ writer.writeAttribute("name", data.getTestName(testCaseStarted));
writer.writeAttribute("time", String.valueOf(data.getDurationInSeconds(testCaseStarted)));
}
diff --git a/java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterAcceptanceTest.java b/java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterAcceptanceTest.java
index 05ef8a8..e649036 100644
--- a/java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterAcceptanceTest.java
+++ b/java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterAcceptanceTest.java
@@ -2,6 +2,8 @@
import io.cucumber.messages.NdjsonToMessageIterable;
import io.cucumber.messages.types.Envelope;
+import io.cucumber.query.NamingStrategy;
+import io.cucumber.query.NamingStrategy.Strategy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
@@ -24,23 +26,44 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.cucumber.junitxmlformatter.Jackson.OBJECT_MAPPER;
+import static io.cucumber.query.NamingStrategy.Strategy.LONG;
+import static io.cucumber.query.NamingStrategy.strategy;
import static org.xmlunit.assertj.XmlAssert.assertThat;
class MessagesToJunitXmlWriterAcceptanceTest {
private static final NdjsonToMessageIterable.Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class);
static List acceptance() throws IOException {
+ List testCases = new ArrayList<>();
+
try (Stream paths = Files.list(Paths.get("../testdata/src"))) {
- return paths
+ paths
.filter(path -> path.getFileName().toString().endsWith(".ndjson"))
- .map(TestCase::new)
+ .map(source -> new TestCase(
+ source,
+ "default",
+ MessagesToJunitXmlWriter.builder()
+ )
+ )
.sorted(Comparator.comparing(testCase -> testCase.source))
- .collect(Collectors.toList());
+ .forEach(testCases::add);
}
+
+ testCases.add(
+ new TestCase(
+ Paths.get("../testdata/src/examples-tables.ndjson"),
+ "custom",
+ MessagesToJunitXmlWriter.builder()
+ .testSuiteName("Cucumber Suite")
+ .testClassName("Cucumber Class")
+ .testNamingStrategy(strategy(LONG).build())
+ )
+ );
+
+ return testCases;
}
@ParameterizedTest
@@ -94,7 +117,7 @@ void updateExpectedFiles(TestCase testCase) throws IOException {
private static T writeJunitXmlReport(TestCase testCase, T out) throws IOException {
try (InputStream in = Files.newInputStream(testCase.source)) {
try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) {
- try (MessagesToJunitXmlWriter writer = new MessagesToJunitXmlWriter(out)) {
+ try (MessagesToJunitXmlWriter writer = testCase.getBuilder().build(out)) {
for (Envelope envelope : envelopes) {
writer.write(envelope);
}
@@ -109,17 +132,25 @@ static class TestCase {
private final Path expected;
private final String name;
+ private final MessagesToJunitXmlWriter.Builder builder;
+ private final String strategyName;
- TestCase(Path source) {
+ TestCase(Path source, String namingStrategyName, MessagesToJunitXmlWriter.Builder builder) {
this.source = source;
String fileName = source.getFileName().toString();
this.name = fileName.substring(0, fileName.lastIndexOf(".ndjson"));
- this.expected = source.getParent().resolve(name + ".xml");
+ this.expected = source.getParent().resolve(name + "." + namingStrategyName + ".xml");
+ this.builder = builder;
+ this.strategyName = namingStrategyName;
+ }
+
+ MessagesToJunitXmlWriter.Builder getBuilder() {
+ return builder;
}
@Override
public String toString() {
- return name;
+ return name + " -> " + strategyName;
}
@Override
diff --git a/javascript/package-lock.json b/javascript/package-lock.json
index ee64dc9..a7e3bac 100644
--- a/javascript/package-lock.json
+++ b/javascript/package-lock.json
@@ -74,7 +74,6 @@
"resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-30.1.0.tgz",
"integrity": "sha512-KxnsSjHz9EGF23GeZc3BRMK2+bagt2p87mwwNfisBK7BfuyvnXJumyBQJJN4xv5SLSzBKxH3FsZnuOf8LwsHhg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"class-transformer": "0.5.1",
"reflect-metadata": "0.2.2"
@@ -635,7 +634,6 @@
"integrity": "sha512-5yBtK0k/q8PjkMXbTfeIEP/XVYnz1R9qZJ3yUicdEW7ppdDJfe+MqXEhpqDL3mtn4Wvs1u0KLEG0RXzCgNpsSg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -686,7 +684,6 @@
"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
@@ -918,7 +915,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1307,7 +1303,6 @@
"resolved": "https://registry.npmjs.org/chai/-/chai-6.0.1.tgz",
"integrity": "sha512-/JOoU2//6p5vCXh00FpNgtlw0LjvhGttaWc+y7wpW9yjBm3ys0dI8tSKZxIOgNruz5J0RleccatSIC3uxEZP0g==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1855,7 +1850,6 @@
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4790,7 +4784,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/javascript/src/index.spec.ts b/javascript/src/index.spec.ts
index 17cc57c..c43e569 100644
--- a/javascript/src/index.spec.ts
+++ b/javascript/src/index.spec.ts
@@ -5,6 +5,7 @@ import util from 'node:util'
import { NdjsonToMessageStream } from '@cucumber/message-streams'
import { Envelope } from '@cucumber/messages'
+import { namingStrategy, NamingStrategyLength } from '@cucumber/query'
import { expect, use } from 'chai'
import chaiXml from 'chai-xml'
import { globbySync } from 'globby'
@@ -22,13 +23,33 @@ describe('Acceptance Tests', async function () {
absolute: true,
})
- for (const ndjsonFile of ndjsonFiles) {
+ const testCases = ndjsonFiles.map((ndjsonFile) => {
const [suiteName] = path.basename(ndjsonFile).split('.')
- it(suiteName, async () => {
+ return {
+ suiteName,
+ source: ndjsonFile,
+ strategyName: 'default',
+ options: {},
+ }
+ })
+
+ testCases.push({
+ suiteName: 'examples-tables',
+ source: '../testdata/src/examples-tables.ndjson',
+ strategyName: 'custom',
+ options: {
+ suiteName: 'Cucumber Suite',
+ testClassName: 'Cucumber Class',
+ testNamingStrategy: namingStrategy(NamingStrategyLength.LONG),
+ },
+ })
+
+ for (const testCase of testCases) {
+ it(testCase.suiteName + ' -> ' + testCase.strategyName, async () => {
let emit: (message: Envelope) => void
let content = ''
formatter.formatter({
- options: {},
+ options: testCase.options,
on(type, handler) {
emit = handler
},
@@ -38,7 +59,7 @@ describe('Acceptance Tests', async function () {
})
await asyncPipeline(
- fs.createReadStream(ndjsonFile, { encoding: 'utf-8' }),
+ fs.createReadStream(testCase.source, { encoding: 'utf-8' }),
new NdjsonToMessageStream(),
new Writable({
objectMode: true,
@@ -49,9 +70,12 @@ describe('Acceptance Tests', async function () {
})
)
- const expectedXml = fs.readFileSync(ndjsonFile.replace('.ndjson', '.xml'), {
- encoding: 'utf-8',
- })
+ const expectedXml = fs.readFileSync(
+ testCase.source.replace('.ndjson', '.' + testCase.strategyName + '.xml'),
+ {
+ encoding: 'utf-8',
+ }
+ )
expect(content).xml.to.be.valid()
expect(content).xml.to.deep.eq(expectedXml)
})
diff --git a/javascript/src/index.ts b/javascript/src/index.ts
index b94874d..96730b1 100644
--- a/javascript/src/index.ts
+++ b/javascript/src/index.ts
@@ -1,5 +1,5 @@
import { Envelope } from '@cucumber/messages'
-import { Query } from '@cucumber/query'
+import { NamingStrategy, Query } from '@cucumber/query'
import xmlbuilder from 'xmlbuilder'
import { makeReport } from './makeReport.js'
@@ -11,7 +11,11 @@ export default {
on,
write,
}: {
- options: { suiteName?: string }
+ options: {
+ suiteName?: string
+ testClassName?: string
+ testNamingStrategy?: NamingStrategy
+ }
on: (type: 'message', handler: (message: Envelope) => void) => void
write: (content: string) => void
}) {
@@ -24,7 +28,7 @@ export default {
query.update(message)
if (message.testRunFinished) {
- const testSuite = makeReport(query)
+ const testSuite = makeReport(query, options.testClassName, options.testNamingStrategy)
builder.att('time', testSuite.time)
builder.att('tests', testSuite.tests)
builder.att('skipped', testSuite.skipped)
diff --git a/javascript/src/makeReport.ts b/javascript/src/makeReport.ts
index ca97b5b..1c3c796 100644
--- a/javascript/src/makeReport.ts
+++ b/javascript/src/makeReport.ts
@@ -1,5 +1,6 @@
import { TestCaseStarted, TestStepResultStatus } from '@cucumber/messages'
import {
+ NamingStrategy,
namingStrategy,
NamingStrategyExampleName,
NamingStrategyFeatureName,
@@ -40,7 +41,11 @@ interface ReportFailure {
stack?: string
}
-export function makeReport(query: Query): ReportSuite {
+export function makeReport(
+ query: Query,
+ testClassName: string | undefined = undefined,
+ customNamingStrategy: NamingStrategy = NAMING_STRATEGY
+): ReportSuite {
const statuses = query.countMostSevereTestStepResultStatus()
return {
time: durationToSeconds(query.findTestRunDuration()),
@@ -51,12 +56,16 @@ export function makeReport(query: Query): ReportSuite {
(status) => status !== TestStepResultStatus.PASSED && status !== TestStepResultStatus.SKIPPED
),
errors: 0,
- testCases: makeTestCases(query),
+ testCases: makeTestCases(query, testClassName, customNamingStrategy),
timestamp: formatTimestamp(query.findTestRunStarted()),
}
}
-function makeTestCases(query: Query): ReadonlyArray {
+function makeTestCases(
+ query: Query,
+ testClassName: string | undefined,
+ testNamingStrategy: NamingStrategy
+): ReadonlyArray {
return query.findAllTestCaseStarted().map((testCaseStarted) => {
const pickle = ensure(
query.findPickleBy(testCaseStarted),
@@ -65,8 +74,8 @@ function makeTestCases(query: Query): ReadonlyArray {
const lineage = ensure(query.findLineageBy(pickle), 'Expected to find Lineage by Pickle')
return {
- classname: lineage.feature?.name ?? pickle.uri,
- name: NAMING_STRATEGY.reduce(lineage, pickle),
+ classname: testClassName ?? lineage.feature?.name ?? pickle.uri,
+ name: testNamingStrategy.reduce(lineage, pickle),
time: durationToSeconds(query.findTestCaseDurationBy(testCaseStarted)),
failure: makeFailure(query, testCaseStarted),
output: query
diff --git a/testdata/src/ambiguous.xml b/testdata/src/ambiguous.default.xml
similarity index 100%
rename from testdata/src/ambiguous.xml
rename to testdata/src/ambiguous.default.xml
diff --git a/testdata/src/attachments.xml b/testdata/src/attachments.default.xml
similarity index 100%
rename from testdata/src/attachments.xml
rename to testdata/src/attachments.default.xml
diff --git a/testdata/src/backgrounds.xml b/testdata/src/backgrounds.default.xml
similarity index 100%
rename from testdata/src/backgrounds.xml
rename to testdata/src/backgrounds.default.xml
diff --git a/testdata/src/cdata.xml b/testdata/src/cdata.default.xml
similarity index 100%
rename from testdata/src/cdata.xml
rename to testdata/src/cdata.default.xml
diff --git a/testdata/src/data-tables.xml b/testdata/src/data-tables.default.xml
similarity index 100%
rename from testdata/src/data-tables.xml
rename to testdata/src/data-tables.default.xml
diff --git a/testdata/src/doc-strings.xml b/testdata/src/doc-strings.default.xml
similarity index 100%
rename from testdata/src/doc-strings.xml
rename to testdata/src/doc-strings.default.xml
diff --git a/testdata/src/empty.xml b/testdata/src/empty.default.xml
similarity index 100%
rename from testdata/src/empty.xml
rename to testdata/src/empty.default.xml
diff --git a/testdata/src/examples-tables-attachment.xml b/testdata/src/examples-tables-attachment.default.xml
similarity index 100%
rename from testdata/src/examples-tables-attachment.xml
rename to testdata/src/examples-tables-attachment.default.xml
diff --git a/testdata/src/examples-tables-undefined.xml b/testdata/src/examples-tables-undefined.default.xml
similarity index 100%
rename from testdata/src/examples-tables-undefined.xml
rename to testdata/src/examples-tables-undefined.default.xml
diff --git a/testdata/src/examples-tables.custom.xml b/testdata/src/examples-tables.custom.xml
new file mode 100644
index 0000000..d2b6c69
--- /dev/null
+++ b/testdata/src/examples-tables.custom.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testdata/src/examples-tables.xml b/testdata/src/examples-tables.default.xml
similarity index 100%
rename from testdata/src/examples-tables.xml
rename to testdata/src/examples-tables.default.xml
diff --git a/testdata/src/global-hooks-afterall-error.xml b/testdata/src/global-hooks-afterall-error.default.xml
similarity index 100%
rename from testdata/src/global-hooks-afterall-error.xml
rename to testdata/src/global-hooks-afterall-error.default.xml
diff --git a/testdata/src/global-hooks-attachments.xml b/testdata/src/global-hooks-attachments.default.xml
similarity index 100%
rename from testdata/src/global-hooks-attachments.xml
rename to testdata/src/global-hooks-attachments.default.xml
diff --git a/testdata/src/global-hooks-beforeall-error.xml b/testdata/src/global-hooks-beforeall-error.default.xml
similarity index 100%
rename from testdata/src/global-hooks-beforeall-error.xml
rename to testdata/src/global-hooks-beforeall-error.default.xml
diff --git a/testdata/src/global-hooks.xml b/testdata/src/global-hooks.default.xml
similarity index 100%
rename from testdata/src/global-hooks.xml
rename to testdata/src/global-hooks.default.xml
diff --git a/testdata/src/hooks-attachment.xml b/testdata/src/hooks-attachment.default.xml
similarity index 100%
rename from testdata/src/hooks-attachment.xml
rename to testdata/src/hooks-attachment.default.xml
diff --git a/testdata/src/hooks-conditional.xml b/testdata/src/hooks-conditional.default.xml
similarity index 100%
rename from testdata/src/hooks-conditional.xml
rename to testdata/src/hooks-conditional.default.xml
diff --git a/testdata/src/hooks-named.xml b/testdata/src/hooks-named.default.xml
similarity index 100%
rename from testdata/src/hooks-named.xml
rename to testdata/src/hooks-named.default.xml
diff --git a/testdata/src/hooks-undefined.xml b/testdata/src/hooks-undefined.default.xml
similarity index 100%
rename from testdata/src/hooks-undefined.xml
rename to testdata/src/hooks-undefined.default.xml
diff --git a/testdata/src/hooks.xml b/testdata/src/hooks.default.xml
similarity index 100%
rename from testdata/src/hooks.xml
rename to testdata/src/hooks.default.xml
diff --git a/testdata/src/markdown.xml b/testdata/src/markdown.default.xml
similarity index 100%
rename from testdata/src/markdown.xml
rename to testdata/src/markdown.default.xml
diff --git a/testdata/src/minimal.xml b/testdata/src/minimal.default.xml
similarity index 100%
rename from testdata/src/minimal.xml
rename to testdata/src/minimal.default.xml
diff --git a/testdata/src/multiple-features-reversed.xml b/testdata/src/multiple-features-reversed.default.xml
similarity index 100%
rename from testdata/src/multiple-features-reversed.xml
rename to testdata/src/multiple-features-reversed.default.xml
diff --git a/testdata/src/multiple-features.xml b/testdata/src/multiple-features.default.xml
similarity index 100%
rename from testdata/src/multiple-features.xml
rename to testdata/src/multiple-features.default.xml
diff --git a/testdata/src/parameter-types.xml b/testdata/src/parameter-types.default.xml
similarity index 100%
rename from testdata/src/parameter-types.xml
rename to testdata/src/parameter-types.default.xml
diff --git a/testdata/src/pending.xml b/testdata/src/pending.default.xml
similarity index 100%
rename from testdata/src/pending.xml
rename to testdata/src/pending.default.xml
diff --git a/testdata/src/regular-expression.xml b/testdata/src/regular-expression.default.xml
similarity index 100%
rename from testdata/src/regular-expression.xml
rename to testdata/src/regular-expression.default.xml
diff --git a/testdata/src/retry-ambiguous.xml b/testdata/src/retry-ambiguous.default.xml
similarity index 100%
rename from testdata/src/retry-ambiguous.xml
rename to testdata/src/retry-ambiguous.default.xml
diff --git a/testdata/src/retry-pending.xml b/testdata/src/retry-pending.default.xml
similarity index 100%
rename from testdata/src/retry-pending.xml
rename to testdata/src/retry-pending.default.xml
diff --git a/testdata/src/retry-undefined.xml b/testdata/src/retry-undefined.default.xml
similarity index 100%
rename from testdata/src/retry-undefined.xml
rename to testdata/src/retry-undefined.default.xml
diff --git a/testdata/src/retry.xml b/testdata/src/retry.default.xml
similarity index 100%
rename from testdata/src/retry.xml
rename to testdata/src/retry.default.xml
diff --git a/testdata/src/rules-backgrounds.xml b/testdata/src/rules-backgrounds.default.xml
similarity index 100%
rename from testdata/src/rules-backgrounds.xml
rename to testdata/src/rules-backgrounds.default.xml
diff --git a/testdata/src/rules.xml b/testdata/src/rules.default.xml
similarity index 100%
rename from testdata/src/rules.xml
rename to testdata/src/rules.default.xml
diff --git a/testdata/src/skipped.xml b/testdata/src/skipped.default.xml
similarity index 100%
rename from testdata/src/skipped.xml
rename to testdata/src/skipped.default.xml
diff --git a/testdata/src/stack-traces.xml b/testdata/src/stack-traces.default.xml
similarity index 100%
rename from testdata/src/stack-traces.xml
rename to testdata/src/stack-traces.default.xml
diff --git a/testdata/src/undefined.xml b/testdata/src/undefined.default.xml
similarity index 100%
rename from testdata/src/undefined.xml
rename to testdata/src/undefined.default.xml
diff --git a/testdata/src/unknown-parameter-type.xml b/testdata/src/unknown-parameter-type.default.xml
similarity index 100%
rename from testdata/src/unknown-parameter-type.xml
rename to testdata/src/unknown-parameter-type.default.xml
diff --git a/testdata/src/unused-steps.xml b/testdata/src/unused-steps.default.xml
similarity index 100%
rename from testdata/src/unused-steps.xml
rename to testdata/src/unused-steps.default.xml