playwright-bdd
Version:
BDD Testing with Playwright runner
295 lines • 11.9 kB
JavaScript
"use strict";
/**
* Generate Playwright test file for feature.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestFile = void 0;
const node_path_1 = __importDefault(require("node:path"));
const formatter_1 = require("./formatter");
const i18n_1 = require("./i18n");
const lang_1 = require("../config/lang");
const importTest_1 = require("./importTest");
const documentQuery_1 = require("../gherkin/documentQuery");
const examplesTitleBuilder_1 = require("./examplesTitleBuilder");
const helpers_1 = require("../gherkin/helpers");
const finder_1 = require("../steps/finder");
const exit_1 = require("../utils/exit");
const hooks_1 = require("./hooks");
const paths_1 = require("./paths");
const specialTags_1 = require("./specialTags");
const background_1 = require("./background");
const test_1 = require("./test");
const sourceMapper_1 = require("./sourceMapper");
const renderer_1 = require("../bddData/renderer");
const tags_1 = require("../steps/tags");
const utils_1 = require("../utils");
const supportedFeatures_1 = require("../playwright/supportedFeatures");
class TestFile {
constructor(options) {
this.options = options;
this.lines = [];
this.backgrounds = [];
this.tests = [];
this.outputPath = (0, paths_1.getSpecFileByFeatureFile)(this.config, this.featureUri);
this.formatter = new formatter_1.Formatter(options.config);
this.gherkinDocumentQuery = new documentQuery_1.GherkinDocumentQuery(this.gherkinDocument);
this.stepFinder = new finder_1.StepFinder(options.config);
this.hooks = new hooks_1.TestFileHooks(this.formatter);
this.tagsFromPath = (0, tags_1.extractTagsFromPath)(this.featureUri);
}
get gherkinDocument() {
return this.options.gherkinDocument;
}
/**
* Returns to feature file, relative to configDir.
* Separator is OS-specific (/ on Unix, \ on Windows).
*/
get featureUri() {
return this.gherkinDocument.uri;
}
get pickles() {
return this.gherkinDocument.pickles;
}
get content() {
return this.lines.join('\n');
}
get language() {
return this.gherkinDocument.feature?.language || lang_1.LANG_EN;
}
get isEnglish() {
return (0, lang_1.isEnglish)(this.language);
}
get config() {
return this.options.config;
}
hasExecutableTests() {
return this.tests.some((test) => !test.skippedByTag);
}
build() {
if (!this.gherkinDocumentQuery.hasPickles())
return this;
this.loadI18nKeywords();
// important to calc suites first, b/c file header depends on used steps and used tags
const suites = this.renderRootSuite();
this.hooks.fillFromTests(this.tests);
// todo: use lines instead of this.lines,
// and pass them to updateTestLocations() explicitly
this.lines = [
...this.renderFileHeader(), // prettier-ignore
...suites,
];
this.renderInplaceBackgrounds();
this.lines.push(...this.renderTechnicalSection());
return this;
}
/**
* Collect missing steps for all tests in the feature file.
*/
getMissingSteps() {
return (this.tests
// if user skipped scenario manually, don't report missing steps
.filter((test) => !test.skippedByTag)
.reduce((acc, test) => {
test.stepsData.forEach((stepData) => {
if (!stepData.matchedDefinition)
acc.push(stepData);
});
return acc;
}, []));
}
getUsedDefinitions() {
const usedDefinitions = new Set();
this.tests.forEach((test) => {
test.stepsData.forEach(({ matchedDefinition }) => {
if (matchedDefinition)
usedDefinitions.add(matchedDefinition.definition);
});
});
return usedDefinitions;
}
renderFileHeader() {
const importTestFrom = this.resolveImportTestFrom();
return this.formatter.fileHeader(this.featureUri, importTestFrom);
}
loadI18nKeywords() {
if (!this.isEnglish) {
this.i18nKeywordsMap = (0, i18n_1.getKeywordsMap)(this.language);
}
}
renderTechnicalSection() {
const worldFixtureName = this.getWorldFixtureName();
const sourceMapper = new sourceMapper_1.SourceMapper(this.lines);
const bddDataRenderer = new renderer_1.BddDataRenderer(this.tests, sourceMapper);
const testUse = this.formatter.testUse([
...this.formatter.testFixture(),
...this.formatter.uriFixture(this.featureUri),
...bddDataRenderer.renderFixture(),
...this.formatter.scenarioHooksFixtures('before', this.hooks.before.getFixtureNames()),
...this.formatter.scenarioHooksFixtures('after', this.hooks.after.getFixtureNames()),
...(worldFixtureName ? this.formatter.worldFixture(worldFixtureName) : []),
...(supportedFeatures_1.supportedFeatures.ariaSnapshots && this.config.aiFix?.promptAttachment
? this.formatter.pageFixtureWithPromptAttachment()
: []),
]);
return [
'// == technical section ==', // prettier-ignore
'',
...this.hooks.render(),
...testUse,
'',
...bddDataRenderer.renderVariable(),
];
}
renderRootSuite() {
const { feature } = this.gherkinDocument;
if (!feature)
throw new Error(`Document without feature.`);
return this.renderDescribe(feature);
}
/**
* Generate test.describe suite for Feature or Rule
*/
renderDescribe(feature) {
const specialTags = new specialTags_1.SpecialTags((0, helpers_1.getTagNames)(feature.tags));
const lines = [];
feature.children.forEach((child) => {
lines.push(...this.renderChild(child));
});
return this.formatter.describe(feature.name, specialTags, lines);
}
// eslint-disable-next-line visual/complexity
renderChild(child) {
if ('rule' in child && child.rule)
return this.renderDescribe(child.rule);
if (child.background)
return this.renderBackgroundPlaceholder(child.background);
if (child.scenario)
return (0, helpers_1.isScenarioOutline)(child.scenario) // prettier-ignore
? this.renderScenarioOutline(child.scenario)
: this.renderScenario(child.scenario);
throw new Error(`Empty child: ${JSON.stringify(child)}`);
}
renderBackgroundPlaceholder(bg) {
const bgGen = new background_1.BackgroundGen(this.formatter, this.i18nKeywordsMap, bg);
this.backgrounds.push(bgGen);
return [bgGen.placeholder];
}
/**
* Insert test.beforeEach for Backgrounds
*/
renderInplaceBackgrounds() {
this.backgrounds.forEach((bg) => bg.renderInplace(this.lines));
}
renderScenario(scenario) {
const testTitle = scenario.name;
const pickle = this.findPickle(scenario.id, testTitle);
const ownTestTags = (0, helpers_1.getTagNames)(scenario.tags);
return this.renderTest(pickle, testTitle, ownTestTags, scenario.steps);
}
/**
* Generate test.describe suite for Scenario Outline
*/
renderScenarioOutline(scenario) {
const specialTags = new specialTags_1.SpecialTags((0, helpers_1.getTagNames)(scenario.tags));
const examplesTitleBuilder = this.createExamplesTitleBuilder(scenario);
const lines = [];
scenario.examples.forEach((examples) => {
examples.tableBody.forEach((exampleRow) => {
const testTitle = examplesTitleBuilder.buildTitle(examples, exampleRow);
const pickle = this.findPickle(exampleRow.id, testTitle);
const ownTestTags = (0, helpers_1.getTagNames)(examples.tags);
const testLines = this.renderTest(pickle, testTitle, ownTestTags, scenario.steps);
lines.push(...testLines);
});
});
// don't render describe without tests
if (!lines.length)
return [];
return this.formatter.describe(scenario.name, specialTags, lines);
}
/**
* Render test for Scenario or Scenario Outline.
*/
renderTest(pickle, testTitle, ownTestTags, gherkinSteps) {
const testTags = (0, utils_1.removeDuplicates)([...this.tagsFromPath, ...(0, helpers_1.getTagNames)(pickle.tags)]);
if (this.isSkippedByTagsExpression(testTags))
return [];
const test = new test_1.TestGen(this.config, this.featureUri, this.i18nKeywordsMap, this.stepFinder, this.formatter, this.backgrounds, pickle, testTitle, gherkinSteps, testTags, ownTestTags);
this.tests.push(test);
return test.render();
}
findPickle(astNodeId, testTitle) {
const pickles = this.gherkinDocumentQuery.getPickles(astNodeId);
const pickle = pickles[0];
if (!pickle) {
(0, exit_1.exit)(`Pickle not found for scenario: ${testTitle}`);
}
else if (pickles.length > 1) {
(0, exit_1.exit)(`Found ${pickles.length} pickles for scenario: ${testTitle}`);
}
return pickle;
}
isSkippedByTagsExpression(tags) {
// see: https://github.com/cucumber/tag-expressions/tree/main/javascript
const { tagsExpression } = this.options;
return tagsExpression && !tagsExpression.evaluate(tags);
}
getWorldFixtureName() {
const worldFixtureNames = new Set();
this.tests.forEach((test) => {
test.stepsData.forEach(({ matchedDefinition }) => {
if (matchedDefinition) {
const { worldFixture } = matchedDefinition.definition;
if (worldFixture)
worldFixtureNames.add(worldFixture);
}
});
});
this.hooks.getWorldFixtureNames().forEach((name) => worldFixtureNames.add(name));
if (worldFixtureNames.size > 1) {
throw new Error([
`All steps and hooks in a feature file should have the same worldFixture.`,
`Found fixtures: ${[...worldFixtureNames].join(', ')}`,
`File: ${this.featureUri}`,
].join('\n'));
}
return [...worldFixtureNames][0];
}
createExamplesTitleBuilder(scenario) {
return new examplesTitleBuilder_1.ExamplesTitleBuilder({
config: this.config,
gherkinDocument: this.gherkinDocument,
isEnglish: this.isEnglish,
scenario,
});
}
resolveImportTestFrom() {
let { importTestFrom } = this.config;
if (!importTestFrom) {
importTestFrom = new importTest_1.ImportTestGuesser(this.featureUri, this.getUsedDefinitions(), this.getUsedPomFixtures(), this.hooks.getCustomTestInstances()).guess();
}
if (!importTestFrom)
return;
const { file, varName } = importTestFrom;
const dir = node_path_1.default.dirname(this.outputPath);
return {
file: node_path_1.default.relative(dir, file),
varName,
};
}
getUsedPomFixtures() {
const usedPomFixtures = new Set();
this.tests.forEach((test) => {
test.stepsData.forEach(({ pomFixtureName }) => {
if (pomFixtureName)
usedPomFixtures.add(pomFixtureName);
});
});
return usedPomFixtures;
}
}
exports.TestFile = TestFile;
//# sourceMappingURL=file.js.map