@serenity-js/protractor 3.0.0-rc.9 → 3.0.1

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 (169) hide show
  1. package/CHANGELOG.md +144 -2236
  2. package/README.md +39 -26
  3. package/lib/adapter/Config.d.ts +11 -9
  4. package/lib/adapter/Config.d.ts.map +1 -0
  5. package/lib/adapter/ProtractorFrameworkAdapter.d.ts +2 -3
  6. package/lib/adapter/ProtractorFrameworkAdapter.d.ts.map +1 -0
  7. package/lib/adapter/ProtractorFrameworkAdapter.js +11 -10
  8. package/lib/adapter/ProtractorFrameworkAdapter.js.map +1 -1
  9. package/lib/adapter/browser-detector/BrowserDetector.d.ts +1 -0
  10. package/lib/adapter/browser-detector/BrowserDetector.d.ts.map +1 -0
  11. package/lib/adapter/browser-detector/BrowserDetector.js +5 -5
  12. package/lib/adapter/browser-detector/BrowserDetector.js.map +1 -1
  13. package/lib/adapter/browser-detector/StandardisedCapabilities.d.ts +5 -4
  14. package/lib/adapter/browser-detector/StandardisedCapabilities.d.ts.map +1 -0
  15. package/lib/adapter/browser-detector/StandardisedCapabilities.js +17 -17
  16. package/lib/adapter/browser-detector/StandardisedCapabilities.js.map +1 -1
  17. package/lib/adapter/browser-detector/index.d.ts +1 -0
  18. package/lib/adapter/browser-detector/index.d.ts.map +1 -0
  19. package/lib/adapter/browser-detector/index.js +5 -1
  20. package/lib/adapter/browser-detector/index.js.map +1 -1
  21. package/lib/adapter/index.d.ts +2 -0
  22. package/lib/adapter/index.d.ts.map +1 -0
  23. package/lib/adapter/index.js +6 -1
  24. package/lib/adapter/index.js.map +1 -1
  25. package/lib/adapter/reporter/ProtractorReport.d.ts +5 -6
  26. package/lib/adapter/reporter/ProtractorReport.d.ts.map +1 -0
  27. package/lib/adapter/reporter/ProtractorReporter.d.ts +1 -0
  28. package/lib/adapter/reporter/ProtractorReporter.d.ts.map +1 -0
  29. package/lib/adapter/reporter/ProtractorReporter.js +10 -5
  30. package/lib/adapter/reporter/ProtractorReporter.js.map +1 -1
  31. package/lib/adapter/reporter/index.d.ts +1 -0
  32. package/lib/adapter/reporter/index.d.ts.map +1 -0
  33. package/lib/adapter/reporter/index.js +5 -1
  34. package/lib/adapter/reporter/index.js.map +1 -1
  35. package/lib/adapter/run.d.ts +7 -4
  36. package/lib/adapter/run.d.ts.map +1 -0
  37. package/lib/adapter/run.js +6 -4
  38. package/lib/adapter/run.js.map +1 -1
  39. package/lib/adapter/runner/CucumberAdapterConfig.d.ts +5 -10
  40. package/lib/adapter/runner/CucumberAdapterConfig.d.ts.map +1 -0
  41. package/lib/adapter/runner/TestRunnerDetector.d.ts +5 -12
  42. package/lib/adapter/runner/TestRunnerDetector.d.ts.map +1 -0
  43. package/lib/adapter/runner/TestRunnerDetector.js +7 -15
  44. package/lib/adapter/runner/TestRunnerDetector.js.map +1 -1
  45. package/lib/adapter/runner/TestRunnerLoader.d.ts +20 -17
  46. package/lib/adapter/runner/TestRunnerLoader.d.ts.map +1 -0
  47. package/lib/adapter/runner/TestRunnerLoader.js +18 -18
  48. package/lib/adapter/runner/TestRunnerLoader.js.map +1 -1
  49. package/lib/adapter/runner/index.d.ts +1 -0
  50. package/lib/adapter/runner/index.d.ts.map +1 -0
  51. package/lib/adapter/runner/index.js +5 -1
  52. package/lib/adapter/runner/index.js.map +1 -1
  53. package/lib/index.d.ts +1 -0
  54. package/lib/index.d.ts.map +1 -0
  55. package/lib/index.js +5 -1
  56. package/lib/index.js.map +1 -1
  57. package/lib/screenplay/abilities/BrowseTheWebWithProtractor.d.ts +62 -300
  58. package/lib/screenplay/abilities/BrowseTheWebWithProtractor.d.ts.map +1 -0
  59. package/lib/screenplay/abilities/BrowseTheWebWithProtractor.js +62 -422
  60. package/lib/screenplay/abilities/BrowseTheWebWithProtractor.js.map +1 -1
  61. package/lib/screenplay/abilities/index.d.ts +1 -0
  62. package/lib/screenplay/abilities/index.d.ts.map +1 -0
  63. package/lib/screenplay/abilities/index.js +5 -1
  64. package/lib/screenplay/abilities/index.js.map +1 -1
  65. package/lib/screenplay/index.d.ts +1 -0
  66. package/lib/screenplay/index.d.ts.map +1 -0
  67. package/lib/screenplay/index.js +5 -1
  68. package/lib/screenplay/index.js.map +1 -1
  69. package/lib/screenplay/interactions/UseAngular.d.ts +53 -48
  70. package/lib/screenplay/interactions/UseAngular.d.ts.map +1 -0
  71. package/lib/screenplay/interactions/UseAngular.js +52 -42
  72. package/lib/screenplay/interactions/UseAngular.js.map +1 -1
  73. package/lib/screenplay/interactions/index.d.ts +1 -0
  74. package/lib/screenplay/interactions/index.d.ts.map +1 -0
  75. package/lib/screenplay/interactions/index.js +5 -1
  76. package/lib/screenplay/interactions/index.js.map +1 -1
  77. package/lib/screenplay/models/ProtractorBrowsingSession.d.ts +22 -0
  78. package/lib/screenplay/models/ProtractorBrowsingSession.d.ts.map +1 -0
  79. package/lib/screenplay/models/ProtractorBrowsingSession.js +99 -0
  80. package/lib/screenplay/models/ProtractorBrowsingSession.js.map +1 -0
  81. package/lib/screenplay/models/ProtractorCookie.d.ts +6 -0
  82. package/lib/screenplay/models/ProtractorCookie.d.ts.map +1 -0
  83. package/lib/screenplay/models/ProtractorCookie.js +8 -3
  84. package/lib/screenplay/models/ProtractorCookie.js.map +1 -1
  85. package/lib/screenplay/models/ProtractorErrorHandler.d.ts +8 -0
  86. package/lib/screenplay/models/ProtractorErrorHandler.d.ts.map +1 -0
  87. package/lib/screenplay/models/ProtractorErrorHandler.js +23 -0
  88. package/lib/screenplay/models/ProtractorErrorHandler.js.map +1 -0
  89. package/lib/screenplay/models/ProtractorModalDialogHandler.d.ts +25 -0
  90. package/lib/screenplay/models/ProtractorModalDialogHandler.d.ts.map +1 -0
  91. package/lib/screenplay/models/ProtractorModalDialogHandler.js +74 -0
  92. package/lib/screenplay/models/ProtractorModalDialogHandler.js.map +1 -0
  93. package/lib/screenplay/models/ProtractorPage.d.ts +58 -6
  94. package/lib/screenplay/models/ProtractorPage.d.ts.map +1 -0
  95. package/lib/screenplay/models/ProtractorPage.js +221 -44
  96. package/lib/screenplay/models/ProtractorPage.js.map +1 -1
  97. package/lib/screenplay/models/ProtractorPageElement.d.ts +10 -1
  98. package/lib/screenplay/models/ProtractorPageElement.d.ts.map +1 -0
  99. package/lib/screenplay/models/ProtractorPageElement.js +141 -147
  100. package/lib/screenplay/models/ProtractorPageElement.js.map +1 -1
  101. package/lib/screenplay/models/index.d.ts +2 -1
  102. package/lib/screenplay/models/index.d.ts.map +1 -0
  103. package/lib/screenplay/models/index.js +6 -2
  104. package/lib/screenplay/models/index.js.map +1 -1
  105. package/lib/screenplay/models/locators/ProtractorLocator.d.ts +21 -8
  106. package/lib/screenplay/models/locators/ProtractorLocator.d.ts.map +1 -0
  107. package/lib/screenplay/models/locators/ProtractorLocator.js +107 -10
  108. package/lib/screenplay/models/locators/ProtractorLocator.js.map +1 -1
  109. package/lib/screenplay/models/locators/ProtractorRootLocator.d.ts +17 -0
  110. package/lib/screenplay/models/locators/ProtractorRootLocator.d.ts.map +1 -0
  111. package/lib/screenplay/models/locators/ProtractorRootLocator.js +38 -0
  112. package/lib/screenplay/models/locators/ProtractorRootLocator.js.map +1 -0
  113. package/lib/screenplay/models/locators/index.d.ts +2 -1
  114. package/lib/screenplay/models/locators/index.d.ts.map +1 -0
  115. package/lib/screenplay/models/locators/index.js +6 -2
  116. package/lib/screenplay/models/locators/index.js.map +1 -1
  117. package/lib/screenplay/promised.d.ts +3 -3
  118. package/lib/screenplay/promised.d.ts.map +1 -0
  119. package/lib/screenplay/promised.js +2 -3
  120. package/lib/screenplay/promised.js.map +1 -1
  121. package/lib/screenplay/questions/ProtractorParam.d.ts +64 -51
  122. package/lib/screenplay/questions/ProtractorParam.d.ts.map +1 -0
  123. package/lib/screenplay/questions/ProtractorParam.js +59 -47
  124. package/lib/screenplay/questions/ProtractorParam.js.map +1 -1
  125. package/lib/screenplay/questions/index.d.ts +1 -0
  126. package/lib/screenplay/questions/index.d.ts.map +1 -0
  127. package/lib/screenplay/questions/index.js +5 -1
  128. package/lib/screenplay/questions/index.js.map +1 -1
  129. package/lib/screenplay/unpromisedWebElement.d.ts +6 -4
  130. package/lib/screenplay/unpromisedWebElement.d.ts.map +1 -0
  131. package/lib/screenplay/unpromisedWebElement.js +5 -4
  132. package/lib/screenplay/unpromisedWebElement.js.map +1 -1
  133. package/package.json +36 -60
  134. package/src/adapter/Config.ts +10 -9
  135. package/src/adapter/ProtractorFrameworkAdapter.ts +16 -10
  136. package/src/adapter/browser-detector/BrowserDetector.ts +3 -3
  137. package/src/adapter/browser-detector/StandardisedCapabilities.ts +24 -24
  138. package/src/adapter/index.ts +1 -0
  139. package/src/adapter/reporter/ProtractorReport.ts +4 -6
  140. package/src/adapter/reporter/ProtractorReporter.ts +16 -16
  141. package/src/adapter/run.ts +6 -4
  142. package/src/adapter/runner/CucumberAdapterConfig.ts +5 -10
  143. package/src/adapter/runner/TestRunnerDetector.ts +5 -13
  144. package/src/adapter/runner/TestRunnerLoader.ts +21 -19
  145. package/src/screenplay/abilities/BrowseTheWebWithProtractor.ts +63 -480
  146. package/src/screenplay/interactions/UseAngular.ts +55 -44
  147. package/src/screenplay/models/ProtractorBrowsingSession.ts +133 -0
  148. package/src/screenplay/models/ProtractorCookie.ts +8 -3
  149. package/src/screenplay/models/ProtractorErrorHandler.ts +23 -0
  150. package/src/screenplay/models/ProtractorModalDialogHandler.ts +97 -0
  151. package/src/screenplay/models/ProtractorPage.ts +282 -54
  152. package/src/screenplay/models/ProtractorPageElement.ts +160 -165
  153. package/src/screenplay/models/index.ts +1 -1
  154. package/src/screenplay/models/locators/ProtractorLocator.ts +121 -31
  155. package/src/screenplay/models/locators/ProtractorRootLocator.ts +41 -0
  156. package/src/screenplay/models/locators/index.ts +1 -1
  157. package/src/screenplay/promised.ts +2 -3
  158. package/src/screenplay/questions/ProtractorParam.ts +62 -49
  159. package/src/screenplay/unpromisedWebElement.ts +5 -4
  160. package/tsconfig.build.json +14 -0
  161. package/lib/screenplay/models/ProtractorModalDialog.d.ts +0 -11
  162. package/lib/screenplay/models/ProtractorModalDialog.js +0 -43
  163. package/lib/screenplay/models/ProtractorModalDialog.js.map +0 -1
  164. package/lib/screenplay/models/locators/ProtractorNativeElementRoot.d.ts +0 -5
  165. package/lib/screenplay/models/locators/ProtractorNativeElementRoot.js +0 -3
  166. package/lib/screenplay/models/locators/ProtractorNativeElementRoot.js.map +0 -1
  167. package/src/screenplay/models/ProtractorModalDialog.ts +0 -49
  168. package/src/screenplay/models/locators/ProtractorNativeElementRoot.ts +0 -6
  169. package/tsconfig.eslint.json +0 -10
@@ -1,71 +1,96 @@
1
- import { PageElement } from '@serenity-js/web';
2
- import { ElementFinder, protractor } from 'protractor';
3
- import { WebElement } from 'selenium-webdriver';
1
+ import { LogicError } from '@serenity-js/core';
2
+ import { PageElement, SelectOption, SwitchableOrigin } from '@serenity-js/web';
3
+ import * as scripts from '@serenity-js/web/lib/scripts';
4
+ import { by, ElementFinder, protractor } from 'protractor';
5
+ import { Locator, WebElement } from 'selenium-webdriver';
4
6
 
5
7
  import { promised } from '../promised';
8
+ import { ProtractorLocator } from './locators';
9
+
10
+ /**
11
+ * Protractor-specific implementation of {@apilink PageElement}.
12
+ *
13
+ * @group Models
14
+ */
15
+ export class ProtractorPageElement extends PageElement<ElementFinder> {
6
16
 
7
- export class ProtractorPageElement
8
- extends PageElement<ElementFinder>
9
- {
10
17
  of(parent: ProtractorPageElement): PageElement<ElementFinder> {
11
18
  return new ProtractorPageElement(this.locator.of(parent.locator));
12
19
  }
13
20
 
14
21
  async clearValue(): Promise<void> {
15
- function removeCharactersFrom(elf: ElementFinder, numberOfCharacters: number): PromiseLike<void> {
16
- return numberOfCharacters === 0
17
- ? Promise.resolve(void 0)
18
- : elf.sendKeys(
19
- protractor.Key.END,
20
- ...times(numberOfCharacters, protractor.Key.BACK_SPACE),
21
- );
22
- }
23
-
24
22
  // eslint-disable-next-line unicorn/consistent-function-scoping
25
23
  function times(length: number, key: string) {
26
24
  return Array.from({ length }).map(() => key);
27
25
  }
28
26
 
29
- const currentValue = await this.value();
27
+ // eslint-disable-next-line unicorn/consistent-function-scoping
28
+ async function focusOn(element: ElementFinder) {
29
+ const webElement = await element.getWebElement();
30
+ await promised(webElement.getDriver().executeScript(`arguments[0].focus()`, webElement));
31
+ }
30
32
 
31
- if (currentValue !== null && currentValue !== undefined) {
32
- const element = await this.nativeElement();
33
- return removeCharactersFrom(element, currentValue.length);
33
+ // eslint-disable-next-line unicorn/consistent-function-scoping
34
+ async function removeCharactersFrom(elf: ElementFinder, numberOfCharacters: number): Promise<void> {
35
+ if (numberOfCharacters > 0) {
36
+ await focusOn(elf);
37
+ await elf.sendKeys(
38
+ protractor.Key.HOME,
39
+ ...times(numberOfCharacters, protractor.Key.DELETE),
40
+ );
41
+ }
42
+ }
43
+
44
+ const value = await this.value();
45
+ const hasValue = value !== null && value !== undefined;
46
+
47
+ const element = await this.nativeElement();
48
+
49
+ if (hasValue) {
50
+ return removeCharactersFrom(element, value.length);
51
+ }
52
+
53
+ const contentEditable = await promised(element.getAttribute('contenteditable'));
54
+ const hasContentEditable = contentEditable !== null && contentEditable !== undefined && contentEditable !== 'false';
55
+
56
+ if (hasContentEditable) {
57
+ const text = await this.text();
58
+ return removeCharactersFrom(element, text.length);
34
59
  }
35
60
  }
36
61
 
37
62
  async click(): Promise<void> {
38
63
  const element: ElementFinder = await this.nativeElement();
39
64
 
40
- return element.click();
65
+ await promised(element.click());
41
66
  }
42
67
 
43
68
  async doubleClick(): Promise<void> {
44
69
  const element: ElementFinder = await this.nativeElement();
45
70
  const webElement: WebElement = await element.getWebElement();
46
71
 
47
- return promised(
72
+ await promised(
48
73
  webElement.getDriver().actions()
49
74
  .mouseMove(webElement)
50
75
  .doubleClick()
51
- .perform()
76
+ .perform(),
52
77
  );
53
78
  }
54
79
 
55
80
  async enterValue(value: string | number | Array<string | number>): Promise<void> {
56
81
  const element: ElementFinder = await this.nativeElement();
57
82
 
58
- return element.sendKeys(
59
- [].concat(value).join('')
60
- );
83
+ await promised(element.sendKeys(
84
+ [].concat(value).join(''),
85
+ ));
61
86
  }
62
87
 
63
88
  async scrollIntoView(): Promise<void> {
64
89
  const element: ElementFinder = await this.nativeElement();
65
90
  const webElement: WebElement = await element.getWebElement();
66
91
 
67
- return promised(
68
- webElement.getDriver().executeScript('arguments[0].scrollIntoView(true);', webElement)
92
+ await promised(
93
+ webElement.getDriver().executeScript('arguments[0].scrollIntoView(true);', webElement),
69
94
  );
70
95
  }
71
96
 
@@ -73,10 +98,10 @@ export class ProtractorPageElement
73
98
  const element: ElementFinder = await this.nativeElement();
74
99
  const webElement: WebElement = await element.getWebElement();
75
100
 
76
- return promised(
101
+ await promised(
77
102
  webElement.getDriver().actions()
78
103
  .mouseMove(webElement)
79
- .perform()
104
+ .perform(),
80
105
  );
81
106
  }
82
107
 
@@ -84,31 +109,74 @@ export class ProtractorPageElement
84
109
  const element: ElementFinder = await this.nativeElement();
85
110
  const webElement: WebElement = await element.getWebElement();
86
111
 
87
- return promised(
112
+ await promised(
88
113
  webElement.getDriver().actions()
89
114
  .mouseMove(webElement)
90
115
  .click(protractor.Button.RIGHT)
91
- .perform()
116
+ .perform(),
117
+ );
118
+ }
119
+
120
+ async selectOptions(...options: SelectOption[]): Promise<void> {
121
+ const element: ElementFinder = await this.nativeElement();
122
+
123
+ for (const option of options) {
124
+ if (option.value) {
125
+ await promised(element.element(by.xpath(`//option[@value='${ option.value }']`) as Locator).click());
126
+ }
127
+ else if (option.label) {
128
+ await promised(element.element(by.cssContainingText('option', option.label) as Locator).click());
129
+ }
130
+ }
131
+ }
132
+
133
+ async selectedOptions(): Promise<SelectOption[]> {
134
+ const element: ElementFinder = await this.locator.nativeElement();
135
+
136
+ const webElement = await element.getWebElement();
137
+
138
+ const browser = element.browser_;
139
+
140
+ const options: Array<{ label: string, value: string, selected: boolean, disabled: boolean }> = await browser.executeScript(
141
+ /* istanbul ignore next */
142
+ (select: HTMLSelectElement) => {
143
+ const options = [];
144
+ select.querySelectorAll('option').forEach((option: HTMLOptionElement) => {
145
+ options.push({
146
+ selected: option.selected,
147
+ disabled: option.disabled,
148
+ label: option.label,
149
+ value: option.value,
150
+ });
151
+ });
152
+
153
+ return options;
154
+ },
155
+ webElement as unknown
156
+ );
157
+
158
+ return options.map(option =>
159
+ new SelectOption(option.label, option.value, option.selected, option.disabled)
92
160
  );
93
161
  }
94
162
 
95
163
  async attribute(name: string): Promise<string> {
96
164
  const element: ElementFinder = await this.nativeElement();
97
165
 
98
- return element.getAttribute(name);
166
+ return await promised(element.getAttribute(name));
99
167
  }
100
168
 
101
169
  async text(): Promise<string> {
102
170
  const element: ElementFinder = await this.nativeElement();
103
171
 
104
- return element.getText();
172
+ return await promised(element.getText());
105
173
  }
106
174
 
107
175
  async value(): Promise<string> {
108
176
  const element: ElementFinder = await this.nativeElement();
109
177
  const webElement: WebElement = await element.getWebElement();
110
178
 
111
- return promised(webElement.getDriver().executeScript(
179
+ return await promised(webElement.getDriver().executeScript(
112
180
  /* istanbul ignore next */
113
181
  function getValue(webElement) {
114
182
  return webElement.value;
@@ -117,169 +185,96 @@ export class ProtractorPageElement
117
185
  ));
118
186
  }
119
187
 
188
+ async switchTo(): Promise<SwitchableOrigin> {
189
+ const element: ElementFinder = await this.locator.nativeElement();
190
+
191
+ try {
192
+ const tagName = await element.getTagName();
193
+
194
+ if ([ 'iframe', 'frame' ].includes(tagName)) {
195
+ const locator = (this.locator as ProtractorLocator);
196
+ await locator.switchToFrame(element);
197
+
198
+ return {
199
+ switchBack: async (): Promise<void> => {
200
+ await locator.switchToParentFrame();
201
+ },
202
+ };
203
+ }
204
+ else {
205
+ // https://github.com/angular/protractor/issues/1846#issuecomment-82634739;
206
+ const webElement = await element.getWebElement();
207
+
208
+ // focus on element
209
+ const previouslyFocusedElement = await promised(webElement.getDriver().switchTo().activeElement());
210
+
211
+ await promised(webElement.getDriver().executeScript(`arguments[0].focus()`, webElement));
212
+
213
+ return {
214
+ switchBack: async (): Promise<void> => {
215
+ await promised(webElement.getDriver().executeScript(`arguments[0].focus()`, previouslyFocusedElement));
216
+ },
217
+ };
218
+ }
219
+ } catch (error) {
220
+ throw new LogicError(`Couldn't switch to page element located ${ this.locator }`, error);
221
+ }
222
+ }
223
+
120
224
  async isActive(): Promise<boolean> {
121
225
  const element: ElementFinder = await this.nativeElement();
122
226
  const webElement: WebElement = await element.getWebElement();
123
227
 
124
- return webElement.getDriver().switchTo().activeElement().then((active: WebElement) =>
228
+ return await promised(webElement.getDriver().switchTo().activeElement().then((active: WebElement) =>
125
229
  element.equals(active),
126
- );
230
+ ));
127
231
  }
128
232
 
129
233
  async isClickable(): Promise<boolean> {
130
- return this.isEnabled();
234
+ return await this.isEnabled();
131
235
  }
132
236
 
133
237
  async isEnabled(): Promise<boolean> {
134
238
  const element: ElementFinder = await this.nativeElement();
135
239
 
136
- return element.isEnabled();
240
+ return await promised(element.isEnabled());
137
241
  }
138
242
 
139
243
  async isPresent(): Promise<boolean> {
140
244
  const element: ElementFinder = await this.nativeElement();
141
245
 
142
- return element.isPresent();
246
+ return await promised(element.isPresent());
143
247
  }
144
248
 
145
249
  async isSelected(): Promise<boolean> {
146
250
  const element: ElementFinder = await this.nativeElement();
147
251
 
148
- return element.isSelected();
252
+ return await promised(element.isSelected());
149
253
  }
150
254
 
151
255
  async isVisible(): Promise<boolean> {
152
- const element: ElementFinder = await this.nativeElement();
153
-
154
- if (! await element.isDisplayed()) {
155
- return false;
156
- }
256
+ try {
257
+ const element: ElementFinder = await this.nativeElement();
157
258
 
158
- const webElement: WebElement = await element.getWebElement();
159
-
160
- // get element at cx/cy and see if the element we found is our element, and therefore it's visible.
161
- return promised(webElement.getDriver().executeScript(
162
- `
163
- var element = arguments[0];
164
-
165
- if (!element.getBoundingClientRect || !element.scrollIntoView || !element.contains || !element.getClientRects || !document.elementFromPoint) {
166
- return false
167
- }
168
-
169
- // Edge before switching to Chromium
170
- const isOldEdge = !!window['StyleMedia']
171
- // returns true for Chrome and Firefox and false for Safari, Edge and IE
172
- const scrollIntoViewFullSupport = !(window.safari || isOldEdge)
173
-
174
- // get overlapping element
175
- function getOverlappingElement (elem, context) {
176
- context = context || document
177
- const elemDimension = elem.getBoundingClientRect()
178
- const x = elemDimension.left + (elem.clientWidth / 2)
179
- const y = elemDimension.top + (elem.clientHeight / 2)
180
- return context.elementFromPoint(x, y)
181
- }
182
-
183
- // get overlapping element rects (currently only the first)
184
- // applicable if element's text is multiline.
185
- function getOverlappingRects (elem, context) {
186
- context = context || document
187
- const elems = []
188
-
189
- const rects = elem.getClientRects()
190
- // webdriver clicks on center of the first element's rect (line of text), it might change in future
191
- const rect = rects[0]
192
- const x = rect.left + (rect.width / 2)
193
- const y = rect.top + (rect.height / 2)
194
- elems.push(context.elementFromPoint(x, y))
195
-
196
- return elems
259
+ if (! await element.isDisplayed()) {
260
+ return false;
197
261
  }
198
262
 
199
- // get overlapping elements
200
- function getOverlappingElements (elem, context) {
201
- return [getOverlappingElement(elem, context)].concat(getOverlappingRects(elem, context))
202
- }
203
-
204
- // is a node a descendant of a given node
205
- function nodeContains (elem, otherNode) {
206
- // Edge doesn't support neither Shadow Dom nor contains if ShadowRoot polyfill is used
207
- if (isOldEdge) {
208
- let tmpElement = otherNode
209
- while (tmpElement) {
210
- if (tmpElement === elem) {
211
- return true
212
- }
213
-
214
- tmpElement = tmpElement.parentNode
215
- // DocumentFragment / ShadowRoot polyfill like ShadyRoot
216
- if (tmpElement && tmpElement.nodeType === 11 && tmpElement.host) {
217
- tmpElement = tmpElement.host
218
- }
219
- }
220
- return false
221
- }
222
-
223
- return elem.contains(otherNode)
224
- }
263
+ const webElement: WebElement = await element.getWebElement();
225
264
 
226
- // is one of overlapping elements the \`elem\` or one of its child
227
- function isOverlappingElementMatch (elementsFromPoint, elem) {
228
- if (elementsFromPoint.some(function (elementFromPoint) {
229
- return elementFromPoint === elem || nodeContains(elem, elementFromPoint)
230
- })) {
231
- return true
232
- }
233
-
234
- // shadow root
235
- // filter unique elements with shadowRoot
236
- // @ts-ignore
237
- let elemsWithShadowRoot = [].concat(elementsFromPoint)
238
- elemsWithShadowRoot = elemsWithShadowRoot.filter(function (x) {
239
- return x && x.shadowRoot && x.shadowRoot.elementFromPoint
240
- })
241
-
242
- // getOverlappingElements of every element with shadowRoot
243
- let shadowElementsFromPoint = []
244
- for (let i = 0; i < elemsWithShadowRoot.length; ++i) {
245
- let shadowElement = elemsWithShadowRoot[i]
246
- shadowElementsFromPoint = shadowElementsFromPoint.concat(
247
- getOverlappingElements(elem, shadowElement.shadowRoot)
248
- )
249
- }
250
- // remove duplicates and parents
251
- // @ts-ignore
252
- shadowElementsFromPoint = [].concat(shadowElementsFromPoint)
253
- shadowElementsFromPoint = shadowElementsFromPoint.filter(function (x) {
254
- return !elementsFromPoint.includes(x)
255
- })
256
-
257
- if (shadowElementsFromPoint.length === 0) {
258
- return false
259
- }
260
-
261
- return isOverlappingElementMatch(shadowElementsFromPoint, elem)
262
- }
265
+ // get element at cx/cy and see if the element we found is our element, and therefore it's visible.
266
+ return await promised(webElement.getDriver().executeScript(
267
+ scripts.isVisible,
268
+ webElement,
269
+ ));
270
+ }
271
+ catch (error) {
263
272
 
264
- function isElementInViewport (elem) {
265
- if (!elem.getBoundingClientRect) {
266
- return false
267
- }
268
-
269
- const rect = elem.getBoundingClientRect()
270
-
271
- const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
272
- const windowWidth = (window.innerWidth || document.documentElement.clientWidth)
273
-
274
- const vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) > 0)
275
- const horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) > 0)
276
-
277
- return (vertInView && horInView)
273
+ if (error.name === 'NoSuchElementError') {
274
+ return false;
278
275
  }
279
276
 
280
- return isElementInViewport(element) && isOverlappingElementMatch(getOverlappingElements(element), element);
281
- `,
282
- webElement,
283
- ));
277
+ throw error;
278
+ }
284
279
  }
285
280
  }
@@ -1,5 +1,5 @@
1
+ export * from './ProtractorBrowsingSession';
1
2
  export * from './ProtractorCookie';
2
- export * from './ProtractorModalDialog';
3
3
  export * from './ProtractorPage';
4
4
  export * from './ProtractorPageElement';
5
5
 
@@ -1,50 +1,140 @@
1
- import { Locator, PageElement, Selector } from '@serenity-js/web';
2
- import { ElementFinder, Locator as ProtractorNativeLocator, ProtractorBrowser } from 'protractor';
1
+ import { f, LogicError } from '@serenity-js/core';
2
+ import { ByCss, ByCssContainingText, ByDeepCss, ById, ByTagName, ByXPath, Locator, PageElement, RootLocator, Selector } from '@serenity-js/web';
3
+ import * as protractor from 'protractor';
3
4
 
4
5
  import { unpromisedWebElement } from '../../unpromisedWebElement';
6
+ import { ProtractorErrorHandler } from '../ProtractorErrorHandler';
5
7
  import { ProtractorPageElement } from '../ProtractorPageElement';
6
- import { ProtractorNativeElementRoot } from './ProtractorNativeElementRoot';
8
+ import { ProtractorRootLocator } from './ProtractorRootLocator';
7
9
 
8
- export class ProtractorLocator<Selector_Type extends Selector> extends Locator<ElementFinder, ProtractorNativeElementRoot, Selector_Type> {
10
+ /**
11
+ * Protractor-specific implementation of {@apilink Locator}.
12
+ *
13
+ * @group Models
14
+ */
15
+ export class ProtractorLocator extends Locator<protractor.ElementFinder, protractor.Locator> {
9
16
 
10
- static createRootLocator<ST extends Selector>(browser: ProtractorBrowser, selector: ST, protractorBy: ProtractorNativeLocator): ProtractorLocator<ST> {
11
- const parentRoot: ProtractorNativeElementRoot = {
12
- element: browser.element.bind(browser),
13
- all: browser.element.all.bind(browser),
17
+ constructor(
18
+ parent: RootLocator<protractor.ElementFinder>,
19
+ selector: Selector,
20
+ private readonly errorHandler: ProtractorErrorHandler,
21
+ ) {
22
+ super(parent, selector);
23
+ }
24
+
25
+ // todo: refactor; replace with a map and some more generic lookup mechanism
26
+ protected nativeSelector(): protractor.Locator {
27
+ if (this.selector instanceof ByCss) {
28
+ return protractor.by.css(this.selector.value);
29
+ }
30
+
31
+ if (this.selector instanceof ByDeepCss) {
32
+ if (! protractor.by.shadowDomCss) {
33
+ throw new LogicError(`By.deepCss() requires query-selector-shadow-dom plugin, which Serenity/JS ProtractorFrameworkAdapter registers by default. If you're using Serenity/JS without ProtractorFrameworkAdapter, please register the plugin yourself.`)
34
+ }
35
+
36
+ return protractor.by.shadowDomCss(this.selector.value.replace('>>>', '').trim());
37
+ }
38
+
39
+ if (this.selector instanceof ByCssContainingText) {
40
+ return protractor.by.cssContainingText(this.selector.value, this.selector.text);
41
+ }
42
+
43
+ if (this.selector instanceof ById) {
44
+ return protractor.by.id(this.selector.value);
45
+ }
46
+
47
+ if (this.selector instanceof ByTagName) {
48
+ return protractor.by.tagName(this.selector.value);
49
+ }
50
+
51
+ if (this.selector instanceof ByXPath) {
52
+ return protractor.by.xpath(this.selector.value);
53
+ }
54
+
55
+ throw new LogicError(f `${ this.selector } is not supported by ${ this.constructor.name }`);
56
+ }
57
+
58
+ async isPresent(): Promise<boolean> {
59
+ try {
60
+ const element = await this.resolveNativeElement();
61
+ return Boolean(element);
62
+ }
63
+ catch {
64
+ return false;
14
65
  }
66
+ }
67
+
68
+ async nativeElement(): Promise<protractor.ElementFinder> {
69
+ try {
70
+ return await this.resolveNativeElement();
71
+ }
72
+ catch (error) {
73
+ return await this.errorHandler.executeIfHandled(error, () => this.resolveNativeElement());
74
+ }
75
+ }
76
+
77
+ private async resolveNativeElement(): Promise<protractor.ElementFinder> {
78
+ const parent = await this.parent.nativeElement();
79
+ const result = await unpromisedWebElement(parent.element(this.nativeSelector()));
80
+
81
+ // checks if the element can be interacted with; in particular, throws unexpected alert present if there is one
82
+ await result.isPresent();
83
+
84
+ return result;
85
+ }
15
86
 
16
- return new ProtractorLocator(
17
- () => parentRoot,
18
- selector,
19
- (root: ProtractorNativeElementRoot) => unpromisedWebElement(root.element(protractorBy)),
20
- async (root: ProtractorNativeElementRoot) => root.all(protractorBy)
21
- );
87
+ async allNativeElements(): Promise<Array<protractor.ElementFinder>> {
88
+ const parent = await this.parent.nativeElement();
89
+ return parent.all(this.nativeSelector()) as unknown as Array<protractor.ElementFinder>;
22
90
  }
23
91
 
24
- of(parent: ProtractorLocator<unknown>): Locator<ElementFinder, ProtractorNativeElementRoot, Selector_Type> {
25
- return new ProtractorLocator(
26
- () => parent.nativeElement(),
27
- this.selector,
28
- (parentRoot: ProtractorNativeElementRoot) => this.locateElement(parentRoot),
29
- (parentRoot: ProtractorNativeElementRoot) => this.locateAllElements(parentRoot),
30
- );
92
+ of(parent: ProtractorLocator): Locator<protractor.ElementFinder, protractor.Locator> {
93
+ return new ProtractorLocator(parent, this.selector, this.errorHandler);
31
94
  }
32
95
 
33
- element(): PageElement<ElementFinder> {
96
+ locate(child: ProtractorLocator): Locator<protractor.ElementFinder, any> {
97
+ return new ProtractorLocator(this, child.selector, this.errorHandler);
98
+ }
99
+
100
+ element(): PageElement<protractor.ElementFinder> {
34
101
  return new ProtractorPageElement(this);
35
102
  }
36
103
 
37
- async allElements(): Promise<Array<PageElement<ElementFinder>>> {
38
- const parentRoot = await this.parentRoot()
39
- const elements = await this.locateAllElements(parentRoot);
104
+ async allElements(): Promise<Array<PageElement<protractor.ElementFinder>>> {
105
+ const elements = await this.allNativeElements();
40
106
 
41
107
  return Promise.all(elements.map(childElement =>
42
- new ProtractorPageElement(new ProtractorLocator(
43
- this.parentRoot,
44
- this.selector,
45
- () => unpromisedWebElement(childElement),
46
- () => [ unpromisedWebElement(childElement) ],
47
- ))
108
+ new ProtractorPageElement(
109
+ new ExistingElementLocator(
110
+ this.parent as ProtractorRootLocator,
111
+ this.selector,
112
+ this.errorHandler,
113
+ unpromisedWebElement(childElement)
114
+ )
115
+ )
48
116
  ));
49
117
  }
50
118
  }
119
+
120
+ /**
121
+ * @private
122
+ */
123
+ class ExistingElementLocator extends ProtractorLocator {
124
+ constructor(
125
+ parent: ProtractorRootLocator,
126
+ selector: Selector,
127
+ errorHandler: ProtractorErrorHandler,
128
+ private readonly existingNativeElement: protractor.ElementFinder,
129
+ ) {
130
+ super(parent, selector, errorHandler);
131
+ }
132
+
133
+ async nativeElement(): Promise<protractor.ElementFinder> {
134
+ return this.existingNativeElement;
135
+ }
136
+
137
+ async allNativeElements(): Promise<Array<protractor.ElementFinder>> {
138
+ return [ this.existingNativeElement ];
139
+ }
140
+ }
@@ -0,0 +1,41 @@
1
+ import { RootLocator } from '@serenity-js/web';
2
+ import * as protractor from 'protractor';
3
+
4
+ import { promised } from '../../promised';
5
+
6
+ /**
7
+ * Protractor-specific implementation of {@apilink RootLocator}.
8
+ *
9
+ * @group Models
10
+ */
11
+ export class ProtractorRootLocator extends RootLocator<protractor.ElementFinder> {
12
+ constructor(private readonly browser: protractor.ProtractorBrowser) {
13
+ super();
14
+ }
15
+
16
+ async isPresent(): Promise<boolean> {
17
+ return true;
18
+ }
19
+
20
+ async nativeElement(): Promise<Pick<protractor.ElementFinder, 'element' | 'all'>> {
21
+ return {
22
+ element: this.browser.element.bind(this.browser),
23
+ all: this.browser.element.all.bind(this.browser),
24
+ };
25
+ }
26
+
27
+ async switchToFrame(element: protractor.ElementFinder): Promise<void> {
28
+ // https://github.com/angular/protractor/issues/1846#issuecomment-82634739;
29
+ const webElement = await element.getWebElement();
30
+
31
+ await promised(this.browser.switchTo().frame(webElement));
32
+ }
33
+
34
+ async switchToParentFrame(): Promise<void> {
35
+ await promised(this.browser.driver.switchToParentFrame());
36
+ }
37
+
38
+ async switchToMainFrame(): Promise<void> {
39
+ await promised(this.browser.driver.switchTo().defaultContent());
40
+ }
41
+ }
@@ -1,2 +1,2 @@
1
1
  export * from './ProtractorLocator';
2
- export * from './ProtractorNativeElementRoot';
2
+ export * from './ProtractorRootLocator';