@serenity-js/web 3.0.0-rc.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 (250) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE.md +201 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +21 -0
  5. package/lib/errors/CookieMissingError.d.ts +4 -0
  6. package/lib/errors/CookieMissingError.js +11 -0
  7. package/lib/errors/CookieMissingError.js.map +1 -0
  8. package/lib/errors/index.d.ts +1 -0
  9. package/lib/errors/index.js +14 -0
  10. package/lib/errors/index.js.map +1 -0
  11. package/lib/expectations/ElementExpectation.d.ts +11 -0
  12. package/lib/expectations/ElementExpectation.js +27 -0
  13. package/lib/expectations/ElementExpectation.js.map +1 -0
  14. package/lib/expectations/index.d.ts +6 -0
  15. package/lib/expectations/index.js +19 -0
  16. package/lib/expectations/index.js.map +1 -0
  17. package/lib/expectations/isActive.d.ts +15 -0
  18. package/lib/expectations/isActive.js +22 -0
  19. package/lib/expectations/isActive.js.map +1 -0
  20. package/lib/expectations/isClickable.d.ts +20 -0
  21. package/lib/expectations/isClickable.js +30 -0
  22. package/lib/expectations/isClickable.js.map +1 -0
  23. package/lib/expectations/isEnabled.d.ts +14 -0
  24. package/lib/expectations/isEnabled.js +20 -0
  25. package/lib/expectations/isEnabled.js.map +1 -0
  26. package/lib/expectations/isPresent.d.ts +15 -0
  27. package/lib/expectations/isPresent.js +22 -0
  28. package/lib/expectations/isPresent.js.map +1 -0
  29. package/lib/expectations/isSelected.d.ts +14 -0
  30. package/lib/expectations/isSelected.js +23 -0
  31. package/lib/expectations/isSelected.js.map +1 -0
  32. package/lib/expectations/isVisible.d.ts +14 -0
  33. package/lib/expectations/isVisible.js +26 -0
  34. package/lib/expectations/isVisible.js.map +1 -0
  35. package/lib/index.d.ts +5 -0
  36. package/lib/index.js +18 -0
  37. package/lib/index.js.map +1 -0
  38. package/lib/input/Key.d.ts +73 -0
  39. package/lib/input/Key.js +84 -0
  40. package/lib/input/Key.js.map +1 -0
  41. package/lib/input/index.d.ts +1 -0
  42. package/lib/input/index.js +14 -0
  43. package/lib/input/index.js.map +1 -0
  44. package/lib/screenplay/abilities/BrowseTheWeb.d.ts +58 -0
  45. package/lib/screenplay/abilities/BrowseTheWeb.js +19 -0
  46. package/lib/screenplay/abilities/BrowseTheWeb.js.map +1 -0
  47. package/lib/screenplay/abilities/BrowserCapabilities.d.ts +5 -0
  48. package/lib/screenplay/abilities/BrowserCapabilities.js +3 -0
  49. package/lib/screenplay/abilities/BrowserCapabilities.js.map +1 -0
  50. package/lib/screenplay/abilities/index.d.ts +2 -0
  51. package/lib/screenplay/abilities/index.js +15 -0
  52. package/lib/screenplay/abilities/index.js.map +1 -0
  53. package/lib/screenplay/index.d.ts +4 -0
  54. package/lib/screenplay/index.js +17 -0
  55. package/lib/screenplay/index.js.map +1 -0
  56. package/lib/screenplay/interactions/Clear.d.ts +79 -0
  57. package/lib/screenplay/interactions/Clear.js +97 -0
  58. package/lib/screenplay/interactions/Clear.js.map +1 -0
  59. package/lib/screenplay/interactions/Click.d.ts +73 -0
  60. package/lib/screenplay/interactions/Click.js +85 -0
  61. package/lib/screenplay/interactions/Click.js.map +1 -0
  62. package/lib/screenplay/interactions/DoubleClick.d.ts +90 -0
  63. package/lib/screenplay/interactions/DoubleClick.js +101 -0
  64. package/lib/screenplay/interactions/DoubleClick.js.map +1 -0
  65. package/lib/screenplay/interactions/Enter.d.ts +73 -0
  66. package/lib/screenplay/interactions/Enter.js +86 -0
  67. package/lib/screenplay/interactions/Enter.js.map +1 -0
  68. package/lib/screenplay/interactions/EnterBuilder.d.ts +25 -0
  69. package/lib/screenplay/interactions/EnterBuilder.js +3 -0
  70. package/lib/screenplay/interactions/EnterBuilder.js.map +1 -0
  71. package/lib/screenplay/interactions/ExecuteScript.d.ts +206 -0
  72. package/lib/screenplay/interactions/ExecuteScript.js +312 -0
  73. package/lib/screenplay/interactions/ExecuteScript.js.map +1 -0
  74. package/lib/screenplay/interactions/Hover.d.ts +78 -0
  75. package/lib/screenplay/interactions/Hover.js +89 -0
  76. package/lib/screenplay/interactions/Hover.js.map +1 -0
  77. package/lib/screenplay/interactions/Navigate.d.ts +142 -0
  78. package/lib/screenplay/interactions/Navigate.js +198 -0
  79. package/lib/screenplay/interactions/Navigate.js.map +1 -0
  80. package/lib/screenplay/interactions/PageElementInteraction.d.ts +39 -0
  81. package/lib/screenplay/interactions/PageElementInteraction.js +54 -0
  82. package/lib/screenplay/interactions/PageElementInteraction.js.map +1 -0
  83. package/lib/screenplay/interactions/Press.d.ts +84 -0
  84. package/lib/screenplay/interactions/Press.js +171 -0
  85. package/lib/screenplay/interactions/Press.js.map +1 -0
  86. package/lib/screenplay/interactions/PressBuilder.d.ts +26 -0
  87. package/lib/screenplay/interactions/PressBuilder.js +3 -0
  88. package/lib/screenplay/interactions/PressBuilder.js.map +1 -0
  89. package/lib/screenplay/interactions/RightClick.d.ts +89 -0
  90. package/lib/screenplay/interactions/RightClick.js +100 -0
  91. package/lib/screenplay/interactions/RightClick.js.map +1 -0
  92. package/lib/screenplay/interactions/Scroll.d.ts +83 -0
  93. package/lib/screenplay/interactions/Scroll.js +97 -0
  94. package/lib/screenplay/interactions/Scroll.js.map +1 -0
  95. package/lib/screenplay/interactions/Select.d.ts +212 -0
  96. package/lib/screenplay/interactions/Select.js +291 -0
  97. package/lib/screenplay/interactions/Select.js.map +1 -0
  98. package/lib/screenplay/interactions/SelectBuilder.d.ts +33 -0
  99. package/lib/screenplay/interactions/SelectBuilder.js +3 -0
  100. package/lib/screenplay/interactions/SelectBuilder.js.map +1 -0
  101. package/lib/screenplay/interactions/Switch.d.ts +150 -0
  102. package/lib/screenplay/interactions/Switch.js +209 -0
  103. package/lib/screenplay/interactions/Switch.js.map +1 -0
  104. package/lib/screenplay/interactions/TakeScreenshot.d.ts +67 -0
  105. package/lib/screenplay/interactions/TakeScreenshot.js +86 -0
  106. package/lib/screenplay/interactions/TakeScreenshot.js.map +1 -0
  107. package/lib/screenplay/interactions/Wait.d.ts +143 -0
  108. package/lib/screenplay/interactions/Wait.js +242 -0
  109. package/lib/screenplay/interactions/Wait.js.map +1 -0
  110. package/lib/screenplay/interactions/WaitBuilder.d.ts +32 -0
  111. package/lib/screenplay/interactions/WaitBuilder.js +3 -0
  112. package/lib/screenplay/interactions/WaitBuilder.js.map +1 -0
  113. package/lib/screenplay/interactions/index.d.ts +16 -0
  114. package/lib/screenplay/interactions/index.js +29 -0
  115. package/lib/screenplay/interactions/index.js.map +1 -0
  116. package/lib/screenplay/models/Cookie.d.ts +117 -0
  117. package/lib/screenplay/models/Cookie.js +176 -0
  118. package/lib/screenplay/models/Cookie.js.map +1 -0
  119. package/lib/screenplay/models/CookieData.d.ts +89 -0
  120. package/lib/screenplay/models/CookieData.js +3 -0
  121. package/lib/screenplay/models/CookieData.js.map +1 -0
  122. package/lib/screenplay/models/ModalDialog.d.ts +9 -0
  123. package/lib/screenplay/models/ModalDialog.js +14 -0
  124. package/lib/screenplay/models/ModalDialog.js.map +1 -0
  125. package/lib/screenplay/models/Page.d.ts +83 -0
  126. package/lib/screenplay/models/Page.js +52 -0
  127. package/lib/screenplay/models/Page.js.map +1 -0
  128. package/lib/screenplay/models/PageElement.d.ts +30 -0
  129. package/lib/screenplay/models/PageElement.js +62 -0
  130. package/lib/screenplay/models/PageElement.js.map +1 -0
  131. package/lib/screenplay/models/PageElements.d.ts +20 -0
  132. package/lib/screenplay/models/PageElements.js +49 -0
  133. package/lib/screenplay/models/PageElements.js.map +1 -0
  134. package/lib/screenplay/models/index.d.ts +6 -0
  135. package/lib/screenplay/models/index.js +19 -0
  136. package/lib/screenplay/models/index.js.map +1 -0
  137. package/lib/screenplay/questions/Attribute.d.ts +83 -0
  138. package/lib/screenplay/questions/Attribute.js +103 -0
  139. package/lib/screenplay/questions/Attribute.js.map +1 -0
  140. package/lib/screenplay/questions/CssClasses.d.ts +93 -0
  141. package/lib/screenplay/questions/CssClasses.js +113 -0
  142. package/lib/screenplay/questions/CssClasses.js.map +1 -0
  143. package/lib/screenplay/questions/ElementQuestion.d.ts +34 -0
  144. package/lib/screenplay/questions/ElementQuestion.js +53 -0
  145. package/lib/screenplay/questions/ElementQuestion.js.map +1 -0
  146. package/lib/screenplay/questions/LastScriptExecution.d.ts +14 -0
  147. package/lib/screenplay/questions/LastScriptExecution.js +22 -0
  148. package/lib/screenplay/questions/LastScriptExecution.js.map +1 -0
  149. package/lib/screenplay/questions/Selected.d.ts +185 -0
  150. package/lib/screenplay/questions/Selected.js +210 -0
  151. package/lib/screenplay/questions/Selected.js.map +1 -0
  152. package/lib/screenplay/questions/Text.d.ts +99 -0
  153. package/lib/screenplay/questions/Text.js +131 -0
  154. package/lib/screenplay/questions/Text.js.map +1 -0
  155. package/lib/screenplay/questions/Value.d.ts +64 -0
  156. package/lib/screenplay/questions/Value.js +78 -0
  157. package/lib/screenplay/questions/Value.js.map +1 -0
  158. package/lib/screenplay/questions/index.d.ts +6 -0
  159. package/lib/screenplay/questions/index.js +19 -0
  160. package/lib/screenplay/questions/index.js.map +1 -0
  161. package/lib/stage/crew/index.d.ts +1 -0
  162. package/lib/stage/crew/index.js +14 -0
  163. package/lib/stage/crew/index.js.map +1 -0
  164. package/lib/stage/crew/photographer/Photographer.d.ts +83 -0
  165. package/lib/stage/crew/photographer/Photographer.js +102 -0
  166. package/lib/stage/crew/photographer/Photographer.js.map +1 -0
  167. package/lib/stage/crew/photographer/index.d.ts +2 -0
  168. package/lib/stage/crew/photographer/index.js +15 -0
  169. package/lib/stage/crew/photographer/index.js.map +1 -0
  170. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.d.ts +28 -0
  171. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js +81 -0
  172. package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js.map +1 -0
  173. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.d.ts +18 -0
  174. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js +30 -0
  175. package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js.map +1 -0
  176. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.d.ts +17 -0
  177. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js +28 -0
  178. package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js.map +1 -0
  179. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.d.ts +19 -0
  180. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js +28 -0
  181. package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js.map +1 -0
  182. package/lib/stage/crew/photographer/strategies/index.d.ts +4 -0
  183. package/lib/stage/crew/photographer/strategies/index.js +17 -0
  184. package/lib/stage/crew/photographer/strategies/index.js.map +1 -0
  185. package/lib/stage/index.d.ts +1 -0
  186. package/lib/stage/index.js +14 -0
  187. package/lib/stage/index.js.map +1 -0
  188. package/package.json +85 -0
  189. package/src/errors/CookieMissingError.ts +7 -0
  190. package/src/errors/index.ts +1 -0
  191. package/src/expectations/ElementExpectation.ts +32 -0
  192. package/src/expectations/index.ts +6 -0
  193. package/src/expectations/isActive.ts +22 -0
  194. package/src/expectations/isClickable.ts +32 -0
  195. package/src/expectations/isEnabled.ts +19 -0
  196. package/src/expectations/isPresent.ts +21 -0
  197. package/src/expectations/isSelected.ts +24 -0
  198. package/src/expectations/isVisible.ts +28 -0
  199. package/src/index.ts +5 -0
  200. package/src/input/Key.ts +83 -0
  201. package/src/input/index.ts +1 -0
  202. package/src/screenplay/abilities/BrowseTheWeb.ts +89 -0
  203. package/src/screenplay/abilities/BrowserCapabilities.ts +5 -0
  204. package/src/screenplay/abilities/index.ts +2 -0
  205. package/src/screenplay/index.ts +4 -0
  206. package/src/screenplay/interactions/Clear.ts +102 -0
  207. package/src/screenplay/interactions/Click.ts +86 -0
  208. package/src/screenplay/interactions/DoubleClick.ts +102 -0
  209. package/src/screenplay/interactions/Enter.ts +92 -0
  210. package/src/screenplay/interactions/EnterBuilder.ts +28 -0
  211. package/src/screenplay/interactions/ExecuteScript.ts +345 -0
  212. package/src/screenplay/interactions/Hover.ts +90 -0
  213. package/src/screenplay/interactions/Navigate.ts +209 -0
  214. package/src/screenplay/interactions/PageElementInteraction.ts +59 -0
  215. package/src/screenplay/interactions/Press.ts +194 -0
  216. package/src/screenplay/interactions/PressBuilder.ts +29 -0
  217. package/src/screenplay/interactions/RightClick.ts +100 -0
  218. package/src/screenplay/interactions/Scroll.ts +99 -0
  219. package/src/screenplay/interactions/Select.ts +317 -0
  220. package/src/screenplay/interactions/SelectBuilder.ts +36 -0
  221. package/src/screenplay/interactions/Switch.ts +225 -0
  222. package/src/screenplay/interactions/TakeScreenshot.ts +89 -0
  223. package/src/screenplay/interactions/Wait.ts +264 -0
  224. package/src/screenplay/interactions/WaitBuilder.ts +34 -0
  225. package/src/screenplay/interactions/index.ts +16 -0
  226. package/src/screenplay/models/Cookie.ts +219 -0
  227. package/src/screenplay/models/CookieData.ts +97 -0
  228. package/src/screenplay/models/ModalDialog.ts +19 -0
  229. package/src/screenplay/models/Page.ts +147 -0
  230. package/src/screenplay/models/PageElement.ts +95 -0
  231. package/src/screenplay/models/PageElements.ts +70 -0
  232. package/src/screenplay/models/index.ts +6 -0
  233. package/src/screenplay/questions/Attribute.ts +112 -0
  234. package/src/screenplay/questions/CssClasses.ts +118 -0
  235. package/src/screenplay/questions/ElementQuestion.ts +60 -0
  236. package/src/screenplay/questions/LastScriptExecution.ts +21 -0
  237. package/src/screenplay/questions/Selected.ts +212 -0
  238. package/src/screenplay/questions/Text.ts +153 -0
  239. package/src/screenplay/questions/Value.ts +82 -0
  240. package/src/screenplay/questions/index.ts +6 -0
  241. package/src/stage/crew/index.ts +1 -0
  242. package/src/stage/crew/photographer/Photographer.ts +108 -0
  243. package/src/stage/crew/photographer/index.ts +2 -0
  244. package/src/stage/crew/photographer/strategies/PhotoTakingStrategy.ts +116 -0
  245. package/src/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.ts +28 -0
  246. package/src/stage/crew/photographer/strategies/TakePhotosOfFailures.ts +26 -0
  247. package/src/stage/crew/photographer/strategies/TakePhotosOfInteractions.ts +26 -0
  248. package/src/stage/crew/photographer/strategies/index.ts +4 -0
  249. package/src/stage/index.ts +1 -0
  250. package/tsconfig.eslint.json +10 -0
