@zohodesk/testinglibrary 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.eslintrc.js +5 -1
  2. package/build/bdd-framework/cli/commands/env.js +1 -1
  3. package/build/bdd-framework/cli/commands/test.js +9 -3
  4. package/build/bdd-framework/config/env.js +2 -1
  5. package/build/bdd-framework/config/lang.js +14 -0
  6. package/build/bdd-framework/cucumber/loadSteps.js +8 -3
  7. package/build/bdd-framework/decorators.js +2 -2
  8. package/build/bdd-framework/gen/fixtures.js +48 -0
  9. package/build/bdd-framework/gen/formatter.js +57 -10
  10. package/build/bdd-framework/gen/i18n.js +6 -2
  11. package/build/bdd-framework/gen/index.js +7 -6
  12. package/build/bdd-framework/gen/testFile.js +105 -39
  13. package/build/bdd-framework/gen/testNode.js +16 -3
  14. package/build/bdd-framework/gen/testPoms.js +18 -8
  15. package/build/bdd-framework/hooks/scenario.js +107 -0
  16. package/build/bdd-framework/hooks/worker.js +83 -0
  17. package/build/bdd-framework/playwright/fixtureParameterNames.js +24 -8
  18. package/build/bdd-framework/playwright/getLocationInFile.js +13 -7
  19. package/build/bdd-framework/playwright/testTypeImpl.js +11 -7
  20. package/build/bdd-framework/playwright/transform.js +6 -2
  21. package/build/bdd-framework/run/StepInvoker.js +73 -0
  22. package/build/bdd-framework/run/bddFixtures.js +118 -55
  23. package/build/bdd-framework/run/bddWorld.js +24 -36
  24. package/build/bdd-framework/snippets/index.js +3 -1
  25. package/build/bdd-framework/snippets/snippetSyntax.js +3 -1
  26. package/build/bdd-framework/stepDefinitions/createBdd.js +28 -11
  27. package/build/bdd-framework/stepDefinitions/decorators/{poms.js → class.js} +7 -3
  28. package/build/bdd-framework/stepDefinitions/decorators/steps.js +8 -2
  29. package/build/bdd-framework/stepDefinitions/defineStep.js +2 -1
  30. package/build/bdd-framework/utils/exit.js +14 -4
  31. package/build/bdd-framework/utils/index.js +27 -1
  32. package/build/bdd-framework/utils/logger.js +3 -1
  33. package/build/core/playwright/index.js +12 -3
  34. package/build/core/playwright/report-generator.js +2 -1
  35. package/build/core/playwright/setup/config-creator.js +5 -0
  36. package/build/core/playwright/tag-processor.js +6 -2
  37. package/build/setup-folder-structure/reportEnhancement/addonScript.html +25 -0
  38. package/build/setup-folder-structure/reportEnhancement/reportAlteration.js +25 -0
  39. package/changelog.md +10 -0
  40. package/npm-shrinkwrap.json +1 -1
  41. package/package.json +1 -1
  42. package/build/bdd-framework/cucumber/gherkin.d.ts +0 -45
  43. package/build/bdd-framework/gen/poms.js +0 -46
  44. package/build/bdd-framework/stepDefinitions/createDecorators.js +0 -108
package/.eslintrc.js CHANGED
@@ -22,6 +22,10 @@ module.exports = {
22
22
  "sourceType": "module"
23
23
  },
24
24
  "rules": {
25
- "indent": ["error", 2, { "SwitchCase": 1 }]
25
+ "indent": ["error", 2, { "SwitchCase": 1 }],
26
+ "no-empty-pattern": "off",
27
+ "comma-dangle": ["error", "never"],
28
+ "curly": ["error"],
29
+ "brace-style": "error"
26
30
  }
27
31
  }
@@ -33,7 +33,7 @@ function showPackageVersion(packageName) {
33
33
  * to aneble using directly from /dist in tests.
34
34
  */
35
35
  function getOwnVersion() {
36
- return '5.4.0';
36
+ return '5.6.0';
37
37
  }
38
38
  function showPlaywrightConfigPath(cliConfigPath) {
39
39
  const resolvedConfigFile = (0, _loadConfig.resolveConfigFile)(cliConfigPath);
@@ -30,8 +30,12 @@ function readConfigsFromEnv() {
30
30
  }
31
31
  function mergeCliOptions(configs, opts) {
32
32
  configs.forEach(config => {
33
- if ('tags' in opts) config.tags = opts.tags;
34
- if ('verbose' in opts) config.verbose = Boolean(opts.verbose);
33
+ if ('tags' in opts) {
34
+ config.tags = opts.tags;
35
+ }
36
+ if ('verbose' in opts) {
37
+ config.verbose = Boolean(opts.verbose);
38
+ }
35
39
  });
36
40
  }
37
41
  function assertConfigsCount(configs) {
@@ -54,5 +58,7 @@ async function runInWorker(config) {
54
58
  }
55
59
  });
56
60
  const [exitCode] = await (0, _events.once)(worker, 'exit');
57
- if (exitCode) (0, _exit.exit)();
61
+ if (exitCode) {
62
+ (0, _exit.exit)();
63
+ }
58
64
  }
@@ -34,7 +34,8 @@ function getConfigFromEnv(outputDir) {
34
34
  outputDir = _path.default.resolve(outputDir);
35
35
  const config = envConfigs[outputDir];
36
36
  if (!config) {
37
- (0, _exit.exit)(`Config not found for outputDir: "${outputDir}".`, `Available dirs: ${Object.keys(envConfigs).join('\n')}`);
37
+ // exit(`Config not found for outputDir: "${outputDir}".`, `Available dirs: ${Object.keys(envConfigs).join('\n')}`);
38
+ return {};
38
39
  }
39
40
  return config;
40
41
  }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.LANG_EN = void 0;
7
+ exports.isEnglish = isEnglish;
8
+ /**
9
+ * Helpers for Cucumber language option.
10
+ */
11
+ const LANG_EN = exports.LANG_EN = 'en';
12
+ function isEnglish(lang) {
13
+ return !lang || lang === LANG_EN;
14
+ }
@@ -25,9 +25,14 @@ function findStepDefinition(supportCodeLibrary, stepText, file) {
25
25
  const matchedSteps = supportCodeLibrary.stepDefinitions.filter(step => {
26
26
  return step.matchesStepName(stepText);
27
27
  });
28
- if (matchedSteps.length === 0) return;
29
- if (matchedSteps.length > 1) (0, _exit.exit)([`Several step definitions found for text: ${stepText} (${file})`, ...matchedSteps.map(s => `- ${s.pattern}`)].join('\n'));
30
- // todo: check stepDefinition.keyword with PickleStepType
28
+ if (matchedSteps.length === 0) {
29
+ return;
30
+ }
31
+ if (matchedSteps.length > 1) {
32
+ (0, _exit.exit)([`Multiple step definitions matched for text: "${stepText}" (${file})`,
33
+ // todo: print location of every step definition (as in cucumber)
34
+ ...matchedSteps.map(s => ` ${s.pattern}`)].join('\n'));
35
+ }
31
36
  return matchedSteps[0];
32
37
  }
33
38
  function hasTsNodeRegister(runConfiguration) {
@@ -6,11 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  Object.defineProperty(exports, "Fixture", {
7
7
  enumerable: true,
8
8
  get: function () {
9
- return _poms.Fixture;
9
+ return _class.Fixture;
10
10
  }
11
11
  });
12
12
  exports.When = exports.Then = exports.Step = exports.Given = void 0;
13
- var _poms = require("./stepDefinitions/decorators/poms");
13
+ var _class = require("./stepDefinitions/decorators/class");
14
14
  var _steps = require("./stepDefinitions/decorators/steps");
15
15
  const Given = exports.Given = (0, _steps.createStepDecorator)('Given');
16
16
  const When = exports.When = (0, _steps.createStepDecorator)('When');
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.extractFixtureNames = extractFixtureNames;
7
+ exports.extractFixtureNamesFromFnBodyMemo = extractFixtureNamesFromFnBodyMemo;
8
+ var _fixtureParameterNames = require("../playwright/fixtureParameterNames");
9
+ var _bddFixtures = require("../run/bddFixtures");
10
+ var _exit = require("../utils/exit");
11
+ const bodyFixturesSymbol = Symbol('bodyFixtures');
12
+ /**
13
+ * This function is used for playwright-style steps and decorators.
14
+ * It extracts fixtures names from first parameter of function
15
+ * using Playwright's helper.
16
+ */
17
+ function extractFixtureNames(fn) {
18
+ return (0, _fixtureParameterNames.fixtureParameterNames)(fn).filter(name => !(0, _bddFixtures.isBddAutoInjectFixture)(name));
19
+ }
20
+ /**
21
+ * This function is used for cucumber-style steps.
22
+ * It looks for `this.useFixture('xxx')` entries in function body
23
+ * and extracts fixtures names from it.
24
+ */
25
+ function extractFixtureNamesFromFnBodyMemo(fn) {
26
+ if (typeof fn !== 'function') {
27
+ return [];
28
+ }
29
+ const fnWithFixtures = fn;
30
+ if (!fnWithFixtures[bodyFixturesSymbol]) {
31
+ fnWithFixtures[bodyFixturesSymbol] = extractFixtureNamesFromFnBody(fn).filter(name => !(0, _bddFixtures.isBddAutoInjectFixture)(name));
32
+ }
33
+ return fnWithFixtures[bodyFixturesSymbol];
34
+ }
35
+ function extractFixtureNamesFromFnBody(fn) {
36
+ const matches = fn.toString().matchAll(/this\.useFixture\((.+)\)/gi) || [];
37
+ return [...matches].map(match => getFixtureName(match[1]));
38
+ }
39
+ function getFixtureName(arg) {
40
+ if (!/^['"`]/.test(arg)) {
41
+ // todo: log file location with incorrect useFixture
42
+ (0, _exit.exit)('this.useFixture() can accept only static string as an argument.');
43
+ }
44
+ if (arg.startsWith('`') && arg.includes('${')) {
45
+ (0, _exit.exit)('this.useFixture() can accept only static string as an argument.');
46
+ }
47
+ return arg.replace(/['"`]/g, '');
48
+ }
@@ -18,14 +18,20 @@ class Formatter {
18
18
  fileHeader(uri, importTestFrom) {
19
19
  const file = (importTestFrom === null || importTestFrom === void 0 ? void 0 : importTestFrom.file) || '@zohodesk/testinglibrary';
20
20
  let varName = (importTestFrom === null || importTestFrom === void 0 ? void 0 : importTestFrom.varName) || 'test';
21
- if (varName !== 'test') varName = `${varName} as test`;
21
+ if (varName !== 'test') {
22
+ varName = `${varName} as test`;
23
+ }
22
24
  return [`/** Generated from: ${uri} */`,
25
+ // prettier-ignore
23
26
  // this.quoted() is not possible for 'import from' as backticks not parsed
24
27
  `import { ${varName} } from ${JSON.stringify(file)};`, ''];
25
28
  }
26
29
  suite(node, children) {
27
- // prettier-ignore
28
- return [`test.describe${this.getSubFn(node)}(${this.quoted(node.title)}, () => {`, '', ...children.map(indent), `});`, ''];
30
+ const firstLine = `test.describe${this.getSubFn(node)}(${this.quoted(node.title)}, () => {`;
31
+ if (!children.length) {
32
+ return [`${firstLine}});`, ''];
33
+ }
34
+ return [firstLine, '', ...children.map(indent), `});`, ''];
29
35
  }
30
36
  beforeEach(fixtures, children) {
31
37
  const fixturesStr = [...fixtures].join(', ');
@@ -34,8 +40,12 @@ class Formatter {
34
40
  }
35
41
  test(node, fixtures, children) {
36
42
  const fixturesStr = [...fixtures].join(', ');
37
- // prettier-ignore
38
- return [`test${this.getSubFn(node)}(${this.quoted(node.title)}, async ({ ${fixturesStr} }) => {`, ...children.map(indent), `});`, ''];
43
+ const title = this.quoted([node.title, ...node.tags].join(' '));
44
+ const firstLine = `test${this.getSubFn(node)}(${title}, async ({ ${fixturesStr} }) => {`;
45
+ if (!children.length) {
46
+ return [`${firstLine}});`, ''];
47
+ }
48
+ return [firstLine, ...children.map(indent), `});`, ''];
39
49
  }
40
50
  // eslint-disable-next-line max-params
41
51
  step(keyword, text, argument, fixtureNames = []) {
@@ -49,25 +59,62 @@ class Formatter {
49
59
  return `// missing step: ${keyword}(${this.quoted(text)});`;
50
60
  }
51
61
  useFixtures(fixtures) {
52
- return fixtures.length > 0 ? ['// == technical section ==', '', 'test.use({', ...fixtures.map(indent), '});'] : [];
62
+ return fixtures.length > 0 ? ['// == technical section ==',
63
+ // prettier-ignore
64
+ '', 'test.use({', ...fixtures.map(indent), '});'] : [];
53
65
  }
54
66
  testFixture() {
55
67
  return ['$test: ({}, use) => use(test),'];
56
68
  }
69
+ bddWorldFixtures() {
70
+ const fixturesObj = {
71
+ page: null,
72
+ context: null,
73
+ browser: null,
74
+ browserName: null,
75
+ request: null
76
+ };
77
+ const fixtures = Object.keys(fixturesObj).join(', ');
78
+ return [`$bddWorldFixtures: ({ ${fixtures} }, use) => use({ ${fixtures} }),`];
79
+ }
57
80
  tagsFixture(testNodes) {
58
81
  const lines = testNodes.filter(node => node.tags.length).map(node => {
59
82
  // remove first parent as it is the same for all tests: root suite
60
- const key = node.titlePath.slice(1).join(TAGS_FIXTURE_TEST_KEY_SEPARATOR);
83
+ const key = `${node.titlePath.slice(1).join(TAGS_FIXTURE_TEST_KEY_SEPARATOR)} ${node.tags.join(" ")}`;
61
84
  return `${JSON.stringify(key)}: ${JSON.stringify(node.tags)},`;
62
85
  });
63
86
  return lines.length > 0 ? ['$tags: ({}, use, testInfo) => use({', ...lines.map(indent),
64
87
  // .slice(2) -> b/c we remove filename and root suite title
65
88
  `}[testInfo.titlePath.slice(2).join(${JSON.stringify(TAGS_FIXTURE_TEST_KEY_SEPARATOR)})] || []),`] : [];
66
89
  }
90
+ scenarioHookFixtures(fixtureNames) {
91
+ if (!fixtureNames.length) {
92
+ return [];
93
+ }
94
+ const fixtures = fixtureNames.join(', ');
95
+ return [`$scenarioHookFixtures: ({ ${fixtures} }, use) => use({ ${fixtures} }),`];
96
+ }
97
+ workerHookFixtures(fixtureNames) {
98
+ if (!fixtureNames.length) {
99
+ return [];
100
+ }
101
+ const fixtures = fixtureNames.join(', ');
102
+ const scope = this.quoted('worker');
103
+ return [`$workerHookFixtures: [({ ${fixtures} }, use) => use({ ${fixtures} }), { scope: ${scope} }],`];
104
+ }
105
+ langFixture(lang) {
106
+ return [`$lang: ({}, use) => use(${this.quoted(lang)}),`];
107
+ }
67
108
  getSubFn(node) {
68
- if (node.flags.only) return '.only';
69
- if (node.flags.skip) return '.skip';
70
- if (node.flags.fixme) return '.fixme';
109
+ if (node.flags.only) {
110
+ return '.only';
111
+ }
112
+ if (node.flags.skip) {
113
+ return '.skip';
114
+ }
115
+ if (node.flags.fixme) {
116
+ return '.fixme';
117
+ }
71
118
  return '';
72
119
  }
73
120
  /**
@@ -23,10 +23,14 @@ function getKeywordsMap(language) {
23
23
  function handleKeyword(enKeyword, origMap, targetMap) {
24
24
  const nativeKeywords = origMap[enKeyword];
25
25
  // Array.isArray converts to any[]
26
- if (typeof nativeKeywords === 'string') return;
26
+ if (typeof nativeKeywords === 'string') {
27
+ return;
28
+ }
27
29
  nativeKeywords.forEach(nativeKeyword => {
28
30
  nativeKeyword = nativeKeyword.trim();
29
- if (!nativeKeyword || nativeKeyword === '*') return;
31
+ if (!nativeKeyword || nativeKeyword === '*') {
32
+ return;
33
+ }
30
34
  targetMap.set(nativeKeyword, capitalizeFirstLetter(enKeyword));
31
35
  });
32
36
  }
@@ -20,6 +20,7 @@ var _dir = require("../config/dir");
20
20
  var _logger = require("../utils/logger");
21
21
  var _tagExpressions = _interopRequireDefault(require("@cucumber/tag-expressions"));
22
22
  var _exit = require("../utils/exit");
23
+ var _createBdd = require("../stepDefinitions/createBdd");
23
24
  /**
24
25
  * Generate playwright test files from Gherkin documents.
25
26
  */
@@ -38,7 +39,9 @@ class TestFilesGenerator {
38
39
  this.logger = new _logger.Logger({
39
40
  verbose: config.verbose
40
41
  });
41
- if (config.tags) this.tagsExpression = (0, _tagExpressions.default)(config.tags);
42
+ if (config.tags) {
43
+ this.tagsExpression = (0, _tagExpressions.default)(config.tags);
44
+ }
42
45
  }
43
46
  async generate() {
44
47
  await (0, _exit.withExitHandler)(async () => {
@@ -46,7 +49,7 @@ class TestFilesGenerator {
46
49
  await Promise.all([this.loadFeatures(), this.loadSteps()]);
47
50
  this.buildFiles();
48
51
  await this.checkUndefinedSteps();
49
- this.checkImportCustomTest();
52
+ this.checkImportTestFrom();
50
53
  await this.clearOutputDir();
51
54
  await this.saveFiles();
52
55
  });
@@ -133,10 +136,8 @@ class TestFilesGenerator {
133
136
  (0, _exit.exit)();
134
137
  }
135
138
  }
136
- checkImportCustomTest() {
137
- if (this.config.importTestFrom) return;
138
- const hasCustomTest = this.files.some(file => file.hasCustomTest);
139
- if (hasCustomTest) {
139
+ checkImportTestFrom() {
140
+ if (_createBdd.hasCustomTest && !this.config.importTestFrom) {
140
141
  (0, _exit.exit)(`When using custom "test" function in createBdd() you should`, `set "importTestFrom" config option that points to file exporting custom test.`);
141
142
  }
142
143
  }
@@ -10,13 +10,16 @@ var _path = _interopRequireDefault(require("path"));
10
10
  var _formatter = require("./formatter");
11
11
  var _i18n = require("./i18n");
12
12
  var _loadSteps = require("../cucumber/loadSteps");
13
- var _createBdd = require("../stepDefinitions/createBdd");
14
13
  var _index = require("@cucumber/cucumber/lib/formatter/helpers/index");
15
14
  var _utils = require("../utils");
16
15
  var _testPoms = require("./testPoms");
17
16
  var _testNode = require("./testNode");
18
17
  var _stepConfig = require("../stepDefinitions/stepConfig");
19
18
  var _exit = require("../utils/exit");
19
+ var _fixtures = require("./fixtures");
20
+ var _scenario = require("../hooks/scenario");
21
+ var _worker = require("../hooks/worker");
22
+ var _lang = require("../config/lang");
20
23
  /**
21
24
  * Generate test code.
22
25
  */
@@ -26,6 +29,7 @@ class TestFile {
26
29
  lines = [];
27
30
  i18nKeywordsMap;
28
31
  formatter;
32
+ hasCucumberStyle = false;
29
33
  testNodes = [];
30
34
  hasCustomTest = false;
31
35
  undefinedSteps = [];
@@ -37,7 +41,9 @@ class TestFile {
37
41
  const {
38
42
  uri
39
43
  } = this.options.doc;
40
- if (!uri) throw new Error(`Document without uri`);
44
+ if (!uri) {
45
+ throw new Error(`Document without uri`);
46
+ }
41
47
  return uri;
42
48
  }
43
49
  get content() {
@@ -45,7 +51,10 @@ class TestFile {
45
51
  }
46
52
  get language() {
47
53
  var _this$options$doc$fea;
48
- return ((_this$options$doc$fea = this.options.doc.feature) === null || _this$options$doc$fea === void 0 ? void 0 : _this$options$doc$fea.language) || 'en';
54
+ return ((_this$options$doc$fea = this.options.doc.feature) === null || _this$options$doc$fea === void 0 ? void 0 : _this$options$doc$fea.language) || _lang.LANG_EN;
55
+ }
56
+ get isEnglish() {
57
+ return (0, _lang.isEnglish)(this.language);
49
58
  }
50
59
  get config() {
51
60
  return this.options.config;
@@ -55,14 +64,18 @@ class TestFile {
55
64
  }
56
65
  build() {
57
66
  this.loadI18nKeywords();
58
- this.lines = [...this.getFileHeader(), ...this.getRootSuite(), ...this.getFileFixtures()];
67
+ this.lines = [...this.getFileHeader(),
68
+ // prettier-ignore
69
+ ...this.getRootSuite(), ...this.getFileFixtures()];
59
70
  return this;
60
71
  }
61
72
  save() {
62
73
  const dir = _path.default.dirname(this.outputPath);
63
- if (!_fs.default.existsSync(dir)) _fs.default.mkdirSync(dir, {
64
- recursive: true
65
- });
74
+ if (!_fs.default.existsSync(dir)) {
75
+ _fs.default.mkdirSync(dir, {
76
+ recursive: true
77
+ });
78
+ }
66
79
  _fs.default.writeFileSync(this.outputPath, this.content);
67
80
  }
68
81
  getFileHeader() {
@@ -70,7 +83,7 @@ class TestFile {
70
83
  return this.formatter.fileHeader(this.sourceFile, importTestFrom);
71
84
  }
72
85
  loadI18nKeywords() {
73
- if (this.language !== 'en') {
86
+ if (!this.isEnglish) {
74
87
  this.i18nKeywordsMap = (0, _i18n.getKeywordsMap)(this.language);
75
88
  }
76
89
  }
@@ -78,7 +91,9 @@ class TestFile {
78
91
  const {
79
92
  importTestFrom
80
93
  } = this.config;
81
- if (!importTestFrom) return;
94
+ if (!importTestFrom) {
95
+ return;
96
+ }
82
97
  const {
83
98
  file,
84
99
  varName
@@ -90,13 +105,15 @@ class TestFile {
90
105
  };
91
106
  }
92
107
  getFileFixtures() {
93
- return this.formatter.useFixtures([...this.formatter.testFixture(), ...this.formatter.tagsFixture(this.testNodes)]);
108
+ return this.formatter.useFixtures([...this.formatter.testFixture(), ...(!this.isEnglish ? this.formatter.langFixture(this.language) : []), ...((0, _scenario.hasScenarioHooks)() || this.hasCucumberStyle ? this.formatter.bddWorldFixtures() : []), ...this.formatter.scenarioHookFixtures((0, _scenario.getScenarioHooksFixtures)()), ...this.formatter.workerHookFixtures((0, _worker.getWorkerHooksFixtures)()), ...this.formatter.tagsFixture(this.testNodes)]);
94
109
  }
95
110
  getRootSuite() {
96
111
  const {
97
112
  feature
98
113
  } = this.options.doc;
99
- if (!feature) throw new Error(`Document without feature.`);
114
+ if (!feature) {
115
+ throw new Error(`Document without feature.`);
116
+ }
100
117
  return this.getSuite(feature);
101
118
  }
102
119
  /**
@@ -104,16 +121,23 @@ class TestFile {
104
121
  */
105
122
  getSuite(feature, parent) {
106
123
  const node = new _testNode.TestNode(feature, parent);
124
+ if (node.isSkipped()) {
125
+ return this.formatter.suite(node, []);
126
+ }
107
127
  const lines = [];
108
- // const { backgrounds, rules, scenarios } =
109
- // bgFixtures, bgTags - used as fixture hints for decorator steps
110
128
  feature.children.forEach(child => lines.push(...this.getSuiteChild(child, node)));
111
129
  return this.formatter.suite(node, lines);
112
130
  }
113
131
  getSuiteChild(child, parent) {
114
- if ('rule' in child && child.rule) return this.getSuite(child.rule, parent);
115
- if (child.background) return this.getBeforeEach(child.background, parent);
116
- if (child.scenario) return this.getScenarioLines(child.scenario, parent);
132
+ if ('rule' in child && child.rule) {
133
+ return this.getSuite(child.rule, parent);
134
+ }
135
+ if (child.background) {
136
+ return this.getBeforeEach(child.background, parent);
137
+ }
138
+ if (child.scenario) {
139
+ return this.getScenarioLines(child.scenario, parent);
140
+ }
117
141
  throw new Error(`Empty child: ${JSON.stringify(child)}`);
118
142
  }
119
143
  getScenarioLines(scenario, parent) {
@@ -138,10 +162,13 @@ class TestFile {
138
162
  */
139
163
  getOutlineSuite(scenario, parent) {
140
164
  const node = new _testNode.TestNode(scenario, parent);
165
+ if (node.isSkipped()) {
166
+ return this.formatter.suite(node, []);
167
+ }
141
168
  const lines = [];
142
169
  let exampleIndex = 0;
143
170
  scenario.examples.forEach(examples => {
144
- const titleFormat = this.getExamplesTitleFormat(examples);
171
+ const titleFormat = this.getExamplesTitleFormat(scenario, examples);
145
172
  examples.tableBody.forEach(exampleRow => {
146
173
  const testTitle = this.getOutlineTestTitle(titleFormat, examples, exampleRow, ++exampleIndex);
147
174
  const testLines = this.getOutlineTest(scenario, examples, exampleRow, testTitle, node);
@@ -159,7 +186,12 @@ class TestFile {
159
186
  name: title,
160
187
  tags: examples.tags
161
188
  }, parent);
162
- if (this.skipByTagsExpression(node)) return [];
189
+ if (this.skipByTagsExpression(node)) {
190
+ return [];
191
+ }
192
+ if (node.isSkipped()) {
193
+ return this.formatter.test(node, new Set(), []);
194
+ }
163
195
  this.testNodes.push(node);
164
196
  const {
165
197
  fixtures,
@@ -172,7 +204,12 @@ class TestFile {
172
204
  */
173
205
  getTest(scenario, parent) {
174
206
  const node = new _testNode.TestNode(scenario, parent);
175
- if (this.skipByTagsExpression(node)) return [];
207
+ if (this.skipByTagsExpression(node)) {
208
+ return [];
209
+ }
210
+ if (node.isSkipped()) {
211
+ return this.formatter.test(node, new Set(), []);
212
+ }
176
213
  this.testNodes.push(node);
177
214
  const {
178
215
  fixtures,
@@ -242,26 +279,27 @@ class TestFile {
242
279
  language: this.language,
243
280
  previousKeywordType
244
281
  });
245
- let keyword = this.getStepKeyword(step);
282
+ const enKeyword = this.getStepEnglishKeyword(step);
246
283
  if (!stepDefinition) {
247
284
  this.undefinedSteps.push({
248
285
  keywordType,
249
286
  step,
250
287
  pickleStep
251
288
  });
252
- return this.getMissingStep(keyword, keywordType, pickleStep);
289
+ return this.getMissingStep(enKeyword, keywordType, pickleStep);
253
290
  }
254
291
  // for cucumber-style stepConfig is undefined
255
292
  const stepConfig = (0, _stepConfig.getStepConfig)(stepDefinition);
256
- if (stepConfig !== null && stepConfig !== void 0 && stepConfig.hasCustomTest) this.hasCustomTest = true;
257
- // for cucumber-style transform Given/When/Then -> Given_/When_/Then_
258
- // to use own bddWorld (containing PW built-in fixtures)
259
- if (!(0, _stepConfig.isPlaywrightStyle)(stepConfig)) keyword = `${keyword}_`;
260
- // for decorator steps fixtureNames are defined later in second pass
261
- const fixtureNames = (0, _stepConfig.isDecorator)(stepConfig) ? [] : (0, _createBdd.extractFixtureNames)(stepConfig === null || stepConfig === void 0 ? void 0 : stepConfig.fn);
262
- const line = (0, _stepConfig.isDecorator)(stepConfig) ? '' : this.formatter.step(keyword, pickleStep.text, pickleStep.argument, fixtureNames);
293
+ if (stepConfig !== null && stepConfig !== void 0 && stepConfig.hasCustomTest) {
294
+ this.hasCustomTest = true;
295
+ }
296
+ if (!(0, _stepConfig.isPlaywrightStyle)(stepConfig)) {
297
+ this.hasCucumberStyle = true;
298
+ }
299
+ const fixtureNames = this.getStepFixtureNames(stepDefinition);
300
+ const line = (0, _stepConfig.isDecorator)(stepConfig) ? '' : this.formatter.step(enKeyword, pickleStep.text, pickleStep.argument, fixtureNames);
263
301
  return {
264
- keyword,
302
+ keyword: enKeyword,
265
303
  keywordType,
266
304
  fixtureNames,
267
305
  line,
@@ -288,16 +326,29 @@ class TestFile {
288
326
  const hasRowId = !outlineExampleRowId || astNodeIds.includes(outlineExampleRowId);
289
327
  return hasStepId && hasRowId;
290
328
  });
291
- if (pickleStep) return pickleStep;
329
+ if (pickleStep) {
330
+ return pickleStep;
331
+ }
292
332
  }
293
333
  throw new Error(`Pickle step not found for step: ${step.text}`);
294
334
  }
295
- getStepKeyword(step) {
296
- const origKeyword = step.keyword.trim();
297
- const enKeyword = origKeyword === '*' ? 'And' : this.getEnglishKeyword(origKeyword);
298
- if (!enKeyword) throw new Error(`Keyword not found: ${origKeyword}`);
335
+ getStepEnglishKeyword(step) {
336
+ const nativeKeyword = step.keyword.trim();
337
+ const enKeyword = nativeKeyword === '*' ? 'And' : this.getEnglishKeyword(nativeKeyword);
338
+ if (!enKeyword) {
339
+ throw new Error(`Keyword not found: ${nativeKeyword}`);
340
+ }
299
341
  return enKeyword;
300
342
  }
343
+ getStepFixtureNames(stepDefinition) {
344
+ const stepConfig = (0, _stepConfig.getStepConfig)(stepDefinition);
345
+ if ((0, _stepConfig.isPlaywrightStyle)(stepConfig)) {
346
+ // for decorator steps fixtureNames are defined later in second pass
347
+ return (0, _stepConfig.isDecorator)(stepConfig) ? [] : (0, _fixtures.extractFixtureNames)(stepConfig.fn);
348
+ } else {
349
+ return (0, _fixtures.extractFixtureNamesFromFnBodyMemo)(stepDefinition.code);
350
+ }
351
+ }
301
352
  getDecoratorStep(step, testPoms) {
302
353
  const {
303
354
  keyword,
@@ -323,11 +374,16 @@ class TestFile {
323
374
  exampleRow.cells.forEach((cell, index) => {
324
375
  var _examples$tableHeader;
325
376
  const colName = (_examples$tableHeader = examples.tableHeader) === null || _examples$tableHeader === void 0 || (_examples$tableHeader = _examples$tableHeader.cells[index]) === null || _examples$tableHeader === void 0 ? void 0 : _examples$tableHeader.value;
326
- if (colName) params[colName] = cell.value;
377
+ if (colName) {
378
+ params[colName] = cell.value;
379
+ }
327
380
  });
328
381
  return (0, _utils.template)(titleFormat, params);
329
382
  }
330
- getExamplesTitleFormat(examples) {
383
+ getExamplesTitleFormat(scenario, examples) {
384
+ return this.getExamplesTitleFormatFromComment(examples) || this.getExamplesTitleFormatFromScenarioName(scenario, examples) || this.config.examplesTitleFormat;
385
+ }
386
+ getExamplesTitleFormatFromComment(examples) {
331
387
  var _comment$text;
332
388
  const {
333
389
  line
@@ -338,12 +394,22 @@ class TestFile {
338
394
  });
339
395
  const commentText = comment === null || comment === void 0 || (_comment$text = comment.text) === null || _comment$text === void 0 ? void 0 : _comment$text.trim();
340
396
  const prefix = '# title-format:';
341
- return commentText !== null && commentText !== void 0 && commentText.startsWith(prefix) ? commentText.replace(prefix, '').trim() : this.config.examplesTitleFormat;
397
+ return commentText !== null && commentText !== void 0 && commentText.startsWith(prefix) ? commentText.replace(prefix, '').trim() : '';
398
+ }
399
+ getExamplesTitleFormatFromScenarioName(scenario, examples) {
400
+ var _examples$tableHeader2;
401
+ const columnsInScenarioName = (0, _utils.extractTemplateParams)(scenario.name);
402
+ const hasColumnsFromExamples = columnsInScenarioName.length && ((_examples$tableHeader2 = examples.tableHeader) === null || _examples$tableHeader2 === void 0 || (_examples$tableHeader2 = _examples$tableHeader2.cells) === null || _examples$tableHeader2 === void 0 ? void 0 : _examples$tableHeader2.some(cell => {
403
+ return cell.value && columnsInScenarioName.includes(cell.value);
404
+ }));
405
+ return hasColumnsFromExamples ? scenario.name : '';
342
406
  }
343
407
  skipByTagsExpression(node) {
344
- var _this$options$tagsExp;
345
408
  // see: https://github.com/cucumber/tag-expressions/tree/main/javascript
346
- return ((_this$options$tagsExp = this.options.tagsExpression) === null || _this$options$tagsExp === void 0 ? void 0 : _this$options$tagsExp.evaluate(node.tags)) === false;
409
+ const {
410
+ tagsExpression
411
+ } = this.options;
412
+ return tagsExpression && !tagsExpression.evaluate(node.tags);
347
413
  }
348
414
  isOutline(scenario) {
349
415
  const keyword = this.getEnglishKeyword(scenario.keyword);
@@ -23,6 +23,9 @@ class TestNode {
23
23
  this.title = gherkinNode.name;
24
24
  this.titlePath = ((parent === null || parent === void 0 ? void 0 : parent.titlePath) || []).concat([this.title]);
25
25
  }
26
+ isSkipped() {
27
+ return this.flags.skip || this.flags.fixme;
28
+ }
26
29
  initOwnTags(gherkinNode) {
27
30
  const tagNames = (0, _utils.removeDuplicates)(getTagNames(gherkinNode.tags));
28
31
  tagNames.forEach(tag => {
@@ -33,10 +36,20 @@ class TestNode {
33
36
  }
34
37
  });
35
38
  }
39
+ // eslint-disable-next-line complexity
36
40
  setFlag(tag) {
37
- if (tag === '@only') this.flags.only = true;
38
- if (tag === '@skip') this.flags.skip = true;
39
- if (tag === '@fixme') this.flags.fixme = true;
41
+ // in case of several system tags, @only takes precendence
42
+ if (tag === '@only') {
43
+ this.flags = {
44
+ only: true
45
+ };
46
+ }
47
+ if (tag === '@skip' && !this.flags.only) {
48
+ this.flags.skip = true;
49
+ }
50
+ if (tag === '@fixme' && !this.flags.only) {
51
+ this.flags.fixme = true;
52
+ }
40
53
  }
41
54
  }
42
55
  exports.TestNode = TestNode;