@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,317 @@
1
+ import { Answerable, q } from '@serenity-js/core';
2
+ import { commaSeparated, formatted } from '@serenity-js/core/lib/io';
3
+ import { inspected } from '@serenity-js/core/lib/io/inspected';
4
+ import { Interaction } from '@serenity-js/core/lib/screenplay';
5
+
6
+ import { PageElement, PageElements } from '../models';
7
+ import { SelectBuilder } from './SelectBuilder';
8
+
9
+ /**
10
+ * @desc
11
+ * Instructs the {@link @serenity-js/core/lib/screenplay/actor~Actor} to
12
+ * select an option from a [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select),
13
+ * either by its display name, or by value.
14
+ *
15
+ * @see {@link Selected}
16
+ */
17
+ export class Select {
18
+
19
+ /**
20
+ * @desc
21
+ * Instantiates this {@link @serenity-js/core/lib/screenplay~Interaction}
22
+ * with a [`value`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#attr-value)
23
+ * of a single [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
24
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select.
25
+ *
26
+ * @example <caption>Example widget</caption>
27
+ * <select data-test='countries'>
28
+ * <option value='UK'>United Kingdom</option>
29
+ * <option value='PL'>Poland</option>
30
+ * <option value='US'>United States</option>
31
+ * </select>
32
+ *
33
+ * @example <caption>Lean Page Object describing the widget</caption>
34
+ * import { Target } from '@serenity-js/protractor';
35
+ * import { browser, by } from 'protractor';
36
+ *
37
+ * class Countries {
38
+ * static dropdown = Target.the('countries dropdown')
39
+ * .located(by.css('[data-test="countries"]'));
40
+ * }
41
+ *
42
+ * @example <caption>Retrieving the selected value</caption>
43
+ * import { actorCalled } from '@serenity-js/core';
44
+ * import { BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
45
+ * import { Ensure, equals } from '@serenity-js/assertions';
46
+ * import { protractor } from 'protractor';
47
+ *
48
+ * actorCalled('Nick')
49
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
50
+ * .attemptsTo(
51
+ * Select.value('UK').from(Countries.dropdown),
52
+ * Ensure.that(Selected.valueOf(Countries.dropdown), equals('UK')),
53
+ * );
54
+ *
55
+ * @param {Answerable<string>} value
56
+ * A value of the [`option` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
57
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select
58
+ *
59
+ * @returns {SelectBuilder}
60
+ *
61
+ * @see {@link Selected.valueOf}
62
+ * @see {@link BrowseTheWeb}
63
+ * @see {@link Target}
64
+ * @see {@link @serenity-js/assertions~Ensure}
65
+ * @see {@link @serenity-js/assertions/lib/expectations~equals}
66
+ */
67
+ static value(value: Answerable<string>): SelectBuilder {
68
+ return {
69
+ from: (pageElement: Answerable<PageElement>): Interaction =>
70
+ Interaction.where(formatted `#actor selects value ${ value } from ${ pageElement }`, async actor => {
71
+ return PageElement.locatedByCss(q`option[value=${ value }]`)
72
+ .of(pageElement)
73
+ .click()
74
+ .performAs(actor);
75
+ }),
76
+ };
77
+ }
78
+
79
+ /**
80
+ * @desc
81
+ * Instantiates this {@link @serenity-js/core/lib/screenplay~Interaction}
82
+ * with [`value`s](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#attr-value)
83
+ * of multiple [`<option>` elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
84
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select.
85
+ *
86
+ * @example <caption>Example widget</caption>
87
+ * <select multiple data-test='countries'>
88
+ * <option value='UK'>United Kingdom</option>
89
+ * <option value='PL'>Poland</option>
90
+ * <option value='US'>United States</option>
91
+ * </select>
92
+ *
93
+ * @example <caption>Lean Page Object describing the widget</caption>
94
+ * import { Target } from '@serenity-js/protractor';
95
+ * import { browser, by } from 'protractor';
96
+ *
97
+ * class Countries {
98
+ * static dropdown = Target.the('countries dropdown')
99
+ * .located(by.css('[data-test="countries"]'));
100
+ * }
101
+ *
102
+ * @example <caption>Retrieving the selected value</caption>
103
+ * import { actorCalled } from '@serenity-js/core';
104
+ * import { BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
105
+ * import { Ensure, equals } from '@serenity-js/assertions';
106
+ * import { protractor } from 'protractor';
107
+ *
108
+ * actorCalled('Nick')
109
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
110
+ * .attemptsTo(
111
+ * Select.values('UK').from(Countries.dropdown),
112
+ * Ensure.that(Selected.valuesOf(Countries.dropdown), equals([ 'UK' ])),
113
+ * );
114
+ *
115
+ * @param {Array<Answerable<string[] | string>>} values
116
+ * Values of the [`option` elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
117
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select
118
+ *
119
+ * @returns {SelectBuilder}
120
+ *
121
+ * @see {@link Selected.valuesOf}
122
+ * @see {@link BrowseTheWeb}
123
+ * @see {@link Target}
124
+ * @see {@link @serenity-js/assertions~Ensure}
125
+ * @see {@link @serenity-js/assertions/lib/expectations~equals}
126
+ */
127
+ static values(...values: Array<Answerable<string[] | string>>): SelectBuilder {
128
+ return {
129
+ from: (pageElement: Answerable<PageElement>): Interaction =>
130
+ Interaction.where(`#actor selects values ${ commaSeparated(values.flat(), item => inspected(item, { inline: true })) } from ${ inspected(pageElement, { inline: true }) }`, async actor => {
131
+
132
+ const desiredValues = (await Promise.all(values.map(value => actor.answer(value)))).flat(); // eslint-disable-line unicorn/no-await-expression-member
133
+
134
+ const options: PageElements = await PageElements.locatedByCss(`option`).of(pageElement).answeredBy(actor);
135
+ const shouldSelect: boolean[] = await options.map(optionsToSelect(hasValueEqualOneOf(desiredValues)));
136
+
137
+ return options.forEach((option, index) => {
138
+ if (shouldSelect[index]) {
139
+ return option.click()
140
+ }
141
+ });
142
+ }),
143
+ };
144
+ }
145
+
146
+ /**
147
+ * @desc
148
+ * Instantiates this {@link @serenity-js/core/lib/screenplay~Interaction}
149
+ * with a single [`option`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
150
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select.
151
+ *
152
+ * @example <caption>Example widget</caption>
153
+ * <select data-test='countries'>
154
+ * <option value='UK'>United Kingdom</option>
155
+ * <option value='PL'>Poland</option>
156
+ * <option value='US'>United States</option>
157
+ * </select>
158
+ *
159
+ * @example <caption>Lean Page Object describing the widget</caption>
160
+ * import { Target } from '@serenity-js/protractor';
161
+ * import { browser, by } from 'protractor';
162
+ *
163
+ * class Countries {
164
+ * static dropdown = Target.the('countries dropdown')
165
+ * .located(by.css('[data-test="countries"]'));
166
+ * }
167
+ *
168
+ * @example <caption>Retrieving the selected value</caption>
169
+ * import { actorCalled } from '@serenity-js/core';
170
+ * import { BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
171
+ * import { Ensure, equals } from '@serenity-js/assertions';
172
+ * import { protractor } from 'protractor';
173
+ *
174
+ * actorCalled('Nick')
175
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
176
+ * .attemptsTo(
177
+ * Select.option('Poland').from(Countries.dropdown),
178
+ * Ensure.that(
179
+ * Selected.optionIn(Countries.dropdown),
180
+ * equals('Poland')
181
+ * ),
182
+ * );
183
+ *
184
+ * @param {Answerable<string>} value
185
+ * Text of the [`option` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
186
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select
187
+ *
188
+ * @returns {SelectBuilder}
189
+ *
190
+ * @see {@link Selected.optionIn}
191
+ * @see {@link BrowseTheWeb}
192
+ * @see {@link Target}
193
+ * @see {@link @serenity-js/assertions~Ensure}
194
+ * @see {@link @serenity-js/assertions/lib/expectations~equals}
195
+ */
196
+ static option(value: Answerable<string>): SelectBuilder {
197
+ return {
198
+ from: (pageElement: Answerable<PageElement>): Interaction =>
199
+ Interaction.where(formatted `#actor selects ${ value } from ${ pageElement }`, async actor => {
200
+ return PageElement.locatedByCssContainingText('option', value)
201
+ .of(pageElement)
202
+ .click()
203
+ .performAs(actor);
204
+ }),
205
+ };
206
+ }
207
+
208
+ /**
209
+ * @desc
210
+ * Instantiates this {@link @serenity-js/core/lib/screenplay~Interaction}
211
+ * with [`option`s](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
212
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select.
213
+ *
214
+ * @example <caption>Example widget</caption>
215
+ * <select multiple data-test='countries'>
216
+ * <option value='UK'>United Kingdom</option>
217
+ * <option value='PL'>Poland</option>
218
+ * <option value='US'>United States</option>
219
+ * </select>
220
+ *
221
+ * @example <caption>Lean Page Object describing the widget</caption>
222
+ * import { Target } from '@serenity-js/protractor';
223
+ * import { browser, by } from 'protractor';
224
+ *
225
+ * class Countries {
226
+ * static dropdown = Target.the('countries dropdown')
227
+ * .located(by.css('[data-test="countries"]'));
228
+ * }
229
+ *
230
+ * @example <caption>Retrieving the selected value</caption>
231
+ * import { actorCalled } from '@serenity-js/core';
232
+ * import { BrowseTheWeb, Select, Selected } from '@serenity-js/protractor';
233
+ * import { Ensure, equals } from '@serenity-js/assertions';
234
+ * import { protractor } from 'protractor';
235
+ *
236
+ * actorCalled('Nick')
237
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
238
+ * .attemptsTo(
239
+ * Select.options('Poland', 'United States').from(Countries.dropdown),
240
+ * Ensure.that(
241
+ * Selected.optionsIn(Countries.dropdown),
242
+ * equals([ 'Poland', 'United States' ])
243
+ * ),
244
+ * );
245
+ *
246
+ * @param {Array<Answerable<string[] | string>>} values
247
+ * Text of the [`option` elements ](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
248
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor} to select
249
+ *
250
+ * @returns {SelectBuilder}
251
+ *
252
+ * @see {@link Selected.optionsIn}
253
+ * @see {@link BrowseTheWeb}
254
+ * @see {@link Target}
255
+ * @see {@link @serenity-js/assertions~Ensure}
256
+ * @see {@link @serenity-js/assertions/lib/expectations~equals}
257
+ */
258
+ static options(...values: Array<Answerable<string[] | string>>): SelectBuilder {
259
+ return {
260
+ from: (pageElement: Answerable<PageElement>): Interaction =>
261
+ Interaction.where(`#actor selects ${ commaSeparated(values.flat(), item => inspected(item, { inline: true })) } from ${ inspected(pageElement, { inline: true }) }`, async actor => {
262
+
263
+ const desiredOptions = (await Promise.all(values.map(value => actor.answer(value)))).flat(); // eslint-disable-line unicorn/no-await-expression-member
264
+
265
+ const options: PageElements = await PageElements.locatedByCss(`option`).of(pageElement).answeredBy(actor);
266
+ const shouldSelect: boolean[] = await options.map(optionsToSelect(hasTextEqualOneOf(desiredOptions)));
267
+
268
+ return options.forEach((option, index) => {
269
+ if (shouldSelect[index]) {
270
+ return option.click()
271
+ }
272
+ });
273
+ }),
274
+ };
275
+ }
276
+ }
277
+
278
+ /** @package */
279
+ function hasValueEqualOneOf(desiredValues: string[]): (option: PageElement) => Promise<boolean> {
280
+ return async (option: PageElement) => {
281
+
282
+ const value = await option.value()
283
+
284
+ return desiredValues.includes(value);
285
+ }
286
+ }
287
+
288
+ /** @package */
289
+ function hasTextEqualOneOf(desiredValues: string[]): (option: PageElement) => Promise<boolean> {
290
+ return async (option: PageElement) => {
291
+
292
+ const value = await option.text()
293
+
294
+ return desiredValues.includes(value);
295
+ }
296
+ }
297
+
298
+ /** @package */
299
+ function optionsToSelect(criterion: (option: PageElement) => Promise<boolean>) {
300
+ return (option: PageElement) =>
301
+ isAlreadySelected(option)
302
+ .then(alreadySelected =>
303
+ criterion(option).then(criterionMet =>
304
+ xor(alreadySelected, criterionMet)
305
+ )
306
+ );
307
+ }
308
+
309
+ /** @package */
310
+ function isAlreadySelected(option: PageElement): Promise<boolean> {
311
+ return option.isSelected();
312
+ }
313
+
314
+ /** @package */
315
+ function xor(first: boolean, second: boolean): boolean {
316
+ return first !== second;
317
+ }
@@ -0,0 +1,36 @@
1
+ import { Answerable } from '@serenity-js/core';
2
+ import { Interaction } from '@serenity-js/core/lib/screenplay';
3
+
4
+ import { PageElement } from '../models';
5
+
6
+ /**
7
+ * @desc
8
+ * Configures the {@link Target} representing
9
+ * a [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
10
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor}
11
+ * to interact with.
12
+ *
13
+ * @interface
14
+ */
15
+ export interface SelectBuilder {
16
+
17
+ /**
18
+ * @desc
19
+ * Configures the {@link Target} representing
20
+ * a [HTML `<select>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
21
+ * for the {@link @serenity-js/core/lib/screenplay/actor~Actor}
22
+ * to interact with
23
+ *
24
+ * @param {Answerable<PageElement>} pageElement
25
+ *
26
+ * @returns {@serenity-js/core/lib/screenplay~Interaction}
27
+ *
28
+ * @see {@link Select}
29
+ * @see {@link Select.option}
30
+ * @see {@link Select.options}
31
+ * @see {@link Select.value}
32
+ * @see {@link Select.values}
33
+ * @see {@link Target}
34
+ */
35
+ from: (pageElement: Answerable<PageElement>) => Interaction;
36
+ }
@@ -0,0 +1,225 @@
1
+ import { Activity, Answerable, AnswersQuestions, Interaction, PerformsActivities, Task, UsesAbilities } from '@serenity-js/core';
2
+
3
+ import { BrowseTheWeb } from '../abilities';
4
+ import { PageElement } from '../models';
5
+
6
+ /**
7
+ * @desc
8
+ * Instructs the {@link @serenity-js/core/lib/screenplay/actor~Actor}
9
+ * to switch to a different [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame),
10
+ * [inline frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe),
11
+ * or browser window/tab.
12
+ *
13
+ * @example <caption>Lean Page Object describing a login form, embedded in an iframe</caption>
14
+ *
15
+ * import { Target } from '@serenity-js/protractor';
16
+ * import { by } from 'protractor';
17
+ *
18
+ * class LoginForm {
19
+ * static iframe = Target.the('login form').located(by.tagName('iframe'));
20
+ * static usernameField = Target.the('username field').located(by.css('[data-test="username"]'));
21
+ * static passwordField = Target.the('password field').located(by.css('[data-test="password"]'));
22
+ * static submitButton = Target.the('submit button').located(by.css(`button[type='submit']`));
23
+ * }
24
+ *
25
+ * @example <caption>Switch to an iframe and back</caption>
26
+ *
27
+ * import { actorCalled } from '@serenity-js/core';
28
+ * import { BrowseTheWeb, Switch, Enter, Click } from '@serenity-js/protractor';
29
+ * import { protractor } from 'protractor';
30
+ *
31
+ * actorCalled('Francesca')
32
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
33
+ * .attemptsTo(
34
+ * Switch.toFrame(LoginForm.iframe),
35
+ *
36
+ * Enter.theValue('francesca@example.org').into(LoginForm.usernameField),
37
+ * Enter.theValue('correct-horse-battery-staple').into(LoginForm.passwordField),
38
+ * Click.on(LoginForm.submitButton),
39
+ *
40
+ * Switch.toParentFrame(),
41
+ * );
42
+ *
43
+ * @example <caption>Perform activities in the context of an iframe</caption>
44
+ *
45
+ * import { actorCalled } from '@serenity-js/core';
46
+ * import { BrowseTheWeb, Switch, Enter, Click } from '@serenity-js/protractor';
47
+ * import { protractor } from 'protractor';
48
+ *
49
+ * actorCalled('Francesca')
50
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
51
+ * .attemptsTo(
52
+ * Switch.toFrame(LoginForm.iframe).and(
53
+ * Enter.theValue('francesca@example.org').into(LoginForm.usernameField),
54
+ * Enter.theValue('correct-horse-battery-staple').into(LoginForm.passwordField),
55
+ * Click.on(LoginForm.submitButton),
56
+ * ),
57
+ * // Note that Switch.toParentFrame() is invoked automatically
58
+ * );
59
+ *
60
+ * @example <caption>Switch to a new window/tab and back</caption>
61
+ *
62
+ * import { actorCalled } from '@serenity-js/core';
63
+ * import { BrowseTheWeb, Switch, Close } from '@serenity-js/protractor';
64
+ * import { protractor } from 'protractor';
65
+ *
66
+ * actorCalled('Francesca')
67
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
68
+ * .attemptsTo(
69
+ * Switch.toNewWindow(), // or: Switch.toWindow(...)
70
+ *
71
+ * // perform some activities in the context of the new window
72
+ *
73
+ * Close.currentWindow(),
74
+ *
75
+ * Switch.toOriginalWindow(),
76
+ * );
77
+ *
78
+ * @example <caption>Perform activities in the context of a different window/tab</caption>
79
+ *
80
+ * import { actorCalled } from '@serenity-js/core';
81
+ * import { BrowseTheWeb, Switch, Close } from '@serenity-js/protractor';
82
+ * import { protractor } from 'protractor';
83
+ *
84
+ * actorCalled('Francesca')
85
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
86
+ * .attemptsTo(
87
+ * Switch.toNewWindow().and(
88
+ * // perform some activities in the context of the new window
89
+ *
90
+ * Close.currentWindow()
91
+ * ),
92
+ *
93
+ * // Note that Switch.toOriginalWindow() is invoked automatically
94
+ * );
95
+ *
96
+ * @see {@link Close}
97
+ * @see {@link BrowseTheWeb}
98
+ */
99
+ export class Switch {
100
+
101
+ /**
102
+ * @desc
103
+ * Switches the current [browsing context](https://w3c.github.io/webdriver/#dfn-current-browsing-context)
104
+ * for future commands to a [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame)
105
+ * or an [inline frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
106
+ * identified by its name, index or `Question<ElementFinder>`.
107
+ *
108
+ * @param {Answerable<ElementFinder | number | string>} targetOrIndex
109
+ *
110
+ * @returns {SwitchToFrame}
111
+ *
112
+ * @see {@link Switch.toParentFrame}
113
+ * @see {@link Switch.toDefaultContent}
114
+ * @see {@link Target}
115
+ */
116
+ static toFrame(targetOrIndex: Answerable<PageElement | number | string>): SwitchToFrame {
117
+ return new SwitchToFrame(targetOrIndex);
118
+ }
119
+
120
+ /**
121
+ * @desc
122
+ * Sets the current [browsing context](https://w3c.github.io/webdriver/#dfn-current-browsing-context)
123
+ * for future commands to the parent of the current browsing context,
124
+ * i.e. an `iframe` in which the current `iframe` is nested.
125
+ *
126
+ * If the current context is the top-level browsing context, the context remains unchanged.
127
+ *
128
+ * @returns {@serenity-js/core/lib/screenplay~Interaction}
129
+ *
130
+ * @see {@link Switch.toFrame}
131
+ * @see https://w3c.github.io/webdriver/#switch-to-parent-frame
132
+ * @see https://w3c.github.io/webdriver/#dfn-current-browsing-context
133
+ */
134
+ static toParentFrame(): Interaction {
135
+ return new SwitchToParentFrame();
136
+ }
137
+
138
+ /**
139
+ * @desc
140
+ * Switches the current [browsing context](https://w3c.github.io/webdriver/#dfn-current-browsing-context)
141
+ * for future commands to the first frame on the page, or the main document
142
+ * when a page contains [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)s.
143
+ *
144
+ * @returns {@serenity-js/core/lib/screenplay~Interaction}
145
+ *
146
+ * @see {@link Switch.toFrame}
147
+ */
148
+ static toDefaultContent(): Interaction {
149
+ return new SwitchToDefaultContent();
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @package
155
+ */
156
+ class SwitchToFrame extends Interaction {
157
+ constructor(private readonly targetOrIndex: Answerable<PageElement | number | string>) {
158
+ super();
159
+ }
160
+
161
+ and(...activities: Activity[]): Task {
162
+ return new SwitchToFrameAndPerformActivities(this.targetOrIndex, activities);
163
+ }
164
+
165
+ performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void> {
166
+ return actor.answer(this.targetOrIndex)
167
+ .then((targetOrIndex: PageElement | number | string) => {
168
+ return BrowseTheWeb.as(actor).switchToFrame(targetOrIndex);
169
+ });
170
+ }
171
+
172
+ toString(): string {
173
+ return `#actor switches to frame: ${ this.targetOrIndex }`;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @package
179
+ */
180
+ class SwitchToFrameAndPerformActivities extends Task {
181
+ constructor(
182
+ private readonly targetOrIndex: Answerable<PageElement | number | string>,
183
+ private readonly activities: Activity[]
184
+ ) {
185
+ super();
186
+ }
187
+
188
+ performAs(actor: PerformsActivities): PromiseLike<void> {
189
+ return actor.attemptsTo(
190
+ new SwitchToFrame(this.targetOrIndex),
191
+ ...this.activities,
192
+ new SwitchToParentFrame()
193
+ )
194
+ }
195
+
196
+ toString(): string {
197
+ return `#actor switches to frame: ${ this.targetOrIndex }`;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @package
203
+ */
204
+ class SwitchToParentFrame extends Interaction {
205
+ performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void> {
206
+ return BrowseTheWeb.as(actor).switchToParentFrame();
207
+ }
208
+
209
+ toString(): string {
210
+ return `#actor switches to parent frame`;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @package
216
+ */
217
+ class SwitchToDefaultContent extends Interaction {
218
+ performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void> {
219
+ return BrowseTheWeb.as(actor).switchToDefaultContent();
220
+ }
221
+
222
+ toString(): string {
223
+ return `#actor switches to default content`;
224
+ }
225
+ }
@@ -0,0 +1,89 @@
1
+ import { Answerable, AnswersQuestions, CollectsArtifacts, Interaction, UsesAbilities } from '@serenity-js/core';
2
+ import { formatted } from '@serenity-js/core/lib/io';
3
+ import { Name, Photo } from '@serenity-js/core/lib/model';
4
+
5
+ import { BrowseTheWeb } from '../abilities';
6
+
7
+ /**
8
+ * @desc
9
+ * Instructs the {@link @serenity-js/core/lib/screenplay/actor~Actor} to
10
+ * take a screenshot and emit an {@link @serenity-js/core/lib/model~Artifact},
11
+ * which can then be persisted by {@link @serenity-js/core/lib/stage/crew/artifact-archiver~ArtifactArchiver}
12
+ * and reported by [Serenity BDD reporter](/modules/serenity-bdd).
13
+ *
14
+ * @example <caption>Clicking on an element</caption>
15
+ * import { actorCalled } from '@serenity-js/core';
16
+ * import { BrowseTheWeb, Navigate, TakeScreenshot } from '@serenity-js/protractor';
17
+ * import { protractor } from 'protractor';
18
+ *
19
+ * actorCalled('Tania')
20
+ * .whoCan(BrowseTheWeb.using(protractor.browser))
21
+ * .attemptsTo(
22
+ * Navigate.to('/app'),
23
+ * TakeScreenshot.of('my app'),
24
+ * );
25
+ *
26
+ * @see {@link BrowseTheWeb}
27
+ * @see {@link @serenity-js/core/lib/screenplay/actor~CollectsArtifacts}
28
+ * @see {@link @serenity-js/core/lib/model~Artifact}
29
+ * @see {@link @serenity-js/core/lib/stage/crew/artifact-archiver~ArtifactArchiver}
30
+ *
31
+ * @extends {@serenity-js/core/lib/screenplay~Interaction}
32
+ */
33
+ export class TakeScreenshot extends Interaction {
34
+
35
+ /**
36
+ * @desc
37
+ * Instantiates this {@link @serenity-js/core/lib/screenplay~Interaction}.
38
+ *
39
+ * @param {Answerable<string>} name
40
+ * The name to associate the screenshot with
41
+ *
42
+ * @returns {@serenity-js/core/lib/screenplay~Interaction}
43
+ */
44
+ static of(name: Answerable<string>): Interaction {
45
+ return new TakeScreenshot(name);
46
+ }
47
+
48
+ /**
49
+ * @param {Answerable<string>} name
50
+ * The name to associate the screenshot with
51
+ */
52
+ constructor(private readonly name: Answerable<string>) {
53
+ super();
54
+ }
55
+
56
+ /**
57
+ * @desc
58
+ * Makes the provided {@link @serenity-js/core/lib/screenplay/actor~Actor}
59
+ * perform this {@link @serenity-js/core/lib/screenplay~Interaction}.
60
+ *
61
+ * @param {UsesAbilities & AnswersQuestions} actor
62
+ * An {@link @serenity-js/core/lib/screenplay/actor~Actor} to perform this {@link @serenity-js/core/lib/screenplay~Interaction}
63
+ *
64
+ * @returns {PromiseLike<void>}
65
+ *
66
+ * @see {@link @serenity-js/core/lib/screenplay/actor~Actor}
67
+ * @see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities}
68
+ * @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
69
+ */
70
+ performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): PromiseLike<void> {
71
+ return Promise.all([
72
+ BrowseTheWeb.as(actor).takeScreenshot(),
73
+ actor.answer(this.name),
74
+ ]).then(([ screenshot, name ]) => actor.collect(
75
+ Photo.fromBase64(screenshot),
76
+ new Name(name),
77
+ ));
78
+ }
79
+
80
+ /**
81
+ * @desc
82
+ * Generates a description to be used when reporting this {@link @serenity-js/core/lib/screenplay~Activity}.
83
+ *
84
+ * @returns {string}
85
+ */
86
+ toString(): string {
87
+ return formatted `#actor takes a screenshot of ${this.name}`;
88
+ }
89
+ }