@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.
- package/CHANGELOG.md +144 -2236
- package/README.md +39 -26
- package/lib/adapter/Config.d.ts +11 -9
- package/lib/adapter/Config.d.ts.map +1 -0
- package/lib/adapter/ProtractorFrameworkAdapter.d.ts +2 -3
- package/lib/adapter/ProtractorFrameworkAdapter.d.ts.map +1 -0
- package/lib/adapter/ProtractorFrameworkAdapter.js +11 -10
- package/lib/adapter/ProtractorFrameworkAdapter.js.map +1 -1
- package/lib/adapter/browser-detector/BrowserDetector.d.ts +1 -0
- package/lib/adapter/browser-detector/BrowserDetector.d.ts.map +1 -0
- package/lib/adapter/browser-detector/BrowserDetector.js +5 -5
- package/lib/adapter/browser-detector/BrowserDetector.js.map +1 -1
- package/lib/adapter/browser-detector/StandardisedCapabilities.d.ts +5 -4
- package/lib/adapter/browser-detector/StandardisedCapabilities.d.ts.map +1 -0
- package/lib/adapter/browser-detector/StandardisedCapabilities.js +17 -17
- package/lib/adapter/browser-detector/StandardisedCapabilities.js.map +1 -1
- package/lib/adapter/browser-detector/index.d.ts +1 -0
- package/lib/adapter/browser-detector/index.d.ts.map +1 -0
- package/lib/adapter/browser-detector/index.js +5 -1
- package/lib/adapter/browser-detector/index.js.map +1 -1
- package/lib/adapter/index.d.ts +2 -0
- package/lib/adapter/index.d.ts.map +1 -0
- package/lib/adapter/index.js +6 -1
- package/lib/adapter/index.js.map +1 -1
- package/lib/adapter/reporter/ProtractorReport.d.ts +5 -6
- package/lib/adapter/reporter/ProtractorReport.d.ts.map +1 -0
- package/lib/adapter/reporter/ProtractorReporter.d.ts +1 -0
- package/lib/adapter/reporter/ProtractorReporter.d.ts.map +1 -0
- package/lib/adapter/reporter/ProtractorReporter.js +10 -5
- package/lib/adapter/reporter/ProtractorReporter.js.map +1 -1
- package/lib/adapter/reporter/index.d.ts +1 -0
- package/lib/adapter/reporter/index.d.ts.map +1 -0
- package/lib/adapter/reporter/index.js +5 -1
- package/lib/adapter/reporter/index.js.map +1 -1
- package/lib/adapter/run.d.ts +7 -4
- package/lib/adapter/run.d.ts.map +1 -0
- package/lib/adapter/run.js +6 -4
- package/lib/adapter/run.js.map +1 -1
- package/lib/adapter/runner/CucumberAdapterConfig.d.ts +5 -10
- package/lib/adapter/runner/CucumberAdapterConfig.d.ts.map +1 -0
- package/lib/adapter/runner/TestRunnerDetector.d.ts +5 -12
- package/lib/adapter/runner/TestRunnerDetector.d.ts.map +1 -0
- package/lib/adapter/runner/TestRunnerDetector.js +7 -15
- package/lib/adapter/runner/TestRunnerDetector.js.map +1 -1
- package/lib/adapter/runner/TestRunnerLoader.d.ts +20 -17
- package/lib/adapter/runner/TestRunnerLoader.d.ts.map +1 -0
- package/lib/adapter/runner/TestRunnerLoader.js +18 -18
- package/lib/adapter/runner/TestRunnerLoader.js.map +1 -1
- package/lib/adapter/runner/index.d.ts +1 -0
- package/lib/adapter/runner/index.d.ts.map +1 -0
- package/lib/adapter/runner/index.js +5 -1
- package/lib/adapter/runner/index.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +5 -1
- package/lib/index.js.map +1 -1
- package/lib/screenplay/abilities/BrowseTheWebWithProtractor.d.ts +62 -300
- package/lib/screenplay/abilities/BrowseTheWebWithProtractor.d.ts.map +1 -0
- package/lib/screenplay/abilities/BrowseTheWebWithProtractor.js +62 -422
- package/lib/screenplay/abilities/BrowseTheWebWithProtractor.js.map +1 -1
- package/lib/screenplay/abilities/index.d.ts +1 -0
- package/lib/screenplay/abilities/index.d.ts.map +1 -0
- package/lib/screenplay/abilities/index.js +5 -1
- package/lib/screenplay/abilities/index.js.map +1 -1
- package/lib/screenplay/index.d.ts +1 -0
- package/lib/screenplay/index.d.ts.map +1 -0
- package/lib/screenplay/index.js +5 -1
- package/lib/screenplay/index.js.map +1 -1
- package/lib/screenplay/interactions/UseAngular.d.ts +53 -48
- package/lib/screenplay/interactions/UseAngular.d.ts.map +1 -0
- package/lib/screenplay/interactions/UseAngular.js +52 -42
- package/lib/screenplay/interactions/UseAngular.js.map +1 -1
- package/lib/screenplay/interactions/index.d.ts +1 -0
- package/lib/screenplay/interactions/index.d.ts.map +1 -0
- package/lib/screenplay/interactions/index.js +5 -1
- package/lib/screenplay/interactions/index.js.map +1 -1
- package/lib/screenplay/models/ProtractorBrowsingSession.d.ts +22 -0
- package/lib/screenplay/models/ProtractorBrowsingSession.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorBrowsingSession.js +99 -0
- package/lib/screenplay/models/ProtractorBrowsingSession.js.map +1 -0
- package/lib/screenplay/models/ProtractorCookie.d.ts +6 -0
- package/lib/screenplay/models/ProtractorCookie.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorCookie.js +8 -3
- package/lib/screenplay/models/ProtractorCookie.js.map +1 -1
- package/lib/screenplay/models/ProtractorErrorHandler.d.ts +8 -0
- package/lib/screenplay/models/ProtractorErrorHandler.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorErrorHandler.js +23 -0
- package/lib/screenplay/models/ProtractorErrorHandler.js.map +1 -0
- package/lib/screenplay/models/ProtractorModalDialogHandler.d.ts +25 -0
- package/lib/screenplay/models/ProtractorModalDialogHandler.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorModalDialogHandler.js +74 -0
- package/lib/screenplay/models/ProtractorModalDialogHandler.js.map +1 -0
- package/lib/screenplay/models/ProtractorPage.d.ts +58 -6
- package/lib/screenplay/models/ProtractorPage.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorPage.js +221 -44
- package/lib/screenplay/models/ProtractorPage.js.map +1 -1
- package/lib/screenplay/models/ProtractorPageElement.d.ts +10 -1
- package/lib/screenplay/models/ProtractorPageElement.d.ts.map +1 -0
- package/lib/screenplay/models/ProtractorPageElement.js +141 -147
- package/lib/screenplay/models/ProtractorPageElement.js.map +1 -1
- package/lib/screenplay/models/index.d.ts +2 -1
- package/lib/screenplay/models/index.d.ts.map +1 -0
- package/lib/screenplay/models/index.js +6 -2
- package/lib/screenplay/models/index.js.map +1 -1
- package/lib/screenplay/models/locators/ProtractorLocator.d.ts +21 -8
- package/lib/screenplay/models/locators/ProtractorLocator.d.ts.map +1 -0
- package/lib/screenplay/models/locators/ProtractorLocator.js +107 -10
- package/lib/screenplay/models/locators/ProtractorLocator.js.map +1 -1
- package/lib/screenplay/models/locators/ProtractorRootLocator.d.ts +17 -0
- package/lib/screenplay/models/locators/ProtractorRootLocator.d.ts.map +1 -0
- package/lib/screenplay/models/locators/ProtractorRootLocator.js +38 -0
- package/lib/screenplay/models/locators/ProtractorRootLocator.js.map +1 -0
- package/lib/screenplay/models/locators/index.d.ts +2 -1
- package/lib/screenplay/models/locators/index.d.ts.map +1 -0
- package/lib/screenplay/models/locators/index.js +6 -2
- package/lib/screenplay/models/locators/index.js.map +1 -1
- package/lib/screenplay/promised.d.ts +3 -3
- package/lib/screenplay/promised.d.ts.map +1 -0
- package/lib/screenplay/promised.js +2 -3
- package/lib/screenplay/promised.js.map +1 -1
- package/lib/screenplay/questions/ProtractorParam.d.ts +64 -51
- package/lib/screenplay/questions/ProtractorParam.d.ts.map +1 -0
- package/lib/screenplay/questions/ProtractorParam.js +59 -47
- package/lib/screenplay/questions/ProtractorParam.js.map +1 -1
- package/lib/screenplay/questions/index.d.ts +1 -0
- package/lib/screenplay/questions/index.d.ts.map +1 -0
- package/lib/screenplay/questions/index.js +5 -1
- package/lib/screenplay/questions/index.js.map +1 -1
- package/lib/screenplay/unpromisedWebElement.d.ts +6 -4
- package/lib/screenplay/unpromisedWebElement.d.ts.map +1 -0
- package/lib/screenplay/unpromisedWebElement.js +5 -4
- package/lib/screenplay/unpromisedWebElement.js.map +1 -1
- package/package.json +36 -60
- package/src/adapter/Config.ts +10 -9
- package/src/adapter/ProtractorFrameworkAdapter.ts +16 -10
- package/src/adapter/browser-detector/BrowserDetector.ts +3 -3
- package/src/adapter/browser-detector/StandardisedCapabilities.ts +24 -24
- package/src/adapter/index.ts +1 -0
- package/src/adapter/reporter/ProtractorReport.ts +4 -6
- package/src/adapter/reporter/ProtractorReporter.ts +16 -16
- package/src/adapter/run.ts +6 -4
- package/src/adapter/runner/CucumberAdapterConfig.ts +5 -10
- package/src/adapter/runner/TestRunnerDetector.ts +5 -13
- package/src/adapter/runner/TestRunnerLoader.ts +21 -19
- package/src/screenplay/abilities/BrowseTheWebWithProtractor.ts +63 -480
- package/src/screenplay/interactions/UseAngular.ts +55 -44
- package/src/screenplay/models/ProtractorBrowsingSession.ts +133 -0
- package/src/screenplay/models/ProtractorCookie.ts +8 -3
- package/src/screenplay/models/ProtractorErrorHandler.ts +23 -0
- package/src/screenplay/models/ProtractorModalDialogHandler.ts +97 -0
- package/src/screenplay/models/ProtractorPage.ts +282 -54
- package/src/screenplay/models/ProtractorPageElement.ts +160 -165
- package/src/screenplay/models/index.ts +1 -1
- package/src/screenplay/models/locators/ProtractorLocator.ts +121 -31
- package/src/screenplay/models/locators/ProtractorRootLocator.ts +41 -0
- package/src/screenplay/models/locators/index.ts +1 -1
- package/src/screenplay/promised.ts +2 -3
- package/src/screenplay/questions/ProtractorParam.ts +62 -49
- package/src/screenplay/unpromisedWebElement.ts +5 -4
- package/tsconfig.build.json +14 -0
- package/lib/screenplay/models/ProtractorModalDialog.d.ts +0 -11
- package/lib/screenplay/models/ProtractorModalDialog.js +0 -43
- package/lib/screenplay/models/ProtractorModalDialog.js.map +0 -1
- package/lib/screenplay/models/locators/ProtractorNativeElementRoot.d.ts +0 -5
- package/lib/screenplay/models/locators/ProtractorNativeElementRoot.js +0 -3
- package/lib/screenplay/models/locators/ProtractorNativeElementRoot.js.map +0 -1
- package/src/screenplay/models/ProtractorModalDialog.ts +0 -49
- package/src/screenplay/models/locators/ProtractorNativeElementRoot.ts +0 -6
- package/tsconfig.eslint.json +0 -10
|
@@ -1,71 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
if (! await element.isDisplayed()) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
256
|
+
try {
|
|
257
|
+
const element: ElementFinder = await this.nativeElement();
|
|
157
258
|
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
webElement,
|
|
283
|
-
));
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
284
279
|
}
|
|
285
280
|
}
|
|
@@ -1,50 +1,140 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
8
|
+
import { ProtractorRootLocator } from './ProtractorRootLocator';
|
|
7
9
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 './
|
|
2
|
+
export * from './ProtractorRootLocator';
|