@zohodesk/testinglibrary 0.1.9-exp-actors → 0.2.0

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/build/bdd-framework/cli/commands/env.js +1 -1
  2. package/build/bdd-framework/cli/commands/export.js +18 -3
  3. package/build/bdd-framework/decorators.js +2 -2
  4. package/build/bdd-framework/gen/formatter.js +57 -13
  5. package/build/bdd-framework/gen/index.js +21 -9
  6. package/build/bdd-framework/gen/specialTags.js +70 -0
  7. package/build/bdd-framework/gen/testFile.js +10 -5
  8. package/build/bdd-framework/gen/testNode.js +4 -29
  9. package/build/bdd-framework/gen/testPoms.js +1 -1
  10. package/build/bdd-framework/index.js +1 -1
  11. package/build/bdd-framework/playwright/testTypeImpl.js +28 -10
  12. package/build/bdd-framework/playwright/types.js +8 -1
  13. package/build/bdd-framework/playwright/utils.js +22 -0
  14. package/build/bdd-framework/reporter/cucumber/base.js +2 -7
  15. package/build/bdd-framework/reporter/cucumber/html.js +9 -4
  16. package/build/bdd-framework/reporter/cucumber/messagesBuilder/AttachmentMapper.js +28 -10
  17. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Builder.js +21 -20
  18. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Hook.js +3 -3
  19. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestCaseRun.js +46 -18
  20. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestCaseRunHooks.js +41 -20
  21. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestStepAttachments.js +19 -2
  22. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestStepRun.js +42 -16
  23. package/build/bdd-framework/reporter/cucumber/messagesBuilder/{pwUtils.js → pwStepUtils.js} +33 -14
  24. package/build/bdd-framework/run/StepInvoker.js +8 -7
  25. package/build/bdd-framework/run/bddData/index.js +59 -0
  26. package/build/bdd-framework/run/bddData/types.js +5 -0
  27. package/build/bdd-framework/run/bddFixtures.js +5 -4
  28. package/build/bdd-framework/run/bddWorld.js +3 -3
  29. package/build/bdd-framework/run/bddWorldInternal.js +1 -5
  30. package/build/bdd-framework/snippets/index.js +1 -1
  31. package/build/bdd-framework/steps/createBdd.js +78 -0
  32. package/build/bdd-framework/steps/decorators/class.js +68 -0
  33. package/build/bdd-framework/steps/decorators/steps.js +98 -0
  34. package/build/bdd-framework/steps/defineStep.js +62 -0
  35. package/build/bdd-framework/steps/stepConfig.js +24 -0
  36. package/build/core/playwright/env-initializer.js +17 -2
  37. package/build/core/playwright/helpers/configFileNameProvider.js +1 -0
  38. package/build/core/playwright/index.js +14 -71
  39. package/build/core/playwright/readConfigFile.js +13 -2
  40. package/build/index.d.ts +2 -21
  41. package/changelog.md +14 -0
  42. package/npm-shrinkwrap.json +1 -1
  43. package/package.json +3 -2
  44. package/build/bdd-framework/run/bddDataAttachment.js +0 -46
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.AttachmentMapper = void 0;
7
7
  var _AutofillMap = require("../../../utils/AutofillMap");
8
- var _pwUtils = require("./pwUtils");
9
- var _bddDataAttachment = require("../../../run/bddDataAttachment");
8
+ var _pwStepUtils = require("./pwStepUtils");
9
+ var _stripAnsiEscapes = require("../../../utils/stripAnsiEscapes");
10
10
  class AttachmentMapper {
11
11
  result;
12
12
  stepAttachments = new _AutofillMap.AutofillMap();
@@ -20,12 +20,14 @@ class AttachmentMapper {
20
20
  }
21
21
  mapAttachments() {
22
22
  const allAttachments = this.result.attachments.slice();
23
- const attachmentSteps = (0, _pwUtils.collectStepsWithCategory)(this.result, 'attach');
23
+ const attachmentSteps = (0, _pwStepUtils.collectStepsWithCategory)(this.result, 'attach');
24
24
  attachmentSteps.forEach(attachmentStep => {
25
25
  this.mapAttachment(attachmentStep, allAttachments);
26
26
  });
27
27
  this.unusedAttachments.push(...allAttachments);
28
28
  this.mapUnusedAttachments();
29
+ this.mapStdoutAttachments('stdout');
30
+ this.mapStdoutAttachments('stderr');
29
31
  }
30
32
  mapAttachment(attachmentStep, allAttachments) {
31
33
  const index = allAttachments.findIndex(a => getAttachmentStepTitle(a.name) === attachmentStep.title);
@@ -33,9 +35,6 @@ class AttachmentMapper {
33
35
  throw new Error(`Attachment not found for step: ${attachmentStep.title}`);
34
36
  }
35
37
  const [foundAttachment] = allAttachments.splice(index, 1);
36
- if ((0, _bddDataAttachment.isBddDataAttachment)(foundAttachment)) {
37
- return;
38
- }
39
38
  const parentStep = attachmentStep.parent;
40
39
  // step.parent is empty:
41
40
  // - in PW = 1.34 for screenshot attachment
@@ -49,13 +48,32 @@ class AttachmentMapper {
49
48
  return;
50
49
  }
51
50
  // map unused attachments to the 'After Hooks' step
52
- const afterHooksRoot = (0, _pwUtils.getHooksRootStep)(this.result, 'after');
53
- if (!afterHooksRoot) {
54
- throw new Error(`Can not find after hooks root to attach unused attachments.`);
55
- }
51
+ const afterHooksRoot = this.getAfterHooksRoot();
56
52
  const stepAttachments = this.stepAttachments.getOrCreate(afterHooksRoot, () => []);
57
53
  stepAttachments.push(...this.unusedAttachments);
58
54
  }
55
+ mapStdoutAttachments(name) {
56
+ var _this$result$name;
57
+ // map stdout / stderr to the 'After Hooks' step
58
+ if (!((_this$result$name = this.result[name]) !== null && _this$result$name !== void 0 && _this$result$name.length)) {
59
+ return;
60
+ }
61
+ const body = this.result[name].map(s => typeof s === 'string' ? (0, _stripAnsiEscapes.stripAnsiEscapes)(s) : s).join('');
62
+ const afterHooksRoot = this.getAfterHooksRoot();
63
+ const stepAttachments = this.stepAttachments.getOrCreate(afterHooksRoot, () => []);
64
+ stepAttachments.push({
65
+ name,
66
+ contentType: 'text/plain',
67
+ body: Buffer.from(body)
68
+ });
69
+ }
70
+ getAfterHooksRoot() {
71
+ const afterHooksRoot = (0, _pwStepUtils.getHooksRootPwStep)(this.result, 'after');
72
+ if (!afterHooksRoot) {
73
+ throw new Error(`Can not find after hooks root.`);
74
+ }
75
+ return afterHooksRoot;
76
+ }
59
77
  }
60
78
  // See: https://github.com/microsoft/playwright/blob/main/packages/playwright/src/worker/testInfo.ts#L413
61
79
  exports.AttachmentMapper = AttachmentMapper;
@@ -43,10 +43,16 @@ class MessagesBuilder {
43
43
  this.onEndPromise = new Promise(resolve => this.onEndPromiseResolve = resolve);
44
44
  }
45
45
  onTestEnd(test, result) {
46
- this.onTestEnds.push({
47
- test,
48
- result
49
- });
46
+ // For skipped tests Playwright doesn't run fixtures
47
+ // and we don't have bddData attachment -> don't know feature uri.
48
+ // Don't add such test run to report.
49
+ if (test.expectedStatus === 'skipped') {
50
+ return;
51
+ }
52
+ // Important to create TestCaseRun here,
53
+ // b/c test properties can change after retries (e.g. annotations)
54
+ const testCaseRun = new _TestCaseRun.TestCaseRun(test, result, this.hooks);
55
+ this.testCaseRuns.push(testCaseRun);
50
56
  }
51
57
  onEnd(fullResult) {
52
58
  this.fullResult = fullResult;
@@ -66,7 +72,7 @@ class MessagesBuilder {
66
72
  async doBuildMessages() {
67
73
  await this.onEndPromise;
68
74
  // order here is important
69
- this.createTestCaseRuns();
75
+ // this.createTestCaseRuns();
70
76
  await this.loadFeatures();
71
77
  this.createTestCases();
72
78
  this.addMeta();
@@ -100,21 +106,16 @@ class MessagesBuilder {
100
106
  async loadFeatures() {
101
107
  await this.gherkinDocuments.load(this.testCaseRuns);
102
108
  }
103
- createTestCaseRuns() {
104
- this.onTestEnds.forEach(({
105
- test,
106
- result
107
- }) => {
108
- // For skipped tests Playwright doesn't run fixtures
109
- // and we don't have bddData attachment -> don't know feature uri.
110
- // Don't add such test run to report.
111
- if (test.expectedStatus === 'skipped') {
112
- return;
113
- }
114
- const testCaseRun = new _TestCaseRun.TestCaseRun(test, result, this.hooks);
115
- this.testCaseRuns.push(testCaseRun);
116
- });
117
- }
109
+ // private createTestCaseRuns() {
110
+ // this.onTestEnds.forEach(({ test, result }) => {
111
+ // // For skipped tests Playwright doesn't run fixtures
112
+ // // and we don't have bddData attachment -> don't know feature uri.
113
+ // // Don't add such test run to report.
114
+ // if (test.expectedStatus === 'skipped') return;
115
+ // const testCaseRun = new TestCaseRun(test, result, this.hooks);
116
+ // this.testCaseRuns.push(testCaseRun);
117
+ // });
118
+ // }
118
119
  addMeta() {
119
120
  this.report.meta = new _Meta.Meta().buildMessage();
120
121
  }
@@ -60,10 +60,10 @@ class Hook {
60
60
  const uri = file ? _path.default.relative((0, _configDir.getPlaywrightConfigDir)(), file) : undefined;
61
61
  return {
62
62
  uri,
63
- location: {
64
- line: line || 0,
63
+ location: line ? {
64
+ line,
65
65
  column
66
- }
66
+ } : undefined
67
67
  };
68
68
  }
69
69
  }
@@ -7,11 +7,11 @@ exports.TestCaseRun = void 0;
7
7
  var _utils = require("../../../utils");
8
8
  var _TestStepRun = require("./TestStepRun");
9
9
  var _timing = require("./timing");
10
- var _pwUtils = require("./pwUtils");
11
- var _bddDataAttachment = require("../../../run/bddDataAttachment");
10
+ var _pwStepUtils = require("./pwStepUtils");
12
11
  var _AttachmentMapper = require("./AttachmentMapper");
13
12
  var _TestCaseRunHooks = require("./TestCaseRunHooks");
14
13
  var _Projects = require("./Projects");
14
+ var _bddData = require("../../../run/bddData");
15
15
  class TestCaseRun {
16
16
  test;
17
17
  result;
@@ -21,6 +21,10 @@ class TestCaseRun {
21
21
  testCase;
22
22
  attachmentMapper;
23
23
  projectInfo;
24
+ // collect steps with error and show only these errors in report.
25
+ // it allows to not show the same error on parent steps
26
+ errorSteps = new Set();
27
+ timeoutedStep;
24
28
  executedBeforeHooks;
25
29
  executedAfterHooks;
26
30
  executedSteps;
@@ -30,7 +34,7 @@ class TestCaseRun {
30
34
  this.result = result;
31
35
  this.hooks = hooks;
32
36
  this.id = this.generateTestRunId();
33
- this.bddData = this.getBddData();
37
+ this.bddData = this.extractBddData();
34
38
  this.projectInfo = (0, _Projects.getProjectInfo)(this.test);
35
39
  this.attachmentMapper = new _AttachmentMapper.AttachmentMapper(this.result);
36
40
  this.executedSteps = this.fillExecutedSteps();
@@ -43,20 +47,30 @@ class TestCaseRun {
43
47
  }
44
48
  return this.testCase;
45
49
  }
50
+ isTimeouted() {
51
+ return this.result.status === 'timedOut';
52
+ }
46
53
  generateTestRunId() {
47
- return `${this.test.id}-run-${this.result.retry}`;
54
+ return `${this.test.id}-attempt-${this.result.retry}`;
48
55
  }
49
- getBddData() {
50
- const bddData = (0, _bddDataAttachment.getBddDataFromTestResult)(this.result);
56
+ extractBddData() {
57
+ const {
58
+ bddData,
59
+ annotationIndex
60
+ } = (0, _bddData.getBddDataFromTest)(this.test);
51
61
  if (!bddData) {
52
- throw new Error([`BDD data attachment is not found for test: ${this.test.title}`, `Did you set enrichReporterData: true in the Playwright config?`, ''].join('\n'));
62
+ throw new Error(`__bddData annotation is not found for test "${this.test.title}".`);
53
63
  }
64
+ // remove __bddData annotation from test (mutate)
65
+ this.test.annotations.splice(annotationIndex, 1);
54
66
  return bddData;
55
67
  }
56
68
  fillExecutedSteps() {
57
- const possiblePwSteps = this.getPossiblePlaywrightSteps();
69
+ const possiblePwSteps = this.getPossiblePlaywrightBddSteps();
58
70
  return this.bddData.steps.map(bddDataStep => {
59
71
  const pwStep = this.findPlaywrightStep(possiblePwSteps, bddDataStep);
72
+ this.registerErrorStep(pwStep);
73
+ this.registerTimeoutedStep(pwStep);
60
74
  return {
61
75
  bddDataStep,
62
76
  pwStep
@@ -66,6 +80,23 @@ class TestCaseRun {
66
80
  fillExecutedHooks(hookType) {
67
81
  return new _TestCaseRunHooks.TestCaseRunHooks(this, hookType).fill(this.executedSteps);
68
82
  }
83
+ registerErrorStep(pwStep) {
84
+ if (pwStep !== null && pwStep !== void 0 && pwStep.error) {
85
+ this.errorSteps.add(pwStep);
86
+ }
87
+ }
88
+ // eslint-disable-next-line complexity
89
+ registerTimeoutedStep(pwStep) {
90
+ if (!pwStep || !this.isTimeouted() || this.timeoutedStep) {
91
+ return;
92
+ }
93
+ const {
94
+ error
95
+ } = pwStep;
96
+ if ((0, _pwStepUtils.isUnknownDuration)(pwStep) || this.result.errors.some(e => e.message === (error === null || error === void 0 ? void 0 : error.message))) {
97
+ this.timeoutedStep = pwStep;
98
+ }
99
+ }
69
100
  buildMessages() {
70
101
  return [this.buildTestCaseStarted(), ...this.executedBeforeHooks.buildMessages(), ...this.buildStepRuns(), ...this.executedAfterHooks.buildMessages(), this.buildTestCaseFinished()];
71
102
  }
@@ -108,19 +139,16 @@ class TestCaseRun {
108
139
  };
109
140
  }
110
141
  findPlaywrightStep(possiblePwSteps, bddDataStep) {
111
- const pwStep = possiblePwSteps.find(pwStep => {
142
+ return possiblePwSteps.find(pwStep => {
112
143
  return pwStep.location && (0, _utils.stringifyLocation)(pwStep.location) === bddDataStep.pwStepLocation;
113
144
  });
114
- if (!pwStep) {
115
- throw new Error('Playwright step not found for bdd step');
116
- }
117
- return pwStep;
118
145
  }
119
- getPossiblePlaywrightSteps() {
120
- const beforeHooksRoot = (0, _pwUtils.getHooksRootStep)(this.result, 'before');
121
- const bgSteps = (0, _pwUtils.collectStepsWithCategory)(beforeHooksRoot, 'test.step');
122
- const topLevelSteps = this.result.steps.filter(step => step.category === 'test.step');
123
- return [...bgSteps, ...topLevelSteps];
146
+ getPossiblePlaywrightBddSteps() {
147
+ // Before we collected only top-level steps and steps from before hooks (as they are background)
148
+ // But it's more reliable to just collect all test.step items b/c some Playwright versions
149
+ // move steps to fixtures (see: https://github.com/microsoft/playwright/issues/30075)
150
+ // Collecting all test.step items should be ok, as later we anyway map them by location.
151
+ return (0, _pwStepUtils.collectStepsWithCategory)(this.result, 'test.step');
124
152
  }
125
153
  }
126
154
  exports.TestCaseRun = TestCaseRun;
@@ -5,12 +5,12 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.TestCaseRunHooks = void 0;
7
7
  var _Hook = require("./Hook");
8
- var _pwUtils = require("./pwUtils");
8
+ var _pwStepUtils = require("./pwStepUtils");
9
9
  var _TestStepRun = require("./TestStepRun");
10
10
  class TestCaseRunHooks {
11
11
  testCaseRun;
12
12
  hookType;
13
- rootStep;
13
+ rootPwStep;
14
14
  candidateSteps = [];
15
15
  hookSteps = new Set();
16
16
  executedHooks = new Map();
@@ -22,9 +22,10 @@ class TestCaseRunHooks {
22
22
  this.setRootStep();
23
23
  this.setCandidateSteps();
24
24
  this.addStepsWithName();
25
- this.addStepsWithAttachment();
26
25
  this.addStepWithError();
27
- this.excludeBackgroundSteps(mainSteps);
26
+ this.addStepWithTimeout();
27
+ this.addStepsWithAttachment();
28
+ this.excludeMainSteps(mainSteps);
28
29
  this.setExecutedHooks();
29
30
  return this;
30
31
  }
@@ -33,7 +34,7 @@ class TestCaseRunHooks {
33
34
  this.testCaseRun.getTestCase().getHooks(this.hookType).forEach(hookInfo => {
34
35
  const executedHook = this.executedHooks.get(hookInfo.hook.internalId);
35
36
  // todo: if pwStep is not found in this.executedBeforeHooks,
36
- // it means that this hook comes from another run of this test case.
37
+ // it means that this hook comes from another attempt of this test case.
37
38
  // We can stil try to find it in test result, as otherwise it will be marked as skipped,
38
39
  // but actually it was executed.
39
40
  const testStepRun = new _TestStepRun.TestStepRun(this.testCaseRun, hookInfo.testStep, executedHook === null || executedHook === void 0 ? void 0 : executedHook.pwStep);
@@ -42,13 +43,13 @@ class TestCaseRunHooks {
42
43
  return messages;
43
44
  }
44
45
  setRootStep() {
45
- this.rootStep = (0, _pwUtils.getHooksRootStep)(this.testCaseRun.result, this.hookType);
46
+ this.rootPwStep = (0, _pwStepUtils.getHooksRootPwStep)(this.testCaseRun.result, this.hookType);
46
47
  }
47
48
  setCandidateSteps() {
48
- if (this.rootStep) {
49
- this.candidateSteps.push(this.rootStep);
49
+ if (this.rootPwStep) {
50
+ this.candidateSteps.push(this.rootPwStep);
50
51
  }
51
- this.candidateSteps.push(...(0, _pwUtils.collectStepsDfs)(this.rootStep));
52
+ this.candidateSteps.push(...(0, _pwStepUtils.collectStepsDfs)(this.rootPwStep));
52
53
  }
53
54
  addStepsWithName() {
54
55
  this.candidateSteps.forEach(pwStep => {
@@ -68,25 +69,45 @@ class TestCaseRunHooks {
68
69
  });
69
70
  }
70
71
  addStepWithError() {
71
- const stepWithError = (0, _pwUtils.findDeepestErrorStep)(this.rootStep);
72
+ const stepWithError = (0, _pwStepUtils.findDeepestStepWithError)(this.rootPwStep);
72
73
  if (stepWithError) {
73
74
  this.hookSteps.add(stepWithError);
74
75
  // in Playwright error is inherited by all parent steps,
75
76
  // but we want to show it once (in the deepest step)
76
- this.hookSteps.forEach(step => {
77
- if (step !== stepWithError) {
78
- delete step.error;
79
- }
80
- });
77
+ this.testCaseRun.registerErrorStep(stepWithError);
78
+ this.testCaseRun.registerTimeoutedStep(stepWithError);
79
+ }
80
+ }
81
+ addStepWithTimeout() {
82
+ if (!this.testCaseRun.isTimeouted()) {
83
+ return;
84
+ }
85
+ if (this.testCaseRun.timeoutedStep) {
86
+ return;
87
+ }
88
+ const timeoutedStep = this.hookType === 'before' ?
89
+ // Timeouted steps have duration = -1 in PW <= 1.39 and no error field.
90
+ // In PW > 1.39 timeouted steps have '.error' populated
91
+ (0, _pwStepUtils.findDeepestStepWithUnknownDuration)(this.rootPwStep) :
92
+ // Timeouted after hooks don't have duration = -1,
93
+ // so there is no way to find which exactly fixture timed out.
94
+ // We mark root 'After Hooks' step as timeouted.
95
+ this.rootPwStep;
96
+ if (timeoutedStep) {
97
+ this.hookSteps.add(timeoutedStep);
98
+ this.testCaseRun.timeoutedStep = timeoutedStep;
81
99
  }
82
100
  }
83
- excludeBackgroundSteps(mainSteps) {
84
- // exclude background steps, b/c they are in pickle, not in hooks.
101
+ excludeMainSteps(mainSteps) {
102
+ // - exclude background steps, b/c they are in pickle and should not in hooks.
103
+ // - exclude other test.step items that are bdd steps and should not be in hooks.
85
104
  // Important to run this fn after this.fillExecutedSteps()
86
105
  // as we assume steps are already populated
87
- if (this.hookType === 'before') {
88
- mainSteps.forEach(stepInfo => this.hookSteps.delete(stepInfo.pwStep));
89
- }
106
+ mainSteps.forEach(stepInfo => {
107
+ if (stepInfo.pwStep) {
108
+ this.hookSteps.delete(stepInfo.pwStep);
109
+ }
110
+ });
90
111
  }
91
112
  setExecutedHooks() {
92
113
  this.hookSteps.forEach(pwStep => {
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.TestStepAttachments = void 0;
8
8
  var _fs = _interopRequireDefault(require("fs"));
9
9
  var messages = _interopRequireWildcard(require("@cucumber/messages"));
10
+ var _path = _interopRequireDefault(require("path"));
10
11
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
11
12
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
13
  /**
@@ -44,7 +45,23 @@ class TestStepAttachments {
44
45
  };
45
46
  }
46
47
  getAttachmentBodyBase64(pwAttachment) {
47
- return pwAttachment.path ? _fs.default.readFileSync(pwAttachment.path, 'base64') : pwAttachment.body.toString('base64');
48
+ if (pwAttachment.path) {
49
+ if (_fs.default.existsSync(pwAttachment.path)) {
50
+ return _fs.default.readFileSync(pwAttachment.path, 'base64');
51
+ }
52
+ throw createMissingAttachmentError(pwAttachment.path);
53
+ }
54
+ if (pwAttachment.body) {
55
+ return pwAttachment.body.toString('base64');
56
+ }
57
+ throw new Error(`Playwright attachment without path and body`);
48
58
  }
49
59
  }
50
- exports.TestStepAttachments = TestStepAttachments;
60
+ exports.TestStepAttachments = TestStepAttachments;
61
+ function createMissingAttachmentError(attachmentPath) {
62
+ const attachmentDir = _path.default.join(_path.default.dirname(attachmentPath));
63
+ const attachmentDirExists = _fs.default.existsSync(attachmentDir);
64
+ const files = attachmentDirExists ? _fs.default.readdirSync(attachmentDir) : [];
65
+ const errorMsg = [`Attachment file is not found:`, attachmentPath, `Attachment dir ${attachmentDirExists ? 'exists' : 'does not exist'}.`, ...(attachmentDirExists ? [`Available files (${files.length}):`, ...files] : []), ''].join('\n');
66
+ return new Error(errorMsg);
67
+ }
@@ -25,6 +25,9 @@ class TestStepRun {
25
25
  // prettier-ignore
26
26
  ...stepAttachments.buildMessages(), this.buildTestStepFinished()];
27
27
  }
28
+ isHook() {
29
+ return Boolean(this.testStep.hookId);
30
+ }
28
31
  wasExecuted() {
29
32
  return Boolean(this.pwStep);
30
33
  }
@@ -45,16 +48,17 @@ class TestStepRun {
45
48
  };
46
49
  }
47
50
  buildTestStepFinished() {
48
- var _this$pwStep;
49
- const error = (_this$pwStep = this.pwStep) === null || _this$pwStep === void 0 ? void 0 : _this$pwStep.error;
51
+ const error = this.getStepError();
50
52
  const testStepFinished = {
51
53
  testCaseStartedId: this.testCaseRun.id,
52
54
  testStepId: this.testStep.id,
53
55
  testStepResult: {
54
56
  duration: messages.TimeConversion.millisecondsToDuration(this.duration),
55
57
  status: this.getStatus(error),
56
- message: error ? formatError(error) : undefined,
57
- exception: error ? this.buildException(error) : undefined
58
+ // 'message' is deprecated since cucumber 10.4 in favor of 'exception' field
59
+ // See: https://github.com/cucumber/react-components/pull/345
60
+ message: error ? buildErrorMessage(error) : undefined,
61
+ exception: error ? buildException(error) : undefined
58
62
  },
59
63
  timestamp: (0, _timing.toCucumberTimestamp)(this.startTime.getTime() + this.duration)
60
64
  };
@@ -62,27 +66,49 @@ class TestStepRun {
62
66
  testStepFinished
63
67
  };
64
68
  }
65
- buildException(error) {
66
- return {
67
- type: 'Error',
68
- message: error.message ? (0, _stripAnsiEscapes.stripAnsiEscapes)(error.message) : undefined,
69
- stackTrace: error.stack ? (0, _stripAnsiEscapes.stripAnsiEscapes)(error.stack) : undefined
70
- // Use type casting b/c older versions of @cucumber/messages don't have 'stackTrace' field
71
- // todo: add direct dependency on @cucumber/messages
72
- };
73
- }
74
69
  getStatus(error) {
75
70
  switch (true) {
76
- case !this.wasExecuted():
77
- return messages.TestStepResultStatus.SKIPPED;
78
71
  case Boolean(error):
79
72
  return messages.TestStepResultStatus.FAILED;
73
+ // For hooks that were not executted we return PASSED, not SKIPPED.
74
+ // Because these hooks can be from another run attempt of this testCase.
75
+ // If marked as skipped, the whole run is marked as skipped in reporter.
76
+ case !this.isHook() && !this.wasExecuted():
77
+ return messages.TestStepResultStatus.SKIPPED;
80
78
  default:
81
79
  return messages.TestStepResultStatus.PASSED;
82
80
  }
83
81
  }
82
+ // eslint-disable-next-line complexity
83
+ getStepError() {
84
+ if (!this.pwStep) {
85
+ return;
86
+ }
87
+ if (this.testCaseRun.errorSteps.has(this.pwStep)) {
88
+ return this.pwStep.error;
89
+ }
90
+ if (this.testCaseRun.isTimeouted() && this.pwStep === this.testCaseRun.timeoutedStep) {
91
+ var _this$testCaseRun$res;
92
+ return {
93
+ message: (_this$testCaseRun$res = this.testCaseRun.result.errors) === null || _this$testCaseRun$res === void 0 ? void 0 : _this$testCaseRun$res.map(e => e.message).join('\n\n')
94
+ };
95
+ }
96
+ }
84
97
  }
85
98
  exports.TestStepRun = TestStepRun;
86
- function formatError(error) {
99
+ function buildErrorMessage(error) {
87
100
  return (0, _stripAnsiEscapes.stripAnsiEscapes)([error.message, error.snippet].filter(Boolean).join('\n'));
101
+ }
102
+ function buildException(error) {
103
+ return {
104
+ type: 'Error',
105
+ message: buildErrorMessage(error),
106
+ // todo: extract only trace?
107
+ stackTrace: error.stack ? extractStackTrace((0, _stripAnsiEscapes.stripAnsiEscapes)(error.stack)) : undefined
108
+ // Use type casting b/c older versions of @cucumber/messages don't have 'stackTrace' field
109
+ // todo: add direct dependency on @cucumber/messages
110
+ };
111
+ }
112
+ function extractStackTrace(errorStack) {
113
+ return errorStack.split('\n').filter(line => line.match(/^\s+at .*/)).join('\n');
88
114
  }
@@ -5,37 +5,53 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.collectStepsDfs = collectStepsDfs;
7
7
  exports.collectStepsWithCategory = collectStepsWithCategory;
8
- exports.findDeepestErrorStep = findDeepestErrorStep;
9
- exports.getHooksRootStep = getHooksRootStep;
8
+ exports.findDeepestStepWithError = findDeepestStepWithError;
9
+ exports.findDeepestStepWithUnknownDuration = findDeepestStepWithUnknownDuration;
10
+ exports.getHooksRootPwStep = getHooksRootPwStep;
11
+ exports.isUnknownDuration = isUnknownDuration;
10
12
  /**
11
13
  * Utility functions for filtering Playwright steps.
12
14
  */
13
- // Playwright step categoires, that can be mapped to testStep / hook in Cucumber report
15
+ // Playwright step categoires, that can be mapped to testStep / hook in Cucumber messages
14
16
  const MEANINGFUL_STEP_CATEGORIES = ['hook', 'fixture', 'test.step'];
15
17
  function collectStepsWithCategory(parent, category) {
16
18
  const categories = Array.isArray(category) ? category : [category];
17
19
  const steps = collectStepsDfs(parent);
18
20
  return steps.filter(step => categories.includes(step.category));
19
21
  }
20
- function getHooksRootStep(result, type) {
22
+ function getHooksRootPwStep(result, type) {
21
23
  const rootStepTitle = type === 'before' ? 'Before Hooks' : 'After Hooks';
22
24
  return result.steps.find(step => step.category === 'hook' && step.title === rootStepTitle);
23
25
  }
26
+ function findDeepestStepWithError(root) {
27
+ if (!root) {
28
+ return;
29
+ }
30
+ return findDeepestStepWith(root, pwStep => {
31
+ return Boolean(pwStep.error) && MEANINGFUL_STEP_CATEGORIES.includes(pwStep.category);
32
+ });
33
+ }
34
+ function findDeepestStepWithUnknownDuration(root) {
35
+ if (!root) {
36
+ return;
37
+ }
38
+ return findDeepestStepWith(root, pwStep => {
39
+ return isUnknownDuration(pwStep) && MEANINGFUL_STEP_CATEGORIES.includes(pwStep.category);
40
+ });
41
+ }
24
42
  /**
25
- * Drills down to the deepest error step.
43
+ * Finds the deepest step that satisfies predicate function.
26
44
  */
27
- function findDeepestErrorStep(root) {
28
- let errorStep = root !== null && root !== void 0 && root.error ? root : null;
29
- while (errorStep) {
30
- const nextErrorStep = errorStep.steps.find(step => {
31
- return step.error && MEANINGFUL_STEP_CATEGORIES.includes(step.category);
32
- });
33
- if (!nextErrorStep) {
45
+ function findDeepestStepWith(root, predicate) {
46
+ let currentStep = predicate(root) ? root : undefined;
47
+ while (currentStep) {
48
+ const nextStep = currentStep.steps.find(pwStep => predicate(pwStep));
49
+ if (!nextStep) {
34
50
  break;
35
51
  }
36
- errorStep = nextErrorStep;
52
+ currentStep = nextStep;
37
53
  }
38
- return errorStep;
54
+ return currentStep;
39
55
  }
40
56
  /**
41
57
  * Returns all steps in DFS order.
@@ -48,4 +64,7 @@ function collectStepsDfs(parent) {
48
64
  res.push(...collectStepsDfs(step));
49
65
  return res;
50
66
  }, [])) || [];
67
+ }
68
+ function isUnknownDuration(pwStep) {
69
+ return pwStep.duration === -1;
51
70
  }
@@ -7,7 +7,7 @@ exports.StepInvoker = void 0;
7
7
  var _loadSteps = require("../cucumber/loadSteps");
8
8
  var _getLocationInFile = require("../playwright/getLocationInFile");
9
9
  var _testTypeImpl = require("../playwright/testTypeImpl");
10
- var _defineStep = require("../stepDefinitions/defineStep");
10
+ var _defineStep = require("../steps/defineStep");
11
11
  var _lang = require("../config/lang");
12
12
  /**
13
13
  * Class to invoke step in playwright runner.
@@ -25,17 +25,18 @@ class StepInvoker {
25
25
  * Invokes particular step.
26
26
  * See: https://github.com/cucumber/cucumber-js/blob/main/src/runtime/test_case_runner.ts#L299
27
27
  */
28
- async invoke(text, argument, stepFixtures) {
28
+ async invoke(stepText, argument, stepFixtures) {
29
+ var _this$world$$internal;
29
30
  this.world.$internal.currentStepFixtures = stepFixtures || {};
30
- const stepDefinition = this.getStepDefinition(text);
31
+ const stepDefinition = this.getStepDefinition(stepText);
31
32
  // Get location of step call in generated test file.
32
33
  // This call must be exactly here to have correct call stack (before async calls)
33
34
  const location = (0, _getLocationInFile.getLocationInFile)(this.world.testInfo.file);
34
- const stepTitle = this.getStepTitle(text);
35
+ const stepTitle = this.getStepTitle(stepText);
35
36
  const code = (0, _defineStep.getStepCode)(stepDefinition);
36
- const parameters = await this.getStepParameters(stepDefinition, text, argument || undefined);
37
- this.world.$internal.bddData.registerStep(stepDefinition, text, location);
38
- return (0, _testTypeImpl.runStepWithCustomLocation)(this.world.test, stepTitle, location, () => code.apply(this.world, parameters));
37
+ const parameters = await this.getStepParameters(stepDefinition, stepText, argument || undefined);
38
+ (_this$world$$internal = this.world.$internal.bddDataManager) === null || _this$world$$internal === void 0 || _this$world$$internal.registerStep(stepDefinition, stepText, location);
39
+ await (0, _testTypeImpl.runStepWithCustomLocation)(this.world.test, stepTitle, location, () => code.apply(this.world, parameters));
39
40
  }
40
41
  getStepDefinition(text) {
41
42
  const stepDefinition = (0, _loadSteps.findStepDefinition)(this.world.options.supportCodeLibrary, text, this.world.testInfo.file);