@serenity-js/webdriverio 2.33.1 → 3.0.0-rc.11

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 (247) hide show
  1. package/CHANGELOG.md +455 -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} +42 -27
  11. package/lib/screenplay/abilities/{BrowseTheWeb.js → BrowseTheWebWithWebdriverIO.js} +104 -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/WebdriverIOFrame.d.ts +10 -0
  23. package/lib/screenplay/models/WebdriverIOFrame.js +34 -0
  24. package/lib/screenplay/models/WebdriverIOFrame.js.map +1 -0
  25. package/lib/screenplay/models/WebdriverIOModalDialog.d.ts +11 -0
  26. package/lib/screenplay/models/WebdriverIOModalDialog.js +40 -0
  27. package/lib/screenplay/models/WebdriverIOModalDialog.js.map +1 -0
  28. package/lib/screenplay/models/WebdriverIOPage.d.ts +26 -0
  29. package/lib/screenplay/models/WebdriverIOPage.js +104 -0
  30. package/lib/screenplay/models/WebdriverIOPage.js.map +1 -0
  31. package/lib/screenplay/models/WebdriverIOPageElement.d.ts +31 -0
  32. package/lib/screenplay/models/WebdriverIOPageElement.js +185 -0
  33. package/lib/screenplay/models/WebdriverIOPageElement.js.map +1 -0
  34. package/lib/screenplay/models/index.d.ts +5 -0
  35. package/lib/{stage/crew/photographer/strategies → screenplay/models}/index.js +5 -4
  36. package/lib/screenplay/models/index.js.map +1 -0
  37. package/lib/screenplay/models/locators/WebdriverIOLocator.d.ts +9 -0
  38. package/lib/screenplay/models/locators/WebdriverIOLocator.js +22 -0
  39. package/lib/screenplay/models/locators/WebdriverIOLocator.js.map +1 -0
  40. package/lib/screenplay/models/locators/WebdriverIONativeElementRoot.d.ts +2 -0
  41. package/lib/screenplay/{interactions/EnterBuilder.js → models/locators/WebdriverIONativeElementRoot.js} +1 -1
  42. package/lib/screenplay/models/locators/WebdriverIONativeElementRoot.js.map +1 -0
  43. package/lib/screenplay/models/locators/index.d.ts +2 -0
  44. package/lib/{stage/crew/photographer → screenplay/models/locators}/index.js +2 -2
  45. package/lib/screenplay/models/locators/index.js.map +1 -0
  46. package/package.json +14 -24
  47. package/src/adapter/WebdriverIOFrameworkAdapter.ts +2 -0
  48. package/src/adapter/WebdriverIONotifier.ts +225 -23
  49. package/src/index.ts +0 -3
  50. package/src/screenplay/abilities/{BrowseTheWeb.ts → BrowseTheWebWithWebdriverIO.ts} +125 -35
  51. package/src/screenplay/abilities/index.ts +1 -1
  52. package/src/screenplay/index.ts +1 -2
  53. package/src/screenplay/models/WebdriverIOCookie.ts +44 -0
  54. package/src/screenplay/models/WebdriverIOFrame.ts +38 -0
  55. package/src/screenplay/models/WebdriverIOModalDialog.ts +45 -0
  56. package/src/screenplay/models/WebdriverIOPage.ts +128 -0
  57. package/src/screenplay/models/WebdriverIOPageElement.ts +227 -0
  58. package/src/screenplay/models/index.ts +5 -0
  59. package/src/screenplay/models/locators/WebdriverIOLocator.ts +43 -0
  60. package/src/screenplay/models/locators/WebdriverIONativeElementRoot.ts +3 -0
  61. package/src/screenplay/models/locators/index.ts +2 -0
  62. package/lib/expectations/ElementExpectation.d.ts +0 -11
  63. package/lib/expectations/ElementExpectation.js +0 -27
  64. package/lib/expectations/ElementExpectation.js.map +0 -1
  65. package/lib/expectations/index.d.ts +0 -6
  66. package/lib/expectations/index.js +0 -19
  67. package/lib/expectations/index.js.map +0 -1
  68. package/lib/expectations/isActive.d.ts +0 -15
  69. package/lib/expectations/isActive.js +0 -21
  70. package/lib/expectations/isActive.js.map +0 -1
  71. package/lib/expectations/isClickable.d.ts +0 -20
  72. package/lib/expectations/isClickable.js +0 -26
  73. package/lib/expectations/isClickable.js.map +0 -1
  74. package/lib/expectations/isEnabled.d.ts +0 -14
  75. package/lib/expectations/isEnabled.js +0 -20
  76. package/lib/expectations/isEnabled.js.map +0 -1
  77. package/lib/expectations/isPresent.d.ts +0 -15
  78. package/lib/expectations/isPresent.js +0 -21
  79. package/lib/expectations/isPresent.js.map +0 -1
  80. package/lib/expectations/isSelected.d.ts +0 -14
  81. package/lib/expectations/isSelected.js +0 -20
  82. package/lib/expectations/isSelected.js.map +0 -1
  83. package/lib/expectations/isVisible.d.ts +0 -14
  84. package/lib/expectations/isVisible.js +0 -20
  85. package/lib/expectations/isVisible.js.map +0 -1
  86. package/lib/input/Key.d.ts +0 -73
  87. package/lib/input/Key.js +0 -84
  88. package/lib/input/Key.js.map +0 -1
  89. package/lib/input/index.d.ts +0 -1
  90. package/lib/input/index.js +0 -14
  91. package/lib/input/index.js.map +0 -1
  92. package/lib/screenplay/abilities/BrowseTheWeb.js.map +0 -1
  93. package/lib/screenplay/interactions/Clear.d.ts +0 -79
  94. package/lib/screenplay/interactions/Clear.js +0 -97
  95. package/lib/screenplay/interactions/Clear.js.map +0 -1
  96. package/lib/screenplay/interactions/Click.d.ts +0 -73
  97. package/lib/screenplay/interactions/Click.js +0 -84
  98. package/lib/screenplay/interactions/Click.js.map +0 -1
  99. package/lib/screenplay/interactions/DoubleClick.d.ts +0 -90
  100. package/lib/screenplay/interactions/DoubleClick.js +0 -101
  101. package/lib/screenplay/interactions/DoubleClick.js.map +0 -1
  102. package/lib/screenplay/interactions/Enter.d.ts +0 -73
  103. package/lib/screenplay/interactions/Enter.js +0 -87
  104. package/lib/screenplay/interactions/Enter.js.map +0 -1
  105. package/lib/screenplay/interactions/EnterBuilder.d.ts +0 -25
  106. package/lib/screenplay/interactions/EnterBuilder.js.map +0 -1
  107. package/lib/screenplay/interactions/ExecuteScript.d.ts +0 -206
  108. package/lib/screenplay/interactions/ExecuteScript.js +0 -311
  109. package/lib/screenplay/interactions/ExecuteScript.js.map +0 -1
  110. package/lib/screenplay/interactions/Hover.d.ts +0 -78
  111. package/lib/screenplay/interactions/Hover.js +0 -89
  112. package/lib/screenplay/interactions/Hover.js.map +0 -1
  113. package/lib/screenplay/interactions/Navigate.d.ts +0 -141
  114. package/lib/screenplay/interactions/Navigate.js +0 -197
  115. package/lib/screenplay/interactions/Navigate.js.map +0 -1
  116. package/lib/screenplay/interactions/Press.d.ts +0 -84
  117. package/lib/screenplay/interactions/Press.js +0 -152
  118. package/lib/screenplay/interactions/Press.js.map +0 -1
  119. package/lib/screenplay/interactions/PressBuilder.d.ts +0 -26
  120. package/lib/screenplay/interactions/PressBuilder.js +0 -3
  121. package/lib/screenplay/interactions/PressBuilder.js.map +0 -1
  122. package/lib/screenplay/interactions/RightClick.d.ts +0 -89
  123. package/lib/screenplay/interactions/RightClick.js +0 -100
  124. package/lib/screenplay/interactions/RightClick.js.map +0 -1
  125. package/lib/screenplay/interactions/Scroll.d.ts +0 -75
  126. package/lib/screenplay/interactions/Scroll.js +0 -86
  127. package/lib/screenplay/interactions/Scroll.js.map +0 -1
  128. package/lib/screenplay/interactions/Wait.d.ts +0 -143
  129. package/lib/screenplay/interactions/Wait.js +0 -247
  130. package/lib/screenplay/interactions/Wait.js.map +0 -1
  131. package/lib/screenplay/interactions/WaitBuilder.d.ts +0 -32
  132. package/lib/screenplay/interactions/WaitBuilder.js +0 -3
  133. package/lib/screenplay/interactions/WaitBuilder.js.map +0 -1
  134. package/lib/screenplay/interactions/WebElementInteraction.d.ts +0 -37
  135. package/lib/screenplay/interactions/WebElementInteraction.js +0 -52
  136. package/lib/screenplay/interactions/WebElementInteraction.js.map +0 -1
  137. package/lib/screenplay/interactions/index.d.ts +0 -13
  138. package/lib/screenplay/interactions/index.js +0 -26
  139. package/lib/screenplay/interactions/index.js.map +0 -1
  140. package/lib/screenplay/questions/Attribute.d.ts +0 -82
  141. package/lib/screenplay/questions/Attribute.js +0 -102
  142. package/lib/screenplay/questions/Attribute.js.map +0 -1
  143. package/lib/screenplay/questions/CSSClasses.d.ts +0 -92
  144. package/lib/screenplay/questions/CSSClasses.js +0 -112
  145. package/lib/screenplay/questions/CSSClasses.js.map +0 -1
  146. package/lib/screenplay/questions/LastScriptExecution.d.ts +0 -14
  147. package/lib/screenplay/questions/LastScriptExecution.js +0 -22
  148. package/lib/screenplay/questions/LastScriptExecution.js.map +0 -1
  149. package/lib/screenplay/questions/NestedTargetBuilder.d.ts +0 -27
  150. package/lib/screenplay/questions/NestedTargetBuilder.js +0 -3
  151. package/lib/screenplay/questions/NestedTargetBuilder.js.map +0 -1
  152. package/lib/screenplay/questions/TargetBuilder.d.ts +0 -25
  153. package/lib/screenplay/questions/TargetBuilder.js +0 -3
  154. package/lib/screenplay/questions/TargetBuilder.js.map +0 -1
  155. package/lib/screenplay/questions/Text.d.ts +0 -95
  156. package/lib/screenplay/questions/Text.js +0 -130
  157. package/lib/screenplay/questions/Text.js.map +0 -1
  158. package/lib/screenplay/questions/Value.d.ts +0 -63
  159. package/lib/screenplay/questions/Value.js +0 -78
  160. package/lib/screenplay/questions/Value.js.map +0 -1
  161. package/lib/screenplay/questions/Website.d.ts +0 -21
  162. package/lib/screenplay/questions/Website.js +0 -31
  163. package/lib/screenplay/questions/Website.js.map +0 -1
  164. package/lib/screenplay/questions/index.d.ts +0 -10
  165. package/lib/screenplay/questions/index.js +0 -23
  166. package/lib/screenplay/questions/index.js.map +0 -1
  167. package/lib/screenplay/questions/lists.d.ts +0 -86
  168. package/lib/screenplay/questions/lists.js +0 -137
  169. package/lib/screenplay/questions/lists.js.map +0 -1
  170. package/lib/screenplay/questions/locators.d.ts +0 -196
  171. package/lib/screenplay/questions/locators.js +0 -219
  172. package/lib/screenplay/questions/locators.js.map +0 -1
  173. package/lib/screenplay/questions/targets.d.ts +0 -254
  174. package/lib/screenplay/questions/targets.js +0 -334
  175. package/lib/screenplay/questions/targets.js.map +0 -1
  176. package/lib/stage/crew/index.d.ts +0 -1
  177. package/lib/stage/crew/index.js +0 -14
  178. package/lib/stage/crew/index.js.map +0 -1
  179. package/lib/stage/crew/photographer/Photographer.d.ts +0 -83
  180. package/lib/stage/crew/photographer/Photographer.js +0 -102
  181. package/lib/stage/crew/photographer/Photographer.js.map +0 -1
  182. package/lib/stage/crew/photographer/index.d.ts +0 -2
  183. package/lib/stage/crew/photographer/index.js.map +0 -1
  184. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.d.ts +0 -28
  185. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js +0 -65
  186. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js.map +0 -1
  187. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.d.ts +0 -18
  188. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js +0 -30
  189. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js.map +0 -1
  190. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.d.ts +0 -17
  191. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js +0 -28
  192. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js.map +0 -1
  193. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.d.ts +0 -19
  194. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js +0 -28
  195. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js.map +0 -1
  196. package/lib/stage/crew/photographer/strategies/index.d.ts +0 -4
  197. package/lib/stage/crew/photographer/strategies/index.js.map +0 -1
  198. package/lib/stage/index.d.ts +0 -1
  199. package/lib/stage/index.js +0 -14
  200. package/lib/stage/index.js.map +0 -1
  201. package/src/expectations/ElementExpectation.ts +0 -31
  202. package/src/expectations/index.ts +0 -6
  203. package/src/expectations/isActive.ts +0 -21
  204. package/src/expectations/isClickable.ts +0 -26
  205. package/src/expectations/isEnabled.ts +0 -19
  206. package/src/expectations/isPresent.ts +0 -20
  207. package/src/expectations/isSelected.ts +0 -19
  208. package/src/expectations/isVisible.ts +0 -19
  209. package/src/input/Key.ts +0 -83
  210. package/src/input/index.ts +0 -1
  211. package/src/screenplay/interactions/Clear.ts +0 -102
  212. package/src/screenplay/interactions/Click.ts +0 -85
  213. package/src/screenplay/interactions/DoubleClick.ts +0 -102
  214. package/src/screenplay/interactions/Enter.ts +0 -93
  215. package/src/screenplay/interactions/EnterBuilder.ts +0 -27
  216. package/src/screenplay/interactions/ExecuteScript.ts +0 -344
  217. package/src/screenplay/interactions/Hover.ts +0 -90
  218. package/src/screenplay/interactions/Navigate.ts +0 -208
  219. package/src/screenplay/interactions/Press.ts +0 -172
  220. package/src/screenplay/interactions/PressBuilder.ts +0 -28
  221. package/src/screenplay/interactions/RightClick.ts +0 -100
  222. package/src/screenplay/interactions/Scroll.ts +0 -87
  223. package/src/screenplay/interactions/Wait.ts +0 -267
  224. package/src/screenplay/interactions/WaitBuilder.ts +0 -34
  225. package/src/screenplay/interactions/WebElementInteraction.ts +0 -56
  226. package/src/screenplay/interactions/index.ts +0 -13
  227. package/src/screenplay/questions/Attribute.ts +0 -112
  228. package/src/screenplay/questions/CSSClasses.ts +0 -116
  229. package/src/screenplay/questions/LastScriptExecution.ts +0 -21
  230. package/src/screenplay/questions/NestedTargetBuilder.ts +0 -30
  231. package/src/screenplay/questions/TargetBuilder.ts +0 -27
  232. package/src/screenplay/questions/Text.ts +0 -140
  233. package/src/screenplay/questions/Value.ts +0 -82
  234. package/src/screenplay/questions/Website.ts +0 -34
  235. package/src/screenplay/questions/index.ts +0 -10
  236. package/src/screenplay/questions/lists.ts +0 -161
  237. package/src/screenplay/questions/locators.ts +0 -254
  238. package/src/screenplay/questions/targets.ts +0 -401
  239. package/src/stage/crew/index.ts +0 -1
  240. package/src/stage/crew/photographer/Photographer.ts +0 -108
  241. package/src/stage/crew/photographer/index.ts +0 -2
  242. package/src/stage/crew/photographer/strategies/PhotoTakingStrategy.ts +0 -103
  243. package/src/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.ts +0 -28
  244. package/src/stage/crew/photographer/strategies/TakePhotosOfFailures.ts +0 -26
  245. package/src/stage/crew/photographer/strategies/TakePhotosOfInteractions.ts +0 -26
  246. package/src/stage/crew/photographer/strategies/index.ts +0 -4
  247. 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, Frame, Key, ModalDialog, Page, Selector } from '@serenity-js/web';
3
+ import type * as wdio from 'webdriverio';
3
4
 
4
- import { Key } from '../../input';
5
+ import { WebdriverIOCookie, WebdriverIOFrame, 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,106 @@ 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 frame(bySelector: Selector): Promise<Frame<any>> {
151
+ const locator = this.locate(bySelector);
152
+ return new WebdriverIOFrame(this.browser, locator);
153
+ }
154
+
155
+ async modalDialog(): Promise<ModalDialog> {
156
+ return new WebdriverIOModalDialog(this.browser);
85
157
  }
86
158
 
87
159
  /**
@@ -101,7 +173,7 @@ export class BrowseTheWeb implements Ability {
101
173
  return key;
102
174
  }
103
175
 
104
- if (browser.isDevTools) {
176
+ if (this.browser.isDevTools) {
105
177
  return key.devtoolsName;
106
178
  }
107
179
 
@@ -162,12 +234,13 @@ export class BrowseTheWeb implements Ability {
162
234
  *
163
235
  * @see {@link BrowseTheWeb#getLastScriptExecutionResult}
164
236
  */
165
- executeScript<Result, InnerArguments extends any[]>(
237
+ async executeScript<Result, InnerArguments extends any[]>(
166
238
  script: string | ((...parameters: InnerArguments) => Result),
167
239
  ...args: InnerArguments
168
240
  ): Promise<Result> {
241
+ const nativeArguments = await Promise.all(args.map(arg => arg instanceof WebdriverIOPageElement ? arg.nativeElement() : arg)) as InnerArguments;
169
242
 
170
- return this.browser.execute(script, ...args)
243
+ return this.browser.execute(script, ...nativeArguments)
171
244
  .then(result => {
172
245
  this.lastScriptExecutionSummary = new LastScriptExecutionSummary(
173
246
  result,
@@ -188,7 +261,7 @@ export class BrowseTheWeb implements Ability {
188
261
  * Arrays and objects may also be used as script arguments as long as each item adheres
189
262
  * to the types previously mentioned.
190
263
  *
191
- * Unlike executing synchronous JavaScript with {@link BrowseTheWeb#executeScript},
264
+ * Unlike executing synchronous JavaScript with {@link BrowseTheWebWithWebdriverIO#executeScript},
192
265
  * scripts executed with this function must explicitly signal they are finished by invoking the provided callback.
193
266
  *
194
267
  * This callback will always be injected into the executed function as the last argument,
@@ -227,11 +300,13 @@ export class BrowseTheWeb implements Ability {
227
300
  *
228
301
  * @see {@link BrowseTheWeb#getLastScriptExecutionResult}
229
302
  */
230
- executeAsyncScript<Result, Parameters extends any[]>(
303
+ async executeAsyncScript<Result, Parameters extends any[]>(
231
304
  script: string | ((...args: [...parameters: Parameters, callback: (result: Result) => void]) => void),
232
305
  ...args: Parameters
233
306
  ): Promise<Result> {
234
- return this.browser.executeAsync<Result, Parameters>(script, ...args)
307
+ const nativeArguments = await Promise.all(args.map(arg => arg instanceof WebdriverIOPageElement ? arg.nativeElement() : arg)) as Parameters;
308
+
309
+ return this.browser.executeAsync<Result, Parameters>(script, ...nativeArguments)
235
310
  .then(result => {
236
311
  this.lastScriptExecutionSummary = new LastScriptExecutionSummary<Result>(
237
312
  result,
@@ -242,17 +317,32 @@ export class BrowseTheWeb implements Ability {
242
317
 
243
318
  /**
244
319
  * @desc
245
- * Returns the last result of calling {@link BrowseTheWeb#executeAsyncScript}
246
- * or {@link BrowseTheWeb#executeScript}
320
+ * Returns the last result of calling {@link BrowseTheWebWithWebdriverIO#executeAsyncScript}
321
+ * or {@link BrowseTheWebWithWebdriverIO#executeScript}
247
322
  *
248
323
  * @returns {any}
249
324
  */
250
- getLastScriptExecutionResult<Result = any>(): Result {
325
+ lastScriptExecutionResult<Result = any>(): Result {
251
326
  if (! this.lastScriptExecutionSummary) {
252
327
  throw new LogicError(`Make sure to execute a script before checking on the result`);
253
328
  }
254
329
 
255
- return this.lastScriptExecutionSummary.result as Result;
330
+ // Selenium returns `null` when the script it executed returns `undefined`
331
+ // so we're mapping the result back.
332
+ return this.lastScriptExecutionSummary.result !== null
333
+ ? this.lastScriptExecutionSummary.result as Result
334
+ : undefined;
335
+ }
336
+
337
+ waitFor(duration: Duration): Promise<void> {
338
+ return this.browser.pause(duration.inMilliseconds()) as Promise<void>;
339
+ }
340
+
341
+ waitUntil(condition: () => boolean | Promise<boolean>, timeout: Duration): Promise<void> {
342
+ return this.browser.waitUntil(condition, {
343
+ timeout: timeout.inMilliseconds(),
344
+ timeoutMsg: `Wait timed out after ${ timeout }`,
345
+ }) as Promise<void>;
256
346
  }
257
347
  }
258
348
 
@@ -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';
@@ -0,0 +1,44 @@
1
+ import { Timestamp } from '@serenity-js/core';
2
+ import { Cookie, CookieData, CookieMissingError } from '@serenity-js/web';
3
+ import { ensure, isDefined } from 'tiny-types';
4
+ import * as wdio from 'webdriverio';
5
+
6
+ export class WebdriverIOCookie extends Cookie {
7
+
8
+ constructor(
9
+ private readonly browser: wdio.Browser<'async'>,
10
+ cookieName: string,
11
+ ) {
12
+ super(cookieName);
13
+ ensure('browser', browser, isDefined());
14
+ }
15
+
16
+ async delete(): Promise<void> {
17
+ return this.browser.deleteCookies(this.cookieName) as Promise<void>;
18
+ }
19
+
20
+ protected async read(): Promise<CookieData> {
21
+ const [ cookie ] = await this.browser.getCookies(this.cookieName);
22
+
23
+ if (! cookie) {
24
+ throw new CookieMissingError(`Cookie '${ this.cookieName }' not set`);
25
+ }
26
+
27
+ // There _might_ be a bug in WDIO where the expiry date is set on "expires" rather than the "expiry" key
28
+ // and possibly another one around deserialising the timestamp, since WDIO seems to add several hundred milliseconds
29
+ // to the original expiry date
30
+ const expiry: number | undefined = cookie.expiry || (cookie as any).expires;
31
+
32
+ return {
33
+ name: cookie.name,
34
+ value: cookie.value,
35
+ domain: cookie.domain,
36
+ path: cookie.path,
37
+ expiry: expiry !== undefined
38
+ ? Timestamp.fromTimestampInSeconds(Math.round(expiry))
39
+ : undefined,
40
+ httpOnly: cookie.httpOnly,
41
+ secure: cookie.secure,
42
+ }
43
+ }
44
+ }