@@ -0,0 +1,212 @@
1
+ import { Answerable, Question } from '@serenity-js/core';
2
+ import { formatted } from '@serenity-js/core/lib/io';
3
+
4
+ import { PageElement, PageElements } from '../models';
5
+
6
+ /**
7
+ * @desc
8
+ * Represents options and values selected in a
9
+ * [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select).
10
+ *
11
+ * @see {@link Select}
12
+ */
13
+ export class Selected {
14
+
15
+ /**
16
+ * @desc
17
+ * Represents the value of a single option selected in a
18
+ * [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select).
19
+ *
20
+ * @example <caption>Example widget</caption>
21
+ * <select data-test='countries'>
22
+ * <option value='UK'>United Kingdom</option>
23
+ * <option value='PL'>Poland</option>
24
+ * <option value='US'>United States</option>
25
+ * </select>
26
+ *
27
+ * @example <caption>Lean Page Object</caption>
28
+ * import { Target } from '@serenity-js/protractor';
29
+ * import { browser, by } from 'protractor';
30
+ *
31
+ * class Countries {
32
+ * static dropdown = Target.the('countries dropdown')
33
+ * .located(by.css('[data-test="countries"]'));
34
+ * }
35
+ *
36
+ * @example <caption>Retrieving the selected value</caption>
37
+ * import { actorCalled } from '@serenity-js/core';
38
+ * import { Accept, BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
39
+ * import { Ensure, equals } from '@serenity-js/assertions';
40
+ * import { protractor } from 'protractor';
41
+ *
42
+ * actorCalled('Nick')
43
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
44
+ * .attemptsTo(
45
+ * Select.value('UK').from(Countries.dropdown),
46
+ * Ensure.that(Selected.valueOf(Countries.dropdown), equals('UK')),
47
+ * );
48
+ *
49
+ * @param {Answerable<PageElement>} pageElement
50
+ * A {@link PageElement} identifying the `<select>` element of interest
51
+ *
52
+ * @returns {Question<Promise<string>>}
53
+ *
54
+ * @see {@link Select.value}
55
+ */
56
+ static valueOf(pageElement: Answerable<PageElement>): Question<Promise<string>> {
57
+ return PageElement.locatedByCss('option:checked')
58
+ .of(pageElement)
59
+ .value()
60
+ .describedAs(formatted `value selected in ${ pageElement }`);
61
+ }
62
+
63
+ /**
64
+ * @desc
65
+ * Represents values of options selected in a
66
+ * [HTML `<select multiple>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple)
67
+ *
68
+ * @example <caption>Example widget</caption>
69
+ * <select multiple data-test='countries'>
70
+ * <option value='UK'>United Kingdom</option>
71
+ * <option value='PL'>Poland</option>
72
+ * <option value='US'>United States</option>
73
+ * </select>
74
+ *
75
+ * @example <caption>Lean Page Object</caption>
76
+ * import { Target } from '@serenity-js/protractor';
77
+ * import { browser, by } from 'protractor';
78
+ *
79
+ * class Countries {
80
+ * static dropdown = Target.the('countries dropdown')
81
+ * .located(by.css('[data-test="countries"]'));
82
+ * }
83
+ *
84
+ * @example <caption>Retrieving the selected value</caption>
85
+ * import { actorCalled } from '@serenity-js/core';
86
+ * import { Accept, BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
87
+ * import { Ensure, equals } from '@serenity-js/assertions';
88
+ * import { protractor } from 'protractor';
89
+ *
90
+ * actorCalled('Nick')
91
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
92
+ * .attemptsTo(
93
+ * Select.values('UK').from(Countries.dropdown),
94
+ * Ensure.that(Selected.valuesOf(Countries.dropdown), equals([ 'UK' ])),
95
+ * );
96
+ *
97
+ * @param {Answerable<PageElement>} pageElement
98
+ * A {@link Target} identifying the `<select>` element of interest
99
+ *
100
+ * @returns {Question<Promise<string[]>>}
101
+ *
102
+ * @see {@link Select.values}
103
+ */
104
+ static valuesOf(pageElement: Answerable<PageElement>): Question<Promise<string[]>> {
105
+ return PageElements.locatedByCss('option:checked')
106
+ .of(pageElement)
107
+ .map(item => item.value())
108
+ .describedAs(formatted `values selected in ${ pageElement }`) as Question<Promise<string[]>>;
109
+ }
110
+
111
+ /**
112
+ * @desc
113
+ * Represents a single option selected in a
114
+ * [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple)
115
+ *
116
+ * @example <caption>Example widget</caption>
117
+ * <select data-test='countries'>
118
+ * <option value='UK'>United Kingdom</option>
119
+ * <option value='PL'>Poland</option>
120
+ * <option value='US'>United States</option>
121
+ * </select>
122
+ *
123
+ * @example <caption>Lean Page Object</caption>
124
+ * import { Target } from '@serenity-js/protractor';
125
+ * import { browser, by } from 'protractor';
126
+ *
127
+ * class Countries {
128
+ * static dropdown = Target.the('countries dropdown')
129
+ * .located(by.css('[data-test="countries"]'));
130
+ * }
131
+ *
132
+ * @example <caption>Retrieving the selected value</caption>
133
+ * import { actorCalled } from '@serenity-js/core';
134
+ * import { Accept, BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
135
+ * import { Ensure, equals } from '@serenity-js/assertions';
136
+ * import { protractor } from 'protractor';
137
+ *
138
+ * actorCalled('Nick')
139
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
140
+ * .attemptsTo(
141
+ * Select.option('Poland').from(Countries.dropdown),
142
+ * Ensure.that(
143
+ * Selected.optionIn(Countries.dropdown),
144
+ * equals('Poland')
145
+ * ),
146
+ * );
147
+ *
148
+ * @param {Answerable<PageElement>} pageElement
149
+ * A {@link Target} identifying the `<select>` element of interest
150
+ *
151
+ * @returns {Question<Promise<string>>}
152
+ *
153
+ * @see {@link Select.option}
154
+ */
155
+ static optionIn(pageElement: Answerable<PageElement>): Question<Promise<string>> {
156
+ return PageElement.locatedByCss('option:checked')
157
+ .of(pageElement)
158
+ .text()
159
+ .describedAs(formatted `option selected in ${ pageElement }`);
160
+ }
161
+
162
+ /**
163
+ * @desc
164
+ * Represents options selected in a
165
+ * [HTML `<select multiple>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#attr-multiple)
166
+ *
167
+ * @example <caption>Example widget</caption>
168
+ * <select multiple data-test='countries'>
169
+ * <option value='UK'>United Kingdom</option>
170
+ * <option value='PL'>Poland</option>
171
+ * <option value='US'>United States</option>
172
+ * </select>
173
+ *
174
+ * @example <caption>Lean Page Object</caption>
175
+ * import { Target } from '@serenity-js/protractor';
176
+ * import { browser, by } from 'protractor';
177
+ *
178
+ * class Countries {
179
+ * static dropdown = Target.the('countries dropdown')
180
+ * .located(by.css('[data-test="countries"]'));
181
+ * }
182
+ *
183
+ * @example <caption>Retrieving the selected value</caption>
184
+ * import { actorCalled } from '@serenity-js/core';
185
+ * import { Accept, BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
186
+ * import { Ensure, equals } from '@serenity-js/assertions';
187
+ * import { protractor } from 'protractor';
188
+ *
189
+ * actorCalled('Nick')
190
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
191
+ * .attemptsTo(
192
+ * Select.options('Poland', 'United States').from(Countries.dropdown),
193
+ * Ensure.that(
194
+ * Selected.optionsIn(Countries.dropdown),
195
+ * equals([ 'Poland', 'United States' ])
196
+ * ),
197
+ * );
198
+ *
199
+ * @param {Answerable<PageElement>} pageElement
200
+ * A {@link Target} identifying the `<select>` element of interest
201
+ *
202
+ * @returns {Question<Promise<string[]>>}
203
+ *
204
+ * @see {@link Select.options}
205
+ */
206
+ static optionsIn(pageElement: Answerable<PageElement>): Question<Promise<string[]>> {
207
+ return PageElements.locatedByCss('option:checked')
208
+ .of(pageElement)
209
+ .map(item => item.text())
210
+ .describedAs(formatted `options selected in ${ pageElement }`) as Question<Promise<string[]>>;
211
+ }
212
+ }
@@ -0,0 +1,153 @@
1
+ import { Adapter, Answerable, AnswersQuestions, createAdapter, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
2
+
3
+ import { PageElement, PageElements } from '../models';
4
+ import { ElementQuestion } from './ElementQuestion';
5
+
6
+ /**
7
+ * @desc
8
+ * Resolves to the visible (i.e. not hidden by CSS) `innerText` of:
9
+ * - a given {@link WebElement}, represented by Answerable<{@link @wdio/types~Element}>
10
+ * - a group of {@link WebElement}s, represented by Answerable<{@link @wdio/types~ElementList}>
11
+ *
12
+ * The result includes the visible text of any sub-elements, without any leading or trailing whitespace.
13
+ *
14
+ * @example <caption>Example widget</caption>
15
+ * <h1>Shopping list</h1>
16
+ * <ul id="shopping-list">
17
+ * <li>Coffee<li>
18
+ * <li class="bought">Honey<li>
19
+ * <li>Chocolate<li>
20
+ * </ul>
21
+ *
22
+ * @example <caption>Retrieve text of a single element</caption>
23
+ * import { actorCalled } from '@serenity-js/core';
24
+ * import { Ensure, equals } from '@serenity-js/assertions';
25
+ * import { BrowseTheWeb, by, Target, Text } from '@serenity-js/webdriverio';
26
+ *
27
+ * const header = () =>
28
+ * Target.the('header').located(by.tagName('h1'))
29
+ *
30
+ * actorCalled('Lisa')
31
+ * .whoCan(BrowseTheWeb.using(browser))
32
+ * .attemptsTo(
33
+ * Ensure.that(Text.of(header()), equals('Shopping list')),
34
+ * )
35
+ *
36
+ * @example <caption>Retrieve text of a multiple elements</caption>
37
+ * import { actorCalled } from '@serenity-js/core';
38
+ * import { Ensure, equals } from '@serenity-js/assertions';
39
+ * import { BrowseTheWeb, by, Target, Text } from '@serenity-js/webdriverio';
40
+ *
41
+ * const shoppingListItems = () =>
42
+ * Target.the('shopping list items').located(by.css('#shopping-list li'))
43
+ *
44
+ * actorCalled('Lisa')
45
+ * .whoCan(BrowseTheWeb.using(browser))
46
+ * .attemptsTo(
47
+ * Ensure.that(
48
+ * Text.ofAll(shoppingListItems()),
49
+ * equals([ 'Coffee', 'Honey', 'Chocolate' ])
50
+ * ),
51
+ * )
52
+ *
53
+ * @example <caption>Find element with matching text</caption>
54
+ * import { actorCalled } from '@serenity-js/core';
55
+ * import { contain, Ensure } from '@serenity-js/assertions';
56
+ * import { BrowseTheWeb, by, CssClasses, Target, Text } from '@serenity-js/webdriverio';
57
+ *
58
+ * const shoppingListItemCalled = (name: string) =>
59
+ * Target.the('shopping list items').located(by.css('#shopping-list li'))
60
+ * .where(Text, equals(name))
61
+ * .first()
62
+ *
63
+ * actorCalled('Lisa')
64
+ * .whoCan(BrowseTheWeb.using(browser))
65
+ * .attemptsTo(
66
+ * Ensure.that(
67
+ * CssClasses.of(shoppingListItemCalled('Honey)),
68
+ * contain('bought')
69
+ * ),
70
+ * )
71
+ *
72
+ * @public
73
+ * @see {@link Target}
74
+ */
75
+ export class Text {
76
+
77
+ /**
78
+ * @desc
79
+ * Retrieves text of a single {@link WebElement},
80
+ * represented by Answerable<{@link @wdio/types~Element}>.
81
+ *
82
+ * @param {Answerable<PageElement>} element
83
+ * @returns {Question<Promise<string>> & MetaQuestion<Answerable<PageElement>, Promise<string>>}
84
+ *
85
+ * @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
86
+ */
87
+ static of(element: Answerable<PageElement>):
88
+ Question<Promise<string>> & // eslint-disable-line @typescript-eslint/indent
89
+ MetaQuestion<Answerable<PageElement>, Promise<string>> & // eslint-disable-line @typescript-eslint/indent
90
+ Adapter<string> // eslint-disable-line @typescript-eslint/indent
91
+ {
92
+ return createAdapter<Promise<string>, ElementQuestion<Promise<string>> & MetaQuestion<Answerable<PageElement>, Promise<string>>>(
93
+ new TextOfSingleElement(element)
94
+ );
95
+ }
96
+
97
+ /**
98
+ * @desc
99
+ * Retrieves text of a group of {@link WebElement}s,
100
+ * represented by Answerable<{@link @wdio/types~ElementList}>
101
+ *
102
+ * @param {Answerable<PageElements>} elements
103
+ * @returns {Question<Promise<string[]>> & MetaQuestion<Answerable<PageElement>, Promise<string[]>>}
104
+ *
105
+ * @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
106
+ */
107
+ static ofAll(elements: Answerable<PageElements>):
108
+ Question<Promise<string[]>> & // eslint-disable-line @typescript-eslint/indent
109
+ MetaQuestion<Answerable<PageElement>, Promise<string[]>> & // eslint-disable-line @typescript-eslint/indent
110
+ Adapter<string[]> // eslint-disable-line @typescript-eslint/indent
111
+ {
112
+ return createAdapter<Promise<string[]>, ElementQuestion<Promise<string[]>> & MetaQuestion<Answerable<PageElement>, Promise<string[]>>>(
113
+ new TextOfMultipleElements(elements)
114
+ );
115
+ }
116
+ }
117
+
118
+ class TextOfSingleElement
119
+ extends ElementQuestion<Promise<string>>
120
+ implements MetaQuestion<Answerable<PageElement>, Promise<string>>
121
+ {
122
+ constructor(private readonly element: Answerable<PageElement>) {
123
+ super(`the text of ${ element }`);
124
+ }
125
+
126
+ of(parent: Answerable<PageElement>): Question<Promise<string>> {
127
+ return new TextOfSingleElement(PageElement.of(this.element, parent));
128
+ }
129
+
130
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string> {
131
+ const element = await this.resolve(actor, this.element);
132
+
133
+ return element.text();
134
+ }
135
+ }
136
+
137
+ class TextOfMultipleElements
138
+ extends ElementQuestion<Promise<string[]>>
139
+ implements MetaQuestion<Answerable<PageElement>, Promise<string[]>>
140
+ {
141
+ constructor(private readonly elements: Answerable<PageElements>) {
142
+ super(`the text of ${ elements }`);
143
+ }
144
+
145
+ of(parent: Answerable<PageElement>): Question<Promise<string[]>> {
146
+ return new TextOfMultipleElements(PageElements.of(this.elements, parent));
147
+ }
148
+
149
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string[]> {
150
+ const elements = await this.resolve(actor, this.elements);
151
+ return elements.map(element => element.text());
152
+ }
153
+ }
@@ -0,0 +1,82 @@
1
+ import { Answerable, AnswersQuestions, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
2
+ import { formatted } from '@serenity-js/core/lib/io';
3
+
4
+ import { PageElement } from '../models';
5
+ import { ElementQuestion } from './ElementQuestion';
6
+
7
+ /**
8
+ * @desc
9
+ * Returns the `value` attribute of a given {@link WebElement},
10
+ * represented by Answerable<{@link @wdio/types~Element}>
11
+ *
12
+ * @example <caption>Example widget</caption>
13
+ * <input type="text" id="username" value="Alice" />
14
+ *
15
+ * @example <caption>Retrieve CSS classes of a given WebElement</caption>
16
+ * import { actorCalled } from '@serenity-js/core';
17
+ * import { Ensure, equals } from '@serenity-js/assertions';
18
+ * import { BrowseTheWeb, by, Value, Target } from '@serenity-js/webdriverio';
19
+ *
20
+ * const usernameField = () =>
21
+ * Target.the('username field').located(by.id('username'))
22
+ *
23
+ * actorCalled('Lisa')
24
+ * .whoCan(BrowseTheWeb.using(browser))
25
+ * .attemptsTo(
26
+ * Ensure.that(Value.of(usernameField), equals('Alice')),
27
+ * )
28
+ *
29
+ * @extends {@serenity-js/core/lib/screenplay~Question}
30
+ * @implements {@serenity-js/core/lib/screenplay/questions~MetaQuestion}
31
+ */
32
+ export class Value
33
+ extends ElementQuestion<Promise<string>>
34
+ implements MetaQuestion<Answerable<PageElement>, Promise<string>>
35
+ {
36
+ /**
37
+ * @param {Answerable<PageElement>} element
38
+ * @returns {Value}
39
+ */
40
+ static of(element: Answerable<PageElement>): Question<Promise<string>> & MetaQuestion<Answerable<PageElement>, Promise<string>> {
41
+ return new Value(element);
42
+ }
43
+
44
+ /**
45
+ * @param {Answerable<PageElement>} element
46
+ */
47
+ constructor(private readonly element: Answerable<PageElement>) {
48
+ super(formatted`the value of ${ element }`);
49
+ }
50
+
51
+ /**
52
+ * @desc
53
+ * Resolves to the value of a given [`input`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
54
+ * {@link WebElement}, located in the context of a `parent` element.
55
+ *
56
+ * @param {Answerable<PageElement>} parent
57
+ * @returns {Question<Promise<string>>}
58
+ *
59
+ * @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
60
+ */
61
+ of(parent: Answerable<PageElement>): Question<Promise<string>> {
62
+ return new Value(PageElement.of(this.element, parent));
63
+ }
64
+
65
+ /**
66
+ * @desc
67
+ * Makes the provided {@link @serenity-js/core/lib/screenplay/actor~Actor}
68
+ * answer this {@link @serenity-js/core/lib/screenplay~Question}.
69
+ *
70
+ * @param {AnswersQuestions & UsesAbilities} actor
71
+ * @returns {Promise<void>}
72
+ *
73
+ * @see {@link @serenity-js/core/lib/screenplay/actor~Actor}
74
+ * @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
75
+ * @see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities}
76
+ */
77
+ async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string> {
78
+ const element = await this.resolve(actor, this.element);
79
+
80
+ return element.value();
81
+ }
82
+ }
@@ -0,0 +1,6 @@
1
+ export * from './Attribute';
2
+ export * from './CssClasses';
3
+ export * from './LastScriptExecution';
4
+ export * from './Selected';
5
+ export * from './Text';
6
+ export * from './Value';
@@ -0,0 +1 @@
1
+ export * from './photographer';
@@ -0,0 +1,108 @@
1
+ import { LogicError } from '@serenity-js/core';
2
+ import { ActivityFinished, ActivityStarts, DomainEvent } from '@serenity-js/core/lib/events';
3
+ import { Stage, StageCrewMember } from '@serenity-js/core/lib/stage';
4
+
5
+ import { PhotoTakingStrategy } from './strategies';
6
+
7
+ /**
8
+ * @desc
9
+ * The Photographer is a {@link @serenity-js/core/lib/stage~StageCrewMember} who takes screenshots
10
+ * of the web browser that the {@link @serenity-js/core/lib/screenplay/actor~Actor} in the spotlight is using.
11
+ *
12
+ * @example <caption>Assigning the Photographer to the Stage</caption>
13
+ * // wdio.conf.ts
14
+ * import { ArtifactArchiver } from '@serenity-js/core';
15
+ * import { Photographer, TakePhotosOfFailures } from '@serenity-js/webdriverio';
16
+ *
17
+ * export const config = {
18
+ *
19
+ * serenity: {
20
+ * crew: [
21
+ * ArtifactArchiver.storingArtifactsAt(process.cwd(), 'target/site/serenity'),
22
+ * Photographer.whoWill(TakePhotosOfFailures),
23
+ * ]
24
+ * },
25
+ *
26
+ * // ... rest of the config omitted for brevity
27
+ * };
28
+ *
29
+ * @example <caption>Taking photos upon failures only</caption>
30
+ *
31
+ * import { Photographer, TakePhotosOfFailures } from '@serenity-js/webdriverio';
32
+ *
33
+ * Photographer.whoWill(TakePhotosOfFailures)
34
+ *
35
+ * @example <caption>Taking photos of all the interactions</caption>
36
+ *
37
+ * import { Photographer, TakePhotosOfInteractions } from '@serenity-js/webdriverio';
38
+ *
39
+ * Photographer.whoWill(TakePhotosOfInteractions)
40
+ *
41
+ * @example <caption>Taking photos before and after all the interactions</caption>
42
+ *
43
+ * import { Photographer, TakePhotosBeforeAndAfterInteractions } from '@serenity-js/webdriverio';
44
+ *
45
+ * Photographer.whoWill(TakePhotosBeforeAndAfterInteractions)
46
+ *
47
+ * @see {@link @serenity-js/core/lib/stage~Stage}
48
+ * @see {@link TakePhotosBeforeAndAfterInteractions}
49
+ * @see {@link TakePhotosOfFailures}
50
+ * @see {@link TakePhotosOfInteractions}
51
+ */
52
+ export class Photographer implements StageCrewMember {
53
+
54
+ /**
55
+ * @desc
56
+ * Instantiates a new {@link Photographer} configured to take photos (screenshots)
57
+ * as per the specified {@link PhotoTakingStrategy}.
58
+ *
59
+ * @param {Function} strategy - A no-arg constructor function that instantiates a {@link PhotoTakingStrategy}.
60
+ * @returns {StageCrewMember}
61
+ */
62
+ static whoWill(strategy: new () => PhotoTakingStrategy): StageCrewMember {
63
+ return new Photographer(new strategy());
64
+ }
65
+
66
+ /**
67
+ * @param {PhotoTakingStrategy} photoTakingStrategy
68
+ * @param {Stage} stage
69
+ */
70
+ constructor(
71
+ private readonly photoTakingStrategy: PhotoTakingStrategy,
72
+ private stage?: Stage,
73
+ ) {
74
+ }
75
+
76
+ /**
77
+ * @desc
78
+ * Creates a new instance of this {@link StageCrewMember} and assigns it to a given {@link Stage}.
79
+ *
80
+ * @param {Stage} stage - An instance of a {@link Stage} this {@link StageCrewMember} will be assigned to
81
+ * @returns {StageCrewMember} - A new instance of this {@link StageCrewMember}
82
+ */
83
+ assignedTo(stage: Stage): StageCrewMember {
84
+ return new Photographer(this.photoTakingStrategy, stage);
85
+ }
86
+
87
+ /**
88
+ * @desc
89
+ * Handles {@link DomainEvent} objects emitted by the {@link Stage}
90
+ * this {@link StageCrewMember} is assigned to.
91
+ *
92
+ * @param {DomainEvent} event
93
+ * @returns {void}
94
+ */
95
+ notifyOf(event: DomainEvent): void {
96
+ if (! this.stage) {
97
+ throw new LogicError(`Photographer needs to be assigned to the Stage before it can be notified of any DomainEvents`);
98
+ }
99
+
100
+ if (! this.stage.theShowHasStarted()) {
101
+ return void 0;
102
+ }
103
+
104
+ if (event instanceof ActivityStarts || event instanceof ActivityFinished) {
105
+ this.photoTakingStrategy.considerTakingPhoto(event, this.stage);
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Photographer';
2
+ export * from './strategies';
@@ -0,0 +1,116 @@
1
+ import { Stage } from '@serenity-js/core';
2
+ import { ActivityFinished, ActivityRelatedArtifactGenerated, ActivityStarts, AsyncOperationAttempted, AsyncOperationCompleted, AsyncOperationFailed, DomainEvent } from '@serenity-js/core/lib/events';
3
+ import { CorrelationId, Description, Name, Photo } from '@serenity-js/core/lib/model';
4
+
5
+ import { BrowseTheWeb } from '../../../../screenplay';
6
+
7
+ /**
8
+ * @desc
9
+ * Configures the {@link Photographer} to take photos (a.k.a. screenshots)
10
+ * of the {@link @serenity-js/core/lib/screenplay~Activity} performed
11
+ * by the {@link @serenity-js/core/lib/screenplay/actor~Actor} in the spotlight
12
+ * under specific conditions.
13
+ *
14
+ * @abstract
15
+ */
16
+ export abstract class PhotoTakingStrategy {
17
+
18
+ /**
19
+ * @desc
20
+ * Takes a photo of the web browser held by the {@link @serenity-js/core/lib/screenplay/actor~Actor} in the spotlight.
21
+ *
22
+ * @param {@serenity-js/core/lib/events~ActivityStarts | @serenity-js/core/lib/events~ActivityFinished} event
23
+ * @param {@serenity-js/core/lib/stage~Stage} stage - the Stage that holds reference to the Actor in the spotlight
24
+ * @returns {void}
25
+ *
26
+ * @see {@link @serenity-js/core/lib/stage~Stage#theActorInTheSpotlight}
27
+ */
28
+ async considerTakingPhoto(event: ActivityStarts | ActivityFinished, stage: Stage): Promise<void> {
29
+ if (! this.shouldTakeAPhotoOf(event)) {
30
+ return void 0;
31
+ }
32
+
33
+ let browseTheWeb: BrowseTheWeb;
34
+
35
+ try {
36
+ browseTheWeb = BrowseTheWeb.as(stage.theActorInTheSpotlight());
37
+ } catch {
38
+ // actor doesn't have a browser, abort
39
+ return void 0;
40
+ }
41
+
42
+ const
43
+ id = CorrelationId.create(),
44
+ nameSuffix = this.photoNameFor(event);
45
+
46
+ stage.announce(new AsyncOperationAttempted(
47
+ new Description(`[Photographer:${ this.constructor.name }] Taking screenshot of '${ nameSuffix }'...`),
48
+ id,
49
+ ));
50
+
51
+ let dialogIsPresent: boolean;
52
+
53
+ try {
54
+ dialogIsPresent = await browseTheWeb.modalDialog().then(dialog => dialog.isPresent());
55
+
56
+ if (dialogIsPresent) {
57
+ return stage.announce(new AsyncOperationCompleted(
58
+ new Description(`[${ this.constructor.name }] Aborted taking screenshot of '${ nameSuffix }' because of a modal dialog obstructing the view`),
59
+ id,
60
+ ));
61
+ }
62
+ } catch (error) {
63
+ return stage.announce(new AsyncOperationFailed(error, id));
64
+ }
65
+
66
+ try {
67
+ const [ screenshot, capabilities ] = await Promise.all([
68
+ browseTheWeb.takeScreenshot(),
69
+ browseTheWeb.browserCapabilities(),
70
+ ]);
71
+
72
+ const
73
+ context = [ capabilities.platformName, capabilities.browserName, capabilities.browserVersion ],
74
+ photoName = this.combinedNameFrom(...context, nameSuffix);
75
+
76
+ stage.announce(new ActivityRelatedArtifactGenerated(
77
+ event.sceneId,
78
+ event.activityId,
79
+ photoName,
80
+ Photo.fromBase64(screenshot),
81
+ ));
82
+
83
+ return stage.announce(new AsyncOperationCompleted(
84
+ new Description(`[${ this.constructor.name }] Took screenshot of '${ nameSuffix }' on ${ context }`),
85
+ id,
86
+ ));
87
+ }
88
+ catch (error) {
89
+ if (this.shouldIgnore(error)) {
90
+ stage.announce(new AsyncOperationCompleted(
91
+ new Description(`[${ this.constructor.name }] Aborted taking screenshot of '${ nameSuffix }' because of ${ error.constructor && error.constructor.name }`),
92
+ id,
93
+ ));
94
+ }
95
+ else {
96
+ stage.announce(new AsyncOperationFailed(error, id));
97
+ }
98
+ }
99
+ }
100
+
101
+ protected abstract shouldTakeAPhotoOf(event: DomainEvent): boolean;
102
+
103
+ protected abstract photoNameFor(event: DomainEvent): string;
104
+
105
+ private combinedNameFrom(...parts: string[]): Name {
106
+ return new Name(parts.filter(v => !! v).join('-'));
107
+ }
108
+
109
+ private shouldIgnore(error: Error) {
110
+ return error.name
111
+ && (error.name === 'NoSuchSessionError');
112
+ // todo: add SauceLabs
113
+ // [0-0] 2021-12-02T01:32:36.402Z ERROR webdriver: Request failed with status 404 due to no such window: no such window: target window already closed
114
+ // [0-0] from unknown error: web view not found
115
+ }
116
+ }