@serenity-js/webdriverio 2.32.7 → 3.0.0-rc.10

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 (243) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/lib/adapter/WebdriverIOFrameworkAdapter.js +1 -1
  3. package/lib/adapter/WebdriverIOFrameworkAdapter.js.map +1 -1
  4. package/lib/adapter/WebdriverIONotifier.d.ts +35 -1
  5. package/lib/adapter/WebdriverIONotifier.js +174 -13
  6. package/lib/adapter/WebdriverIONotifier.js.map +1 -1
  7. package/lib/index.d.ts +0 -3
  8. package/lib/index.js +0 -3
  9. package/lib/index.js.map +1 -1
  10. package/lib/screenplay/abilities/{BrowseTheWeb.d.ts → BrowseTheWebWithWebdriverIO.d.ts} +44 -27
  11. package/lib/screenplay/abilities/{BrowseTheWeb.js → BrowseTheWebWithWebdriverIO.js} +112 -32
  12. package/lib/screenplay/abilities/BrowseTheWebWithWebdriverIO.js.map +1 -0
  13. package/lib/screenplay/abilities/index.d.ts +1 -1
  14. package/lib/screenplay/abilities/index.js +1 -1
  15. package/lib/screenplay/abilities/index.js.map +1 -1
  16. package/lib/screenplay/index.d.ts +1 -2
  17. package/lib/screenplay/index.js +1 -2
  18. package/lib/screenplay/index.js.map +1 -1
  19. package/lib/screenplay/models/WebdriverIOCookie.d.ts +8 -0
  20. package/lib/screenplay/models/WebdriverIOCookie.js +39 -0
  21. package/lib/screenplay/models/WebdriverIOCookie.js.map +1 -0
  22. package/lib/screenplay/models/WebdriverIOModalDialog.d.ts +11 -0
  23. package/lib/screenplay/models/WebdriverIOModalDialog.js +40 -0
  24. package/lib/screenplay/models/WebdriverIOModalDialog.js.map +1 -0
  25. package/lib/screenplay/models/WebdriverIOPage.d.ts +24 -0
  26. package/lib/screenplay/models/WebdriverIOPage.js +98 -0
  27. package/lib/screenplay/models/WebdriverIOPage.js.map +1 -0
  28. package/lib/screenplay/models/WebdriverIOPageElement.d.ts +31 -0
  29. package/lib/screenplay/models/WebdriverIOPageElement.js +185 -0
  30. package/lib/screenplay/models/WebdriverIOPageElement.js.map +1 -0
  31. package/lib/screenplay/models/index.d.ts +4 -0
  32. package/lib/{stage → screenplay/models}/index.js +4 -1
  33. package/lib/screenplay/models/index.js.map +1 -0
  34. package/lib/screenplay/models/locators/WebdriverIOLocator.d.ts +9 -0
  35. package/lib/screenplay/models/locators/WebdriverIOLocator.js +22 -0
  36. package/lib/screenplay/models/locators/WebdriverIOLocator.js.map +1 -0
  37. package/lib/screenplay/models/locators/WebdriverIONativeElementRoot.d.ts +2 -0
  38. package/lib/screenplay/{interactions/EnterBuilder.js → models/locators/WebdriverIONativeElementRoot.js} +1 -1
  39. package/lib/screenplay/models/locators/WebdriverIONativeElementRoot.js.map +1 -0
  40. package/lib/screenplay/models/locators/index.d.ts +2 -0
  41. package/lib/{stage/crew/photographer → screenplay/models/locators}/index.js +2 -2
  42. package/lib/screenplay/models/locators/index.js.map +1 -0
  43. package/package.json +15 -25
  44. package/src/adapter/WebdriverIOFrameworkAdapter.ts +2 -0
  45. package/src/adapter/WebdriverIONotifier.ts +225 -23
  46. package/src/index.ts +0 -3
  47. package/src/screenplay/abilities/{BrowseTheWeb.ts → BrowseTheWebWithWebdriverIO.ts} +133 -35
  48. package/src/screenplay/abilities/index.ts +1 -1
  49. package/src/screenplay/index.ts +1 -2
  50. package/src/screenplay/models/WebdriverIOCookie.ts +44 -0
  51. package/src/screenplay/models/WebdriverIOModalDialog.ts +45 -0
  52. package/src/screenplay/models/WebdriverIOPage.ts +120 -0
  53. package/src/screenplay/models/WebdriverIOPageElement.ts +227 -0
  54. package/src/screenplay/models/index.ts +4 -0
  55. package/src/screenplay/models/locators/WebdriverIOLocator.ts +43 -0
  56. package/src/screenplay/models/locators/WebdriverIONativeElementRoot.ts +3 -0
  57. package/src/screenplay/models/locators/index.ts +2 -0
  58. package/lib/expectations/ElementExpectation.d.ts +0 -11
  59. package/lib/expectations/ElementExpectation.js +0 -27
  60. package/lib/expectations/ElementExpectation.js.map +0 -1
  61. package/lib/expectations/index.d.ts +0 -6
  62. package/lib/expectations/index.js +0 -19
  63. package/lib/expectations/index.js.map +0 -1
  64. package/lib/expectations/isActive.d.ts +0 -15
  65. package/lib/expectations/isActive.js +0 -21
  66. package/lib/expectations/isActive.js.map +0 -1
  67. package/lib/expectations/isClickable.d.ts +0 -20
  68. package/lib/expectations/isClickable.js +0 -26
  69. package/lib/expectations/isClickable.js.map +0 -1
  70. package/lib/expectations/isEnabled.d.ts +0 -14
  71. package/lib/expectations/isEnabled.js +0 -20
  72. package/lib/expectations/isEnabled.js.map +0 -1
  73. package/lib/expectations/isPresent.d.ts +0 -15
  74. package/lib/expectations/isPresent.js +0 -21
  75. package/lib/expectations/isPresent.js.map +0 -1
  76. package/lib/expectations/isSelected.d.ts +0 -14
  77. package/lib/expectations/isSelected.js +0 -20
  78. package/lib/expectations/isSelected.js.map +0 -1
  79. package/lib/expectations/isVisible.d.ts +0 -14
  80. package/lib/expectations/isVisible.js +0 -20
  81. package/lib/expectations/isVisible.js.map +0 -1
  82. package/lib/input/Key.d.ts +0 -73
  83. package/lib/input/Key.js +0 -84
  84. package/lib/input/Key.js.map +0 -1
  85. package/lib/input/index.d.ts +0 -1
  86. package/lib/input/index.js +0 -14
  87. package/lib/input/index.js.map +0 -1
  88. package/lib/screenplay/abilities/BrowseTheWeb.js.map +0 -1
  89. package/lib/screenplay/interactions/Clear.d.ts +0 -79
  90. package/lib/screenplay/interactions/Clear.js +0 -97
  91. package/lib/screenplay/interactions/Clear.js.map +0 -1
  92. package/lib/screenplay/interactions/Click.d.ts +0 -73
  93. package/lib/screenplay/interactions/Click.js +0 -84
  94. package/lib/screenplay/interactions/Click.js.map +0 -1
  95. package/lib/screenplay/interactions/DoubleClick.d.ts +0 -90
  96. package/lib/screenplay/interactions/DoubleClick.js +0 -101
  97. package/lib/screenplay/interactions/DoubleClick.js.map +0 -1
  98. package/lib/screenplay/interactions/Enter.d.ts +0 -73
  99. package/lib/screenplay/interactions/Enter.js +0 -87
  100. package/lib/screenplay/interactions/Enter.js.map +0 -1
  101. package/lib/screenplay/interactions/EnterBuilder.d.ts +0 -25
  102. package/lib/screenplay/interactions/EnterBuilder.js.map +0 -1
  103. package/lib/screenplay/interactions/ExecuteScript.d.ts +0 -206
  104. package/lib/screenplay/interactions/ExecuteScript.js +0 -311
  105. package/lib/screenplay/interactions/ExecuteScript.js.map +0 -1
  106. package/lib/screenplay/interactions/Hover.d.ts +0 -78
  107. package/lib/screenplay/interactions/Hover.js +0 -89
  108. package/lib/screenplay/interactions/Hover.js.map +0 -1
  109. package/lib/screenplay/interactions/Navigate.d.ts +0 -141
  110. package/lib/screenplay/interactions/Navigate.js +0 -197
  111. package/lib/screenplay/interactions/Navigate.js.map +0 -1
  112. package/lib/screenplay/interactions/Press.d.ts +0 -84
  113. package/lib/screenplay/interactions/Press.js +0 -152
  114. package/lib/screenplay/interactions/Press.js.map +0 -1
  115. package/lib/screenplay/interactions/PressBuilder.d.ts +0 -26
  116. package/lib/screenplay/interactions/PressBuilder.js +0 -3
  117. package/lib/screenplay/interactions/PressBuilder.js.map +0 -1
  118. package/lib/screenplay/interactions/RightClick.d.ts +0 -89
  119. package/lib/screenplay/interactions/RightClick.js +0 -100
  120. package/lib/screenplay/interactions/RightClick.js.map +0 -1
  121. package/lib/screenplay/interactions/Scroll.d.ts +0 -75
  122. package/lib/screenplay/interactions/Scroll.js +0 -86
  123. package/lib/screenplay/interactions/Scroll.js.map +0 -1
  124. package/lib/screenplay/interactions/Wait.d.ts +0 -143
  125. package/lib/screenplay/interactions/Wait.js +0 -247
  126. package/lib/screenplay/interactions/Wait.js.map +0 -1
  127. package/lib/screenplay/interactions/WaitBuilder.d.ts +0 -32
  128. package/lib/screenplay/interactions/WaitBuilder.js +0 -3
  129. package/lib/screenplay/interactions/WaitBuilder.js.map +0 -1
  130. package/lib/screenplay/interactions/WebElementInteraction.d.ts +0 -37
  131. package/lib/screenplay/interactions/WebElementInteraction.js +0 -52
  132. package/lib/screenplay/interactions/WebElementInteraction.js.map +0 -1
  133. package/lib/screenplay/interactions/index.d.ts +0 -13
  134. package/lib/screenplay/interactions/index.js +0 -26
  135. package/lib/screenplay/interactions/index.js.map +0 -1
  136. package/lib/screenplay/questions/Attribute.d.ts +0 -82
  137. package/lib/screenplay/questions/Attribute.js +0 -102
  138. package/lib/screenplay/questions/Attribute.js.map +0 -1
  139. package/lib/screenplay/questions/CSSClasses.d.ts +0 -92
  140. package/lib/screenplay/questions/CSSClasses.js +0 -112
  141. package/lib/screenplay/questions/CSSClasses.js.map +0 -1
  142. package/lib/screenplay/questions/LastScriptExecution.d.ts +0 -14
  143. package/lib/screenplay/questions/LastScriptExecution.js +0 -22
  144. package/lib/screenplay/questions/LastScriptExecution.js.map +0 -1
  145. package/lib/screenplay/questions/NestedTargetBuilder.d.ts +0 -27
  146. package/lib/screenplay/questions/NestedTargetBuilder.js +0 -3
  147. package/lib/screenplay/questions/NestedTargetBuilder.js.map +0 -1
  148. package/lib/screenplay/questions/TargetBuilder.d.ts +0 -25
  149. package/lib/screenplay/questions/TargetBuilder.js +0 -3
  150. package/lib/screenplay/questions/TargetBuilder.js.map +0 -1
  151. package/lib/screenplay/questions/Text.d.ts +0 -95
  152. package/lib/screenplay/questions/Text.js +0 -130
  153. package/lib/screenplay/questions/Text.js.map +0 -1
  154. package/lib/screenplay/questions/Value.d.ts +0 -63
  155. package/lib/screenplay/questions/Value.js +0 -78
  156. package/lib/screenplay/questions/Value.js.map +0 -1
  157. package/lib/screenplay/questions/Website.d.ts +0 -21
  158. package/lib/screenplay/questions/Website.js +0 -31
  159. package/lib/screenplay/questions/Website.js.map +0 -1
  160. package/lib/screenplay/questions/index.d.ts +0 -10
  161. package/lib/screenplay/questions/index.js +0 -23
  162. package/lib/screenplay/questions/index.js.map +0 -1
  163. package/lib/screenplay/questions/lists.d.ts +0 -86
  164. package/lib/screenplay/questions/lists.js +0 -137
  165. package/lib/screenplay/questions/lists.js.map +0 -1
  166. package/lib/screenplay/questions/locators.d.ts +0 -196
  167. package/lib/screenplay/questions/locators.js +0 -219
  168. package/lib/screenplay/questions/locators.js.map +0 -1
  169. package/lib/screenplay/questions/targets.d.ts +0 -254
  170. package/lib/screenplay/questions/targets.js +0 -334
  171. package/lib/screenplay/questions/targets.js.map +0 -1
  172. package/lib/stage/crew/index.d.ts +0 -1
  173. package/lib/stage/crew/index.js +0 -14
  174. package/lib/stage/crew/index.js.map +0 -1
  175. package/lib/stage/crew/photographer/Photographer.d.ts +0 -83
  176. package/lib/stage/crew/photographer/Photographer.js +0 -102
  177. package/lib/stage/crew/photographer/Photographer.js.map +0 -1
  178. package/lib/stage/crew/photographer/index.d.ts +0 -2
  179. package/lib/stage/crew/photographer/index.js.map +0 -1
  180. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.d.ts +0 -28
  181. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js +0 -65
  182. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js.map +0 -1
  183. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.d.ts +0 -18
  184. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js +0 -30
  185. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js.map +0 -1
  186. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.d.ts +0 -17
  187. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js +0 -28
  188. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js.map +0 -1
  189. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.d.ts +0 -19
  190. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js +0 -28
  191. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js.map +0 -1
  192. package/lib/stage/crew/photographer/strategies/index.d.ts +0 -4
  193. package/lib/stage/crew/photographer/strategies/index.js +0 -17
  194. package/lib/stage/crew/photographer/strategies/index.js.map +0 -1
  195. package/lib/stage/index.d.ts +0 -1
  196. package/lib/stage/index.js.map +0 -1
  197. package/src/expectations/ElementExpectation.ts +0 -31
  198. package/src/expectations/index.ts +0 -6
  199. package/src/expectations/isActive.ts +0 -21
  200. package/src/expectations/isClickable.ts +0 -26
  201. package/src/expectations/isEnabled.ts +0 -19
  202. package/src/expectations/isPresent.ts +0 -20
  203. package/src/expectations/isSelected.ts +0 -19
  204. package/src/expectations/isVisible.ts +0 -19
  205. package/src/input/Key.ts +0 -83
  206. package/src/input/index.ts +0 -1
  207. package/src/screenplay/interactions/Clear.ts +0 -102
  208. package/src/screenplay/interactions/Click.ts +0 -85
  209. package/src/screenplay/interactions/DoubleClick.ts +0 -102
  210. package/src/screenplay/interactions/Enter.ts +0 -93
  211. package/src/screenplay/interactions/EnterBuilder.ts +0 -27
  212. package/src/screenplay/interactions/ExecuteScript.ts +0 -344
  213. package/src/screenplay/interactions/Hover.ts +0 -90
  214. package/src/screenplay/interactions/Navigate.ts +0 -208
  215. package/src/screenplay/interactions/Press.ts +0 -172
  216. package/src/screenplay/interactions/PressBuilder.ts +0 -28
  217. package/src/screenplay/interactions/RightClick.ts +0 -100
  218. package/src/screenplay/interactions/Scroll.ts +0 -87
  219. package/src/screenplay/interactions/Wait.ts +0 -267
  220. package/src/screenplay/interactions/WaitBuilder.ts +0 -34
  221. package/src/screenplay/interactions/WebElementInteraction.ts +0 -56
  222. package/src/screenplay/interactions/index.ts +0 -13
  223. package/src/screenplay/questions/Attribute.ts +0 -112
  224. package/src/screenplay/questions/CSSClasses.ts +0 -116
  225. package/src/screenplay/questions/LastScriptExecution.ts +0 -21
  226. package/src/screenplay/questions/NestedTargetBuilder.ts +0 -30
  227. package/src/screenplay/questions/TargetBuilder.ts +0 -27
  228. package/src/screenplay/questions/Text.ts +0 -140
  229. package/src/screenplay/questions/Value.ts +0 -82
  230. package/src/screenplay/questions/Website.ts +0 -34
  231. package/src/screenplay/questions/index.ts +0 -10
  232. package/src/screenplay/questions/lists.ts +0 -161
  233. package/src/screenplay/questions/locators.ts +0 -254
  234. package/src/screenplay/questions/targets.ts +0 -401
  235. package/src/stage/crew/index.ts +0 -1
  236. package/src/stage/crew/photographer/Photographer.ts +0 -108
  237. package/src/stage/crew/photographer/index.ts +0 -2
  238. package/src/stage/crew/photographer/strategies/PhotoTakingStrategy.ts +0 -103
  239. package/src/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.ts +0 -28
  240. package/src/stage/crew/photographer/strategies/TakePhotosOfFailures.ts +0 -26
  241. package/src/stage/crew/photographer/strategies/TakePhotosOfInteractions.ts +0 -26
  242. package/src/stage/crew/photographer/strategies/index.ts +0 -4
  243. package/src/stage/index.ts +0 -1
@@ -1,7 +1,8 @@
1
1
  import { LogicError, Stage, StageCrewMember } from '@serenity-js/core';
2
- import { DomainEvent, SceneFinished, SceneStarts, TestSuiteFinished, TestSuiteStarts } from '@serenity-js/core/lib/events';
2
+ import { AsyncOperationAttempted, AsyncOperationCompleted, DomainEvent, SceneFinished, SceneStarts, TestRunFinishes, TestSuiteFinished, TestSuiteStarts } from '@serenity-js/core/lib/events';
3
3
  import {
4
4
  CorrelationId,
5
+ Description,
5
6
  ExecutionCompromised,
6
7
  ExecutionFailedWithAssertionError,
7
8
  ExecutionFailedWithError,
@@ -12,20 +13,35 @@ import {
12
13
  ProblemIndication,
13
14
  TestSuiteDetails,
14
15
  } from '@serenity-js/core/lib/model';
15
- import { Suite } from '@wdio/reporter/build/stats/suite';
16
- import { Test } from '@wdio/reporter/build/stats/test';
16
+ import { Suite as suiteStats } from '@wdio/reporter/build/stats/suite';
17
+ import { Test as testStats } from '@wdio/reporter/build/stats/test';
18
+ import { RemoteCapability } from '@wdio/types/build/Capabilities';
19
+ import * as frameworks from '@wdio/types/build/Frameworks';
17
20
  import type { EventEmitter } from 'events';
18
21
  import { match } from 'tiny-types';
19
22
 
23
+ import { WebdriverIOConfig } from './WebdriverIOConfig';
24
+
20
25
  /**
21
26
  * @package
22
27
  */
23
28
  export class WebdriverIONotifier implements StageCrewMember {
24
29
 
30
+ /**
31
+ * We don't have access to the "context" object produced by Mocha,
32
+ * and can't assume that other test runners have a similar concept.
33
+ * Since, at the time of writing, none of the WebdriverIO rely on this parameter
34
+ * using a dummy seems to be sufficient.
35
+ * @private
36
+ */
37
+ private static dummmyContext = {};
38
+
25
39
  private readonly events = new EventLog();
26
40
  private readonly suites: TestSuiteDetails[] = [];
27
41
 
28
42
  constructor(
43
+ private readonly config: WebdriverIOConfig,
44
+ private readonly capabilities: RemoteCapability,
29
45
  private readonly reporter: EventEmitter,
30
46
  private readonly successThreshold: Outcome | { Code: number },
31
47
  private readonly cid: string,
@@ -46,28 +62,42 @@ export class WebdriverIONotifier implements StageCrewMember {
46
62
  .when(TestSuiteFinished, WebdriverIONotifier.prototype.onTestSuiteFinished.bind(this))
47
63
  .when(SceneStarts, WebdriverIONotifier.prototype.onSceneStarts.bind(this))
48
64
  .when(SceneFinished, WebdriverIONotifier.prototype.onSceneFinished.bind(this))
65
+ .when(TestRunFinishes, WebdriverIONotifier.prototype.onTestRunFinishes.bind(this))
49
66
  .else(() => void 0);
50
67
  }
51
68
 
69
+ private onTestRunFinishes(): Promise<any> {
70
+ return this.invokeHooks('after', this.config.after, [this.failures, this.capabilities, this.specs]);
71
+ }
72
+
52
73
  failureCount(): number {
53
74
  return this.failures;
54
75
  }
55
76
 
56
- private onTestSuiteStarts(started: TestSuiteStarts) {
77
+ private onTestSuiteStarts(started: TestSuiteStarts): Promise<any> {
57
78
  this.events.record(started.details.correlationId, started);
58
- this.reporter.emit('suite:start', this.suiteStartEventFrom(started));
79
+
80
+ const suite = this.suiteStartEventFrom(started);
81
+
82
+ this.reporter.emit('suite:start', suite);
59
83
 
60
84
  this.suites.push(started.details);
85
+
86
+ return this.invokeHooks('beforeSuite', this.config.beforeSuite, [suite as any]); // todo: correct types
61
87
  }
62
88
 
63
- private onTestSuiteFinished(finished: TestSuiteFinished) {
89
+ private onTestSuiteFinished(finished: TestSuiteFinished): Promise<any> {
64
90
  this.suites.pop();
65
91
 
66
92
  const started = this.events.getByCorrelationId<TestSuiteStarts>(finished.details.correlationId);
67
- this.reporter.emit('suite:end', this.suiteEndEventFrom(started, finished));
93
+ const suite = this.suiteEndEventFrom(started, finished);
94
+
95
+ this.reporter.emit('suite:end', suite);
96
+
97
+ return this.invokeHooks('afterSuite', this.config.afterSuite, [suite as any]); // todo: correct types
68
98
  }
69
99
 
70
- private suiteStartEventFrom(started: TestSuiteStarts): Suite {
100
+ private suiteStartEventFrom(started: TestSuiteStarts): suiteStats & frameworks.Suite {
71
101
  return {
72
102
  type: 'suite:start',
73
103
  uid: started.details.correlationId.value,
@@ -85,7 +115,7 @@ export class WebdriverIONotifier implements StageCrewMember {
85
115
  return this.suites.map(suite => suite.name.value).concat(name).join(' ');
86
116
  }
87
117
 
88
- private suiteEndEventFrom(started: TestSuiteStarts, finished: TestSuiteFinished): Suite {
118
+ private suiteEndEventFrom(started: TestSuiteStarts, finished: TestSuiteFinished): suiteStats & frameworks.Suite {
89
119
  return {
90
120
  ...this.suiteStartEventFrom(started),
91
121
  type: 'suite:end',
@@ -99,6 +129,8 @@ export class WebdriverIONotifier implements StageCrewMember {
99
129
  this.events.record(started.sceneId, started);
100
130
 
101
131
  this.reporter.emit(test.type, test);
132
+
133
+ return this.invokeHooks('beforeTest', this.config.beforeTest, [ this.testFrom(started), WebdriverIONotifier.dummmyContext ]);
102
134
  }
103
135
 
104
136
  private onSceneFinished(finished: SceneFinished) {
@@ -108,37 +140,59 @@ export class WebdriverIONotifier implements StageCrewMember {
108
140
  }
109
141
 
110
142
  const started = this.events.getByCorrelationId<SceneStarts>(finished.sceneId);
143
+ let testEnd: testStats;
111
144
 
112
145
  if (this.willBeRetried(finished.outcome)) {
113
- const testResult = this.testEndEventFrom(started, finished);
146
+ testEnd = this.testEndEventFrom(started, finished);
114
147
 
115
148
  const type = 'test:retry';
116
149
 
117
150
  this.reporter.emit(type, {
118
- ...testResult,
151
+ ...testEnd,
119
152
  type,
120
153
  error: this.errorFrom(finished.outcome),
121
154
  });
122
155
 
123
156
  } else {
124
157
 
125
- const testResult = this.testResultEventFrom(started, finished);
126
- this.reporter.emit(testResult.type, testResult);
158
+ const testResultEvent = this.testResultEventFrom(started, finished);
159
+ this.reporter.emit(testResultEvent.type, testResultEvent);
127
160
 
128
- const testEnd = this.testEndEventFrom(started, finished);
161
+ testEnd = this.testEndEventFrom(started, finished);
129
162
  this.reporter.emit(testEnd.type, testEnd);
130
163
  }
164
+
165
+ return this.invokeHooks('afterTest', this.config.afterTest, [ this.testFrom(started), WebdriverIONotifier.dummmyContext, this.testResultFrom(started, finished) ]);
131
166
  }
132
167
 
133
168
  private willBeRetried(outcome: Outcome): outcome is ExecutionIgnored {
134
169
  return outcome instanceof ExecutionIgnored;
135
170
  }
136
171
 
137
- private testStartEventFrom(started: SceneStarts): Test {
138
- const title = started.details.name.value
172
+ private testShortTitleFrom(started: SceneStarts): string {
173
+ return started.details.name.value
139
174
  .replace(new RegExp(`^.*?(${ this.parentSuiteName() })`), '')
140
175
  .trim();
176
+ }
177
+
178
+ private testFrom(started: SceneStarts): frameworks.Test {
179
+ const
180
+ title = this.testShortTitleFrom(started);
141
181
 
182
+ return {
183
+ ctx: WebdriverIONotifier.dummmyContext,
184
+ file: started.details.location.path.value,
185
+ fullName: this.suiteNamesConcatenatedWith(title),
186
+ fullTitle: this.suiteNamesConcatenatedWith(title),
187
+ parent: this.parentSuiteName(),
188
+ pending: false,
189
+ title,
190
+ type: 'test' // I _think_ it's either 'test' or 'hook' - https://github.com/mochajs/mocha/blob/0ea732c1169c678ef116c3eb452cc94758fff150/lib/test.js#L31
191
+ }
192
+ }
193
+
194
+ private testStartEventFrom(started: SceneStarts): testStats {
195
+ const title = this.testShortTitleFrom(started);
142
196
  return {
143
197
  type: 'test:start',
144
198
  title,
@@ -156,7 +210,95 @@ export class WebdriverIONotifier implements StageCrewMember {
156
210
  return this.suites[this.suites.length - 1]?.name.value || '';
157
211
  }
158
212
 
159
- private testEndEventFrom(started: SceneStarts, finished: SceneFinished): Test {
213
+ /**
214
+ * test status is 'passed' | 'pending' | 'skipped' | 'failed' | 'broken' | 'canceled'
215
+ * Since this is not documented, we're mimicking other WebdriverIO reporters, for example:
216
+ * https://github.com/webdriverio/webdriverio/blob/7415f3126e15a733b51721492e4995ceafae6046/packages/wdio-allure-reporter/src/constants.ts#L3-L9
217
+ *
218
+ * @param started
219
+ * @param finished
220
+ * @private
221
+ */
222
+ private testResultFrom(started: SceneStarts, finished: SceneFinished): frameworks.TestResult {
223
+ const duration = finished.timestamp.diff(started.timestamp).inMilliseconds();
224
+ const defaultRetries = { attempts: 0, limit: 0 };
225
+
226
+ const passedOrFailed = (outcome: Outcome): boolean =>
227
+ this.whenSuccessful<boolean>(outcome, true, false);
228
+
229
+ return match<Outcome, frameworks.TestResult>(finished.outcome)
230
+ .when(ExecutionCompromised, (outcome: ExecutionCompromised) => {
231
+ const error = this.errorFrom(outcome);
232
+ return {
233
+ duration,
234
+ error,
235
+ exception: error.message,
236
+ passed: passedOrFailed(outcome),
237
+ status: 'broken',
238
+ retries: defaultRetries
239
+ }
240
+ })
241
+ .when(ExecutionFailedWithError, (outcome: ExecutionFailedWithError) => {
242
+ const error = this.errorFrom(outcome);
243
+ return {
244
+ duration,
245
+ error,
246
+ exception: error.message,
247
+ passed: passedOrFailed(outcome),
248
+ status: 'broken',
249
+ retries: defaultRetries
250
+ }
251
+ })
252
+ .when(ExecutionFailedWithAssertionError, (outcome: ExecutionFailedWithAssertionError) => {
253
+ const error = this.errorFrom(outcome);
254
+ return {
255
+ duration,
256
+ error,
257
+ exception: error.message,
258
+ passed: passedOrFailed(outcome),
259
+ status: 'failed',
260
+ retries: defaultRetries
261
+ }
262
+ })
263
+ .when(ImplementationPending, (outcome: ImplementationPending) => {
264
+ const error = this.errorFrom(outcome);
265
+ return {
266
+ duration,
267
+ error,
268
+ exception: error.message,
269
+ passed: passedOrFailed(outcome),
270
+ status: 'pending',
271
+ retries: defaultRetries
272
+ }
273
+ })
274
+ .when(ExecutionIgnored, (outcome: ExecutionIgnored) => {
275
+ const error = this.errorFrom(outcome);
276
+ return {
277
+ duration,
278
+ error,
279
+ exception: error.message,
280
+ passed: passedOrFailed(outcome),
281
+ status: 'canceled', // fixme: mark as canceled for now for the lack of a better alternative;
282
+ retries: defaultRetries // consider capturing info about retries from Mocha and putting it on the ExecutionIgnored event so we can pass it on
283
+ }
284
+ })
285
+ .when(ExecutionSkipped, (outcome: ExecutionSkipped) => ({
286
+ duration,
287
+ exception: '',
288
+ passed: passedOrFailed(outcome),
289
+ status: 'skipped',
290
+ retries: defaultRetries
291
+ }))
292
+ .else(() => ({
293
+ duration,
294
+ exception: '',
295
+ passed: true,
296
+ status: 'passed',
297
+ retries: defaultRetries
298
+ }));
299
+ }
300
+
301
+ private testEndEventFrom(started: SceneStarts, finished: SceneFinished): testStats {
160
302
  const duration = finished.timestamp.diff(started.timestamp).inMilliseconds();
161
303
  return {
162
304
  ...this.testStartEventFrom(started),
@@ -165,15 +307,19 @@ export class WebdriverIONotifier implements StageCrewMember {
165
307
  };
166
308
  }
167
309
 
168
- private testResultEventFrom(started: SceneStarts, finished: SceneFinished): Test {
310
+ private whenSuccessful<O>(outcome: Outcome, resultWhenSuccessful: O, resultWhenNotSuccessful: O): O {
311
+ return ! outcome.isWorseThan(this.successThreshold) && (outcome instanceof ProblemIndication)
312
+ ? resultWhenSuccessful
313
+ : resultWhenNotSuccessful
314
+ }
315
+
316
+ private testResultEventFrom(started: SceneStarts, finished: SceneFinished): testStats {
169
317
  const test = this.testEndEventFrom(started, finished)
170
318
 
171
- const unlessSuccessful = (outcome: Outcome, type: Test['type']) =>
172
- ! outcome.isWorseThan(this.successThreshold) && (outcome instanceof ProblemIndication)
173
- ? 'test:pass'
174
- : type;
319
+ const unlessSuccessful = (outcome: Outcome, type: testStats['type']) =>
320
+ this.whenSuccessful<testStats['type']>(outcome, 'test:pass', type);
175
321
 
176
- return match<Outcome, Test>(finished.outcome)
322
+ return match<Outcome, testStats>(finished.outcome)
177
323
  .when(ExecutionCompromised, (outcome: ExecutionCompromised) => ({
178
324
  ...test,
179
325
  type: unlessSuccessful(outcome, 'test:fail'),
@@ -231,6 +377,62 @@ export class WebdriverIONotifier implements StageCrewMember {
231
377
  actual: error.actual
232
378
  }
233
379
  }
380
+
381
+ /**
382
+ * @see https://github.com/webdriverio/webdriverio/blob/main/packages/wdio-utils/src/shim.ts
383
+ * @param hookName
384
+ * @param hookFunctions
385
+ * @param args
386
+ * @private
387
+ */
388
+ private invokeHooks<Result, InnerArguments extends any[]>(
389
+ hookName: string,
390
+ hookFunctions: ((...parameters: InnerArguments) => Promise<Result> | Result) | Array<((...parameters: InnerArguments) => Result)>,
391
+ args: InnerArguments
392
+ ): Promise<Array<Result | Error>> {
393
+
394
+ const hooks = ! Array.isArray(hookFunctions)
395
+ ? [ hookFunctions ]
396
+ : hookFunctions;
397
+
398
+ const asyncOperationId = CorrelationId.create();
399
+
400
+ this.stage.announce(new AsyncOperationAttempted(
401
+ new Description(`[WebdriverIONotifier#invokeHooks] Invoking ${ hookName } hook`),
402
+ asyncOperationId,
403
+ ));
404
+
405
+ return Promise.all(hooks.map((hook) => new Promise<Result | Error>((resolve) => {
406
+ let result
407
+
408
+ try {
409
+ result = hook(...args);
410
+ } catch (error) {
411
+ return resolve(error);
412
+ }
413
+
414
+ /**
415
+ * if a promise is returned make sure we don't have a catch handler
416
+ * so in case of a rejection it won't cause the hook to fail
417
+ */
418
+ if (result && typeof result.then === 'function') {
419
+ return result.then(resolve, (error: Error) => {
420
+ resolve(error);
421
+ })
422
+ }
423
+
424
+ resolve(result);
425
+ }))).
426
+ then(results => {
427
+
428
+ this.stage.announce(new AsyncOperationCompleted(
429
+ new Description(`[WebdriverIONotifier#invokeHooks] Invoking ${ hookName } hook completed`),
430
+ asyncOperationId,
431
+ ));
432
+
433
+ return results;
434
+ });
435
+ }
234
436
  }
235
437
 
236
438
  class EventLog {
package/src/index.ts CHANGED
@@ -12,7 +12,4 @@ const adapterFactory = new WebdriverIOFrameworkAdapterFactory(
12
12
  export default adapterFactory;
13
13
 
14
14
  export { WebdriverIOConfig } from './adapter';
15
- export * from './expectations';
16
- export * from './input';
17
15
  export * from './screenplay';
18
- export * from './stage';
@@ -1,7 +1,9 @@
1
- import { Ability, LogicError, UsesAbilities } from '@serenity-js/core';
2
- import type { Browser } from 'webdriverio';
1
+ import { Duration, LogicError } from '@serenity-js/core';
2
+ import { BrowserCapabilities, BrowseTheWeb, ByCss, ByCssContainingText, ById, ByTagName, ByXPath, Cookie, CookieData, Key, ModalDialog, Page, PageElement } from '@serenity-js/web';
3
+ import type * as wdio from 'webdriverio';
3
4
 
4
- import { Key } from '../../input';
5
+ import { WebdriverIOCookie, WebdriverIOModalDialog, WebdriverIOPage, WebdriverIOPageElement } from '../models';
6
+ import { WebdriverIOLocator, WebdriverIONativeElementRoot } from '../models/locators';
5
7
 
6
8
  /**
7
9
  * @desc
@@ -12,8 +14,6 @@ import { Key } from '../../input';
12
14
  * This means that its interface can change without affecting the major version of Serenity/JS itself.
13
15
  * In particular, please don't rely on the `browser` field to remain `public` in future releases.
14
16
  *
15
- * @experimental
16
- *
17
17
  * @example <caption>Using the WebdriverIO browser</caption>
18
18
  * import { Actor } from '@serenity-js/core';
19
19
  * import { BrowseTheWeb, by, Navigate, Target } from '@serenity-js/webdriverio'
@@ -38,7 +38,15 @@ import { Key } from '../../input';
38
38
  * @implements {@serenity-js/core/lib/screenplay~Ability}
39
39
  * @see {@link @serenity-js/core/lib/screenplay/actor~Actor}
40
40
  */
41
- export class BrowseTheWeb implements Ability {
41
+ export class BrowseTheWebWithWebdriverIO extends BrowseTheWeb<wdio.Element<'async'>, WebdriverIONativeElementRoot> {
42
+
43
+ /**
44
+ * @param {@wdio/types~Browser} browserInstance
45
+ * @returns {BrowseTheWebWithWebdriverIO}
46
+ */
47
+ static using(browserInstance: wdio.Browser<'async'>): BrowseTheWebWithWebdriverIO {
48
+ return new BrowseTheWebWithWebdriverIO(browserInstance);
49
+ }
42
50
 
43
51
  /**
44
52
  * @private
@@ -46,42 +54,114 @@ export class BrowseTheWeb implements Ability {
46
54
  private lastScriptExecutionSummary: LastScriptExecutionSummary;
47
55
 
48
56
  /**
49
- * @param {@wdio/types~Browser} browserInstance
50
- * @returns {BrowseTheWeb}
57
+ * @param {@wdio/types~Browser} browser
51
58
  */
52
- static using(browserInstance: Browser<'async'>): BrowseTheWeb {
53
- return new BrowseTheWeb(browserInstance);
59
+ constructor(protected readonly browser: wdio.Browser<'async'>) {
60
+ super(new Map()
61
+ .set(ByCss, (selector: ByCss) => WebdriverIOLocator.createRootLocator(this.browser, selector, selector.value))
62
+ .set(ByCssContainingText, (selector: ByCssContainingText) => WebdriverIOLocator.createRootLocator(this.browser, selector, `${ selector.value }*=${ selector.text }`))
63
+ .set(ById, (selector: ById) => WebdriverIOLocator.createRootLocator(this.browser, selector, `#${ selector.value }`))
64
+ .set(ByTagName, (selector: ByTagName) => WebdriverIOLocator.createRootLocator(this.browser, selector, `<${ selector.value } />`))
65
+ .set(ByXPath, (selector: ByXPath) => WebdriverIOLocator.createRootLocator(this.browser, selector, selector.value))
66
+ );
67
+
68
+ if (! this.browser.$ || ! this.browser.$$) {
69
+ throw new LogicError(`WebdriverIO browser object is not initialised yet, so can't be assigned to an actor. Are you trying to instantiate an actor outside of a test or a test hook?`)
70
+ }
71
+ }
72
+
73
+ browserCapabilities(): Promise<BrowserCapabilities> {
74
+ return Promise.resolve(this.browser.capabilities as BrowserCapabilities);
75
+ }
76
+
77
+ async cookie(name: string): Promise<Cookie> {
78
+ return new WebdriverIOCookie(this.browser, name);
79
+ }
80
+
81
+ async setCookie(cookieData: CookieData): Promise<void> {
82
+ return this.browser.setCookies({
83
+ name: cookieData.name,
84
+ value: cookieData.value,
85
+ path: cookieData.path,
86
+ domain: cookieData.domain,
87
+ secure: cookieData.secure,
88
+ httpOnly: cookieData.httpOnly,
89
+ expiry: cookieData.expiry
90
+ ? cookieData.expiry.toSeconds()
91
+ : undefined,
92
+ sameSite: cookieData.sameSite,
93
+ });
94
+ }
95
+
96
+ deleteAllCookies(): Promise<void> {
97
+ return this.browser.deleteCookies() as Promise<void>;
54
98
  }
55
99
 
56
100
  /**
57
101
  * @desc
58
- * Used to access the Actor's ability to {@link BrowseTheWeb}
59
- * from within the {@link @serenity-js/core/lib/screenplay~Interaction} classes,
60
- * such as {@link Navigate}.
102
+ * Navigate to a given destination, specified as an absolute URL
103
+ * or a path relative to WebdriverIO `baseUrl`.
61
104
  *
62
- * @param {@serenity-js/core/lib/screenplay/actor~UsesAbilities} actor
63
- * @return {BrowseTheWeb}
105
+ * @param {string} destination
106
+ * @returns {Promise<void>}
64
107
  */
65
- static as(actor: UsesAbilities): BrowseTheWeb {
66
- return actor.abilityTo(BrowseTheWeb);
108
+ navigateTo(destination: string): Promise<void> {
109
+ return this.browser.url(destination) as any; // todo: check if this returns a string or is mistyped
110
+ }
111
+
112
+ navigateBack(): Promise<void> {
113
+ return this.browser.back();
114
+ }
115
+
116
+ navigateForward(): Promise<void> {
117
+ return this.browser.forward();
118
+ }
119
+
120
+ reloadPage(): Promise<void> {
121
+ return this.browser.refresh();
67
122
  }
68
123
 
69
124
  /**
70
- * @param {@wdio/types~Browser} browser
125
+ * @desc
126
+ * Returns a {@link Page} representing the currently active top-level browsing context.
127
+ *
128
+ * @returns {Promise<Page>}
71
129
  */
72
- constructor(public readonly browser: Browser<'async'>) {
130
+ async currentPage(): Promise<Page> {
131
+
132
+ const windowHandle = await this.browser.getWindowHandle();
133
+
134
+ return new WebdriverIOPage(this.browser, windowHandle);
73
135
  }
74
136
 
75
137
  /**
76
138
  * @desc
77
- * Navigate to a given destination, specified as an absolute URL
78
- * or a path relative to WebdriverIO `baseUrl`.
139
+ * Returns an array of {@link Page} objects representing all the available
140
+ * top-level browsing context, e.g. all the open browser tabs.
79
141
  *
80
- * @param {string} destination
81
- * @returns {Promise<void>}
142
+ * @returns {Promise<Array<Page>>}
82
143
  */
83
- get(destination: string): Promise<void> {
84
- return this.browser.url(destination) as any; // todo: check if this returns a string or is mistyped
144
+ async allPages(): Promise<Array<Page>> {
145
+ const windowHandles = await this.browser.getWindowHandles();
146
+
147
+ return windowHandles.map(windowHandle => new WebdriverIOPage(this.browser, windowHandle));
148
+ }
149
+
150
+ async modalDialog(): Promise<ModalDialog> {
151
+ return new WebdriverIOModalDialog(this.browser);
152
+ }
153
+
154
+ // todo: remove
155
+ switchToFrame(targetOrIndex: PageElement | number | string): Promise<void> {
156
+ throw new Error('Not implemented, yet');
157
+ }
158
+ // todo: remove
159
+ switchToParentFrame(): Promise<void> {
160
+ throw new Error('Not implemented, yet');
161
+ }
162
+ // todo: remove
163
+ switchToDefaultContent(): Promise<void> {
164
+ throw new Error('Not implemented, yet');
85
165
  }
86
166
 
87
167
  /**
@@ -101,7 +181,7 @@ export class BrowseTheWeb implements Ability {
101
181
  return key;
102
182
  }
103
183
 
104
- if (browser.isDevTools) {
184
+ if (this.browser.isDevTools) {
105
185
  return key.devtoolsName;
106
186
  }
107
187
 
@@ -162,12 +242,13 @@ export class BrowseTheWeb implements Ability {
162
242
  *
163
243
  * @see {@link BrowseTheWeb#getLastScriptExecutionResult}
164
244
  */
165
- executeScript<Result, InnerArguments extends any[]>(
245
+ async executeScript<Result, InnerArguments extends any[]>(
166
246
  script: string | ((...parameters: InnerArguments) => Result),
167
247
  ...args: InnerArguments
168
248
  ): Promise<Result> {
249
+ const nativeArguments = await Promise.all(args.map(arg => arg instanceof WebdriverIOPageElement ? arg.nativeElement() : arg)) as InnerArguments;
169
250
 
170
- return this.browser.execute(script, ...args)
251
+ return this.browser.execute(script, ...nativeArguments)
171
252
  .then(result => {
172
253
  this.lastScriptExecutionSummary = new LastScriptExecutionSummary(
173
254
  result,
@@ -188,7 +269,7 @@ export class BrowseTheWeb implements Ability {
188
269
  * Arrays and objects may also be used as script arguments as long as each item adheres
189
270
  * to the types previously mentioned.
190
271
  *
191
- * Unlike executing synchronous JavaScript with {@link BrowseTheWeb#executeScript},
272
+ * Unlike executing synchronous JavaScript with {@link BrowseTheWebWithWebdriverIO#executeScript},
192
273
  * scripts executed with this function must explicitly signal they are finished by invoking the provided callback.
193
274
  *
194
275
  * This callback will always be injected into the executed function as the last argument,
@@ -227,11 +308,13 @@ export class BrowseTheWeb implements Ability {
227
308
  *
228
309
  * @see {@link BrowseTheWeb#getLastScriptExecutionResult}
229
310
  */
230
- executeAsyncScript<Result, Parameters extends any[]>(
311
+ async executeAsyncScript<Result, Parameters extends any[]>(
231
312
  script: string | ((...args: [...parameters: Parameters, callback: (result: Result) => void]) => void),
232
313
  ...args: Parameters
233
314
  ): Promise<Result> {
234
- return this.browser.executeAsync<Result, Parameters>(script, ...args)
315
+ const nativeArguments = await Promise.all(args.map(arg => arg instanceof WebdriverIOPageElement ? arg.nativeElement() : arg)) as Parameters;
316
+
317
+ return this.browser.executeAsync<Result, Parameters>(script, ...nativeArguments)
235
318
  .then(result => {
236
319
  this.lastScriptExecutionSummary = new LastScriptExecutionSummary<Result>(
237
320
  result,
@@ -242,17 +325,32 @@ export class BrowseTheWeb implements Ability {
242
325
 
243
326
  /**
244
327
  * @desc
245
- * Returns the last result of calling {@link BrowseTheWeb#executeAsyncScript}
246
- * or {@link BrowseTheWeb#executeScript}
328
+ * Returns the last result of calling {@link BrowseTheWebWithWebdriverIO#executeAsyncScript}
329
+ * or {@link BrowseTheWebWithWebdriverIO#executeScript}
247
330
  *
248
331
  * @returns {any}
249
332
  */
250
- getLastScriptExecutionResult<Result = any>(): Result {
333
+ lastScriptExecutionResult<Result = any>(): Result {
251
334
  if (! this.lastScriptExecutionSummary) {
252
335
  throw new LogicError(`Make sure to execute a script before checking on the result`);
253
336
  }
254
337
 
255
- return this.lastScriptExecutionSummary.result as Result;
338
+ // Selenium returns `null` when the script it executed returns `undefined`
339
+ // so we're mapping the result back.
340
+ return this.lastScriptExecutionSummary.result !== null
341
+ ? this.lastScriptExecutionSummary.result as Result
342
+ : undefined;
343
+ }
344
+
345
+ waitFor(duration: Duration): Promise<void> {
346
+ return this.browser.pause(duration.inMilliseconds()) as Promise<void>;
347
+ }
348
+
349
+ waitUntil(condition: () => boolean | Promise<boolean>, timeout: Duration): Promise<void> {
350
+ return this.browser.waitUntil(condition, {
351
+ timeout: timeout.inMilliseconds(),
352
+ timeoutMsg: `Wait timed out after ${ timeout }`,
353
+ }) as Promise<void>;
256
354
  }
257
355
  }
258
356
 
@@ -1 +1 @@
1
- export * from './BrowseTheWeb';
1
+ export * from './BrowseTheWebWithWebdriverIO';
@@ -1,3 +1,2 @@
1
1
  export * from './abilities';
2
- export * from './interactions';
3
- export * from './questions';
2
+ export * from './models';