@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.
- package/CHANGELOG.md +50 -0
- package/LICENSE.md +201 -0
- package/NOTICE.md +1 -0
- package/README.md +21 -0
- package/lib/errors/CookieMissingError.d.ts +4 -0
- package/lib/errors/CookieMissingError.js +11 -0
- package/lib/errors/CookieMissingError.js.map +1 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +14 -0
- package/lib/errors/index.js.map +1 -0
- package/lib/expectations/ElementExpectation.d.ts +11 -0
- package/lib/expectations/ElementExpectation.js +27 -0
- package/lib/expectations/ElementExpectation.js.map +1 -0
- package/lib/expectations/index.d.ts +6 -0
- package/lib/expectations/index.js +19 -0
- package/lib/expectations/index.js.map +1 -0
- package/lib/expectations/isActive.d.ts +15 -0
- package/lib/expectations/isActive.js +22 -0
- package/lib/expectations/isActive.js.map +1 -0
- package/lib/expectations/isClickable.d.ts +20 -0
- package/lib/expectations/isClickable.js +30 -0
- package/lib/expectations/isClickable.js.map +1 -0
- package/lib/expectations/isEnabled.d.ts +14 -0
- package/lib/expectations/isEnabled.js +20 -0
- package/lib/expectations/isEnabled.js.map +1 -0
- package/lib/expectations/isPresent.d.ts +15 -0
- package/lib/expectations/isPresent.js +22 -0
- package/lib/expectations/isPresent.js.map +1 -0
- package/lib/expectations/isSelected.d.ts +14 -0
- package/lib/expectations/isSelected.js +23 -0
- package/lib/expectations/isSelected.js.map +1 -0
- package/lib/expectations/isVisible.d.ts +14 -0
- package/lib/expectations/isVisible.js +26 -0
- package/lib/expectations/isVisible.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/input/Key.d.ts +73 -0
- package/lib/input/Key.js +84 -0
- package/lib/input/Key.js.map +1 -0
- package/lib/input/index.d.ts +1 -0
- package/lib/input/index.js +14 -0
- package/lib/input/index.js.map +1 -0
- package/lib/screenplay/abilities/BrowseTheWeb.d.ts +58 -0
- package/lib/screenplay/abilities/BrowseTheWeb.js +19 -0
- package/lib/screenplay/abilities/BrowseTheWeb.js.map +1 -0
- package/lib/screenplay/abilities/BrowserCapabilities.d.ts +5 -0
- package/lib/screenplay/abilities/BrowserCapabilities.js +3 -0
- package/lib/screenplay/abilities/BrowserCapabilities.js.map +1 -0
- package/lib/screenplay/abilities/index.d.ts +2 -0
- package/lib/screenplay/abilities/index.js +15 -0
- package/lib/screenplay/abilities/index.js.map +1 -0
- package/lib/screenplay/index.d.ts +4 -0
- package/lib/screenplay/index.js +17 -0
- package/lib/screenplay/index.js.map +1 -0
- package/lib/screenplay/interactions/Clear.d.ts +79 -0
- package/lib/screenplay/interactions/Clear.js +97 -0
- package/lib/screenplay/interactions/Clear.js.map +1 -0
- package/lib/screenplay/interactions/Click.d.ts +73 -0
- package/lib/screenplay/interactions/Click.js +85 -0
- package/lib/screenplay/interactions/Click.js.map +1 -0
- package/lib/screenplay/interactions/DoubleClick.d.ts +90 -0
- package/lib/screenplay/interactions/DoubleClick.js +101 -0
- package/lib/screenplay/interactions/DoubleClick.js.map +1 -0
- package/lib/screenplay/interactions/Enter.d.ts +73 -0
- package/lib/screenplay/interactions/Enter.js +86 -0
- package/lib/screenplay/interactions/Enter.js.map +1 -0
- package/lib/screenplay/interactions/EnterBuilder.d.ts +25 -0
- package/lib/screenplay/interactions/EnterBuilder.js +3 -0
- package/lib/screenplay/interactions/EnterBuilder.js.map +1 -0
- package/lib/screenplay/interactions/ExecuteScript.d.ts +206 -0
- package/lib/screenplay/interactions/ExecuteScript.js +312 -0
- package/lib/screenplay/interactions/ExecuteScript.js.map +1 -0
- package/lib/screenplay/interactions/Hover.d.ts +78 -0
- package/lib/screenplay/interactions/Hover.js +89 -0
- package/lib/screenplay/interactions/Hover.js.map +1 -0
- package/lib/screenplay/interactions/Navigate.d.ts +142 -0
- package/lib/screenplay/interactions/Navigate.js +198 -0
- package/lib/screenplay/interactions/Navigate.js.map +1 -0
- package/lib/screenplay/interactions/PageElementInteraction.d.ts +39 -0
- package/lib/screenplay/interactions/PageElementInteraction.js +54 -0
- package/lib/screenplay/interactions/PageElementInteraction.js.map +1 -0
- package/lib/screenplay/interactions/Press.d.ts +84 -0
- package/lib/screenplay/interactions/Press.js +171 -0
- package/lib/screenplay/interactions/Press.js.map +1 -0
- package/lib/screenplay/interactions/PressBuilder.d.ts +26 -0
- package/lib/screenplay/interactions/PressBuilder.js +3 -0
- package/lib/screenplay/interactions/PressBuilder.js.map +1 -0
- package/lib/screenplay/interactions/RightClick.d.ts +89 -0
- package/lib/screenplay/interactions/RightClick.js +100 -0
- package/lib/screenplay/interactions/RightClick.js.map +1 -0
- package/lib/screenplay/interactions/Scroll.d.ts +83 -0
- package/lib/screenplay/interactions/Scroll.js +97 -0
- package/lib/screenplay/interactions/Scroll.js.map +1 -0
- package/lib/screenplay/interactions/Select.d.ts +212 -0
- package/lib/screenplay/interactions/Select.js +291 -0
- package/lib/screenplay/interactions/Select.js.map +1 -0
- package/lib/screenplay/interactions/SelectBuilder.d.ts +33 -0
- package/lib/screenplay/interactions/SelectBuilder.js +3 -0
- package/lib/screenplay/interactions/SelectBuilder.js.map +1 -0
- package/lib/screenplay/interactions/Switch.d.ts +150 -0
- package/lib/screenplay/interactions/Switch.js +209 -0
- package/lib/screenplay/interactions/Switch.js.map +1 -0
- package/lib/screenplay/interactions/TakeScreenshot.d.ts +67 -0
- package/lib/screenplay/interactions/TakeScreenshot.js +86 -0
- package/lib/screenplay/interactions/TakeScreenshot.js.map +1 -0
- package/lib/screenplay/interactions/Wait.d.ts +143 -0
- package/lib/screenplay/interactions/Wait.js +242 -0
- package/lib/screenplay/interactions/Wait.js.map +1 -0
- package/lib/screenplay/interactions/WaitBuilder.d.ts +32 -0
- package/lib/screenplay/interactions/WaitBuilder.js +3 -0
- package/lib/screenplay/interactions/WaitBuilder.js.map +1 -0
- package/lib/screenplay/interactions/index.d.ts +16 -0
- package/lib/screenplay/interactions/index.js +29 -0
- package/lib/screenplay/interactions/index.js.map +1 -0
- package/lib/screenplay/models/Cookie.d.ts +117 -0
- package/lib/screenplay/models/Cookie.js +176 -0
- package/lib/screenplay/models/Cookie.js.map +1 -0
- package/lib/screenplay/models/CookieData.d.ts +89 -0
- package/lib/screenplay/models/CookieData.js +3 -0
- package/lib/screenplay/models/CookieData.js.map +1 -0
- package/lib/screenplay/models/ModalDialog.d.ts +9 -0
- package/lib/screenplay/models/ModalDialog.js +14 -0
- package/lib/screenplay/models/ModalDialog.js.map +1 -0
- package/lib/screenplay/models/Page.d.ts +83 -0
- package/lib/screenplay/models/Page.js +52 -0
- package/lib/screenplay/models/Page.js.map +1 -0
- package/lib/screenplay/models/PageElement.d.ts +30 -0
- package/lib/screenplay/models/PageElement.js +62 -0
- package/lib/screenplay/models/PageElement.js.map +1 -0
- package/lib/screenplay/models/PageElements.d.ts +20 -0
- package/lib/screenplay/models/PageElements.js +49 -0
- package/lib/screenplay/models/PageElements.js.map +1 -0
- package/lib/screenplay/models/index.d.ts +6 -0
- package/lib/screenplay/models/index.js +19 -0
- package/lib/screenplay/models/index.js.map +1 -0
- package/lib/screenplay/questions/Attribute.d.ts +83 -0
- package/lib/screenplay/questions/Attribute.js +103 -0
- package/lib/screenplay/questions/Attribute.js.map +1 -0
- package/lib/screenplay/questions/CssClasses.d.ts +93 -0
- package/lib/screenplay/questions/CssClasses.js +113 -0
- package/lib/screenplay/questions/CssClasses.js.map +1 -0
- package/lib/screenplay/questions/ElementQuestion.d.ts +34 -0
- package/lib/screenplay/questions/ElementQuestion.js +53 -0
- package/lib/screenplay/questions/ElementQuestion.js.map +1 -0
- package/lib/screenplay/questions/LastScriptExecution.d.ts +14 -0
- package/lib/screenplay/questions/LastScriptExecution.js +22 -0
- package/lib/screenplay/questions/LastScriptExecution.js.map +1 -0
- package/lib/screenplay/questions/Selected.d.ts +185 -0
- package/lib/screenplay/questions/Selected.js +210 -0
- package/lib/screenplay/questions/Selected.js.map +1 -0
- package/lib/screenplay/questions/Text.d.ts +99 -0
- package/lib/screenplay/questions/Text.js +131 -0
- package/lib/screenplay/questions/Text.js.map +1 -0
- package/lib/screenplay/questions/Value.d.ts +64 -0
- package/lib/screenplay/questions/Value.js +78 -0
- package/lib/screenplay/questions/Value.js.map +1 -0
- package/lib/screenplay/questions/index.d.ts +6 -0
- package/lib/screenplay/questions/index.js +19 -0
- package/lib/screenplay/questions/index.js.map +1 -0
- package/lib/stage/crew/index.d.ts +1 -0
- package/lib/stage/crew/index.js +14 -0
- package/lib/stage/crew/index.js.map +1 -0
- package/lib/stage/crew/photographer/Photographer.d.ts +83 -0
- package/lib/stage/crew/photographer/Photographer.js +102 -0
- package/lib/stage/crew/photographer/Photographer.js.map +1 -0
- package/lib/stage/crew/photographer/index.d.ts +2 -0
- package/lib/stage/crew/photographer/index.js +15 -0
- package/lib/stage/crew/photographer/index.js.map +1 -0
- package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.d.ts +28 -0
- package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js +81 -0
- package/lib/stage/crew/photographer/strategies/PhotoTakingStrategy.js.map +1 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.d.ts +18 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js +30 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.js.map +1 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.d.ts +17 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js +28 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfFailures.js.map +1 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.d.ts +19 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js +28 -0
- package/lib/stage/crew/photographer/strategies/TakePhotosOfInteractions.js.map +1 -0
- package/lib/stage/crew/photographer/strategies/index.d.ts +4 -0
- package/lib/stage/crew/photographer/strategies/index.js +17 -0
- package/lib/stage/crew/photographer/strategies/index.js.map +1 -0
- package/lib/stage/index.d.ts +1 -0
- package/lib/stage/index.js +14 -0
- package/lib/stage/index.js.map +1 -0
- package/package.json +85 -0
- package/src/errors/CookieMissingError.ts +7 -0
- package/src/errors/index.ts +1 -0
- package/src/expectations/ElementExpectation.ts +32 -0
- package/src/expectations/index.ts +6 -0
- package/src/expectations/isActive.ts +22 -0
- package/src/expectations/isClickable.ts +32 -0
- package/src/expectations/isEnabled.ts +19 -0
- package/src/expectations/isPresent.ts +21 -0
- package/src/expectations/isSelected.ts +24 -0
- package/src/expectations/isVisible.ts +28 -0
- package/src/index.ts +5 -0
- package/src/input/Key.ts +83 -0
- package/src/input/index.ts +1 -0
- package/src/screenplay/abilities/BrowseTheWeb.ts +89 -0
- package/src/screenplay/abilities/BrowserCapabilities.ts +5 -0
- package/src/screenplay/abilities/index.ts +2 -0
- package/src/screenplay/index.ts +4 -0
- package/src/screenplay/interactions/Clear.ts +102 -0
- package/src/screenplay/interactions/Click.ts +86 -0
- package/src/screenplay/interactions/DoubleClick.ts +102 -0
- package/src/screenplay/interactions/Enter.ts +92 -0
- package/src/screenplay/interactions/EnterBuilder.ts +28 -0
- package/src/screenplay/interactions/ExecuteScript.ts +345 -0
- package/src/screenplay/interactions/Hover.ts +90 -0
- package/src/screenplay/interactions/Navigate.ts +209 -0
- package/src/screenplay/interactions/PageElementInteraction.ts +59 -0
- package/src/screenplay/interactions/Press.ts +194 -0
- package/src/screenplay/interactions/PressBuilder.ts +29 -0
- package/src/screenplay/interactions/RightClick.ts +100 -0
- package/src/screenplay/interactions/Scroll.ts +99 -0
- package/src/screenplay/interactions/Select.ts +317 -0
- package/src/screenplay/interactions/SelectBuilder.ts +36 -0
- package/src/screenplay/interactions/Switch.ts +225 -0
- package/src/screenplay/interactions/TakeScreenshot.ts +89 -0
- package/src/screenplay/interactions/Wait.ts +264 -0
- package/src/screenplay/interactions/WaitBuilder.ts +34 -0
- package/src/screenplay/interactions/index.ts +16 -0
- package/src/screenplay/models/Cookie.ts +219 -0
- package/src/screenplay/models/CookieData.ts +97 -0
- package/src/screenplay/models/ModalDialog.ts +19 -0
- package/src/screenplay/models/Page.ts +147 -0
- package/src/screenplay/models/PageElement.ts +95 -0
- package/src/screenplay/models/PageElements.ts +70 -0
- package/src/screenplay/models/index.ts +6 -0
- package/src/screenplay/questions/Attribute.ts +112 -0
- package/src/screenplay/questions/CssClasses.ts +118 -0
- package/src/screenplay/questions/ElementQuestion.ts +60 -0
- package/src/screenplay/questions/LastScriptExecution.ts +21 -0
- package/src/screenplay/questions/Selected.ts +212 -0
- package/src/screenplay/questions/Text.ts +153 -0
- package/src/screenplay/questions/Value.ts +82 -0
- package/src/screenplay/questions/index.ts +6 -0
- package/src/stage/crew/index.ts +1 -0
- package/src/stage/crew/photographer/Photographer.ts +108 -0
- package/src/stage/crew/photographer/index.ts +2 -0
- package/src/stage/crew/photographer/strategies/PhotoTakingStrategy.ts +116 -0
- package/src/stage/crew/photographer/strategies/TakePhotosBeforeAndAfterInteractions.ts +28 -0
- package/src/stage/crew/photographer/strategies/TakePhotosOfFailures.ts +26 -0
- package/src/stage/crew/photographer/strategies/TakePhotosOfInteractions.ts +26 -0
- package/src/stage/crew/photographer/strategies/index.ts +4 -0
- package/src/stage/index.ts +1 -0
- package/tsconfig.eslint.json +10 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Adapter, Expectation, ExpectationMet, ExpectationOutcome, LogicError, Question } from '@serenity-js/core';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
|
|
4
|
+
import { BrowseTheWeb } from '../abilities';
|
|
5
|
+
|
|
6
|
+
export abstract class Page {
|
|
7
|
+
static current(): Question<Promise<Page>> & Adapter<Page> {
|
|
8
|
+
return Question.about<Promise<Page>>('current page', actor => {
|
|
9
|
+
return BrowseTheWeb.as(actor).currentPage();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static whichName(expectation: Expectation<any, string>): Question<Promise<Page>> & Adapter<Page> {
|
|
14
|
+
return Question.about(`page which name does ${ expectation }`, async actor => {
|
|
15
|
+
const pages = await BrowseTheWeb.as(actor).allPages();
|
|
16
|
+
const matcher = await actor.answer(expectation);
|
|
17
|
+
|
|
18
|
+
return Page.findMatchingPage(
|
|
19
|
+
`name does ${ expectation }`,
|
|
20
|
+
pages,
|
|
21
|
+
page => page.name().then(matcher)
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static whichTitle(expectation: Expectation<any, string>): Question<Promise<Page>> & Adapter<Page> {
|
|
27
|
+
return Question.about(`page which title does ${ expectation }`, async actor => {
|
|
28
|
+
const pages = await BrowseTheWeb.as(actor).allPages();
|
|
29
|
+
const matcher = await actor.answer(expectation);
|
|
30
|
+
|
|
31
|
+
return Page.findMatchingPage(
|
|
32
|
+
`title does ${ expectation }`,
|
|
33
|
+
pages,
|
|
34
|
+
page => page.title().then(title => {
|
|
35
|
+
return matcher(title);
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static whichUrl(expectation: Expectation<any, string>): Question<Promise<Page>> & Adapter<Page> {
|
|
42
|
+
return Question.about(`page which URL does ${ expectation }`, async actor => {
|
|
43
|
+
const pages = await BrowseTheWeb.as(actor).allPages();
|
|
44
|
+
const matcher = await actor.answer(expectation);
|
|
45
|
+
|
|
46
|
+
return Page.findMatchingPage(
|
|
47
|
+
`url does ${ expectation }`,
|
|
48
|
+
pages,
|
|
49
|
+
page => page.url().then(url => matcher(url.toString()))
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static async findMatchingPage(expectationDescription: string, pages: Page[], matcher: (page: Page) => Promise<ExpectationOutcome<any, any>>): Promise<Page> {
|
|
55
|
+
for (const page of pages) {
|
|
56
|
+
const outcome = await matcher(page);
|
|
57
|
+
|
|
58
|
+
if (outcome instanceof ExpectationMet) {
|
|
59
|
+
return page;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new LogicError(`Couldn't find a page which ${ expectationDescription }`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
protected readonly handle: string,
|
|
68
|
+
) {
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @desc
|
|
73
|
+
* Retrieves the document title of the current top-level browsing context, equivalent to calling `document.title`.
|
|
74
|
+
*
|
|
75
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title
|
|
76
|
+
*
|
|
77
|
+
* @returns {Promise<string>}
|
|
78
|
+
*/
|
|
79
|
+
abstract title(): Promise<string>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @desc
|
|
83
|
+
* Retrieves the URL of the current top-level browsing context.
|
|
84
|
+
*
|
|
85
|
+
* @returns {Promise<URL>}
|
|
86
|
+
*/
|
|
87
|
+
abstract url(): Promise<URL>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @desc
|
|
91
|
+
* Retrieves the name of the current top-level browsing context.
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<string>}
|
|
94
|
+
*/
|
|
95
|
+
abstract name(): Promise<string>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @desc
|
|
99
|
+
* Checks if a given window / tab / page is open and can be switched to.
|
|
100
|
+
*
|
|
101
|
+
* @returns {Promise<string>}
|
|
102
|
+
*/
|
|
103
|
+
abstract isPresent(): Promise<boolean>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @desc
|
|
107
|
+
* Returns the actual viewport size available for the given page,
|
|
108
|
+
* excluding any scrollbars.
|
|
109
|
+
*
|
|
110
|
+
* @returns {Promise<{ width: number, height: number }>}
|
|
111
|
+
*/
|
|
112
|
+
abstract viewportSize(): Promise<{ width: number, height: number }>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @param size
|
|
117
|
+
*/
|
|
118
|
+
abstract setViewportSize(size: { width: number, height: number }): Promise<void>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @desc
|
|
122
|
+
* Switches the current top-level browsing context to the given page
|
|
123
|
+
*
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
abstract switchTo(): Promise<void>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @desc
|
|
130
|
+
* Closes the given page.
|
|
131
|
+
*
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
134
|
+
abstract close(): Promise<void>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @desc
|
|
138
|
+
* Closes any open pages, except for this one.
|
|
139
|
+
*
|
|
140
|
+
* @returns {Promise<void>}
|
|
141
|
+
*/
|
|
142
|
+
abstract closeOthers(): Promise<void>;
|
|
143
|
+
|
|
144
|
+
toString(): string {
|
|
145
|
+
return `page (handle=${ this.handle })`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Adapter, Answerable, format, LogicError, Question } from '@serenity-js/core';
|
|
2
|
+
|
|
3
|
+
import { BrowseTheWeb } from '../abilities';
|
|
4
|
+
|
|
5
|
+
const d = format({ markQuestions: false });
|
|
6
|
+
const f = format({ markQuestions: true });
|
|
7
|
+
|
|
8
|
+
export abstract class PageElement<NativeElementContext = any, NativeElement = any> {
|
|
9
|
+
static of(childElement: Answerable<PageElement>, parentElement: Answerable<PageElement>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
10
|
+
return Question.about(d`${ childElement } of ${ parentElement })`, async actor => {
|
|
11
|
+
const child = await actor.answer(childElement);
|
|
12
|
+
const parent = await actor.answer(parentElement);
|
|
13
|
+
|
|
14
|
+
return child.of(parent);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static locatedByCss(selector: Answerable<string>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
19
|
+
return Question.about(f`page element located by css (${selector})`, async actor => {
|
|
20
|
+
const cssSelector = await actor.answer(selector);
|
|
21
|
+
|
|
22
|
+
return BrowseTheWeb.as(actor).findByCss(cssSelector);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static locatedByCssContainingText(selector: Answerable<string>, text: Answerable<string>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
27
|
+
return Question.about(f`page element located by css (${selector}) containing text ${ text }`, async actor => {
|
|
28
|
+
const cssSelector = await actor.answer(selector);
|
|
29
|
+
const desiredText = await actor.answer(text);
|
|
30
|
+
|
|
31
|
+
return BrowseTheWeb.as(actor).findByCssContainingText(cssSelector, desiredText);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static locatedById(selector: Answerable<string>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
36
|
+
return Question.about(f`page element located by id (${selector})`, async actor => {
|
|
37
|
+
const idSelector = await actor.answer(selector);
|
|
38
|
+
|
|
39
|
+
return BrowseTheWeb.as(actor).findById(idSelector);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static locatedByTagName(tagName: Answerable<string>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
44
|
+
return Question.about(f`page element located by tag name (${tagName})`, async actor => {
|
|
45
|
+
const tagNameSelector = await actor.answer(tagName);
|
|
46
|
+
|
|
47
|
+
return BrowseTheWeb.as(actor).findByTagName(tagNameSelector);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static locatedByXPath(selector: Answerable<string>): Question<Promise<PageElement>> & Adapter<PageElement> {
|
|
52
|
+
return Question.about(f`page element located by xpath (${selector})`, async actor => {
|
|
53
|
+
const xpathSelector = await actor.answer(selector);
|
|
54
|
+
|
|
55
|
+
return BrowseTheWeb.as(actor).findByXPath(xpathSelector);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
protected readonly context: () => Promise<NativeElementContext> | NativeElementContext,
|
|
61
|
+
protected readonly locator: (root: NativeElementContext) => Promise<NativeElement> | NativeElement
|
|
62
|
+
) {
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
abstract of(parent: PageElement): PageElement;
|
|
66
|
+
|
|
67
|
+
async nativeElement(): Promise<NativeElement> {
|
|
68
|
+
try {
|
|
69
|
+
const context = await this.context();
|
|
70
|
+
return this.locator(context);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new LogicError(`Couldn't find element`, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
abstract enterValue(value: string | number | Array<string | number>): Promise<void>;
|
|
78
|
+
abstract clearValue(): Promise<void>;
|
|
79
|
+
abstract click(): Promise<void>;
|
|
80
|
+
abstract doubleClick(): Promise<void>;
|
|
81
|
+
abstract scrollIntoView(): Promise<void>;
|
|
82
|
+
abstract hoverOver(): Promise<void>;
|
|
83
|
+
abstract rightClick(): Promise<void>; // todo: should this be a click() call with a parameter?
|
|
84
|
+
|
|
85
|
+
abstract attribute(name: string): Promise<string>;
|
|
86
|
+
abstract text(): Promise<string>;
|
|
87
|
+
abstract value(): Promise<string>;
|
|
88
|
+
|
|
89
|
+
abstract isActive(): Promise<boolean>;
|
|
90
|
+
abstract isClickable(): Promise<boolean>;
|
|
91
|
+
abstract isDisplayed(): Promise<boolean>;
|
|
92
|
+
abstract isEnabled(): Promise<boolean>;
|
|
93
|
+
abstract isPresent(): Promise<boolean>;
|
|
94
|
+
abstract isSelected(): Promise<boolean>;
|
|
95
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Adapter, Answerable, format, LogicError, Question } from '@serenity-js/core';
|
|
2
|
+
|
|
3
|
+
import { BrowseTheWeb } from '../abilities';
|
|
4
|
+
import { PageElement } from './PageElement';
|
|
5
|
+
|
|
6
|
+
const d = format({ markQuestions: false });
|
|
7
|
+
const f = format({ markQuestions: true });
|
|
8
|
+
|
|
9
|
+
export abstract class PageElements<NativeElementContext = any, NativeElementList = any, NativeElement = any>
|
|
10
|
+
// todo: implements List (Attribute.spec.ts)
|
|
11
|
+
{
|
|
12
|
+
static of(childElements: Answerable<PageElements>, parentElement: Answerable<PageElement>): Question<Promise<PageElements>> & Adapter<PageElements> {
|
|
13
|
+
return Question.about(d `${ childElements } of ${ parentElement })`, async actor => {
|
|
14
|
+
const children = await actor.answer(childElements);
|
|
15
|
+
const parent = await actor.answer(parentElement);
|
|
16
|
+
|
|
17
|
+
return children.of(parent);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static locatedByCss(selector: Answerable<string>): Question<Promise<PageElements>> & Adapter<PageElements> {
|
|
22
|
+
return Question.about(f `page elements located by css (${selector})`, async actor => {
|
|
23
|
+
const value = await actor.answer(selector);
|
|
24
|
+
return BrowseTheWeb.as(actor).findAllByCss(value);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static locatedByTagName(selector: Answerable<string>): Question<Promise<PageElements>> & Adapter<PageElements> {
|
|
29
|
+
return Question.about(f `page elements located by tag name (${selector})`, async actor => {
|
|
30
|
+
const tagNameSelector = await actor.answer(selector);
|
|
31
|
+
return BrowseTheWeb.as(actor).findAllByTagName(tagNameSelector);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static locatedByXPath(selector: Answerable<string>): Question<Promise<PageElements>> & Adapter<PageElements> {
|
|
36
|
+
return Question.about(f `page elements located by xpath (${selector})`, async actor => {
|
|
37
|
+
const xpathSelector = await actor.answer(selector);
|
|
38
|
+
return BrowseTheWeb.as(actor).findAllByXPath(xpathSelector);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
protected readonly context: () => Promise<NativeElementContext> | NativeElementContext,
|
|
44
|
+
protected readonly locator: (root: NativeElementContext) => Promise<NativeElementList> | NativeElementList
|
|
45
|
+
) {
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
abstract of(parent: PageElement): PageElements;
|
|
49
|
+
|
|
50
|
+
async nativeElementList(): Promise<NativeElementList> {
|
|
51
|
+
try {
|
|
52
|
+
const context = await this.context();
|
|
53
|
+
return this.locator(context);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new LogicError(`Couldn't find elements`, error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
abstract count(): Promise<number>;
|
|
61
|
+
abstract first(): Promise<PageElement<NativeElementContext, NativeElement>>;
|
|
62
|
+
abstract last(): Promise<PageElement<NativeElementContext, NativeElement>>;
|
|
63
|
+
abstract get(index: number): Promise<PageElement<NativeElementContext, NativeElement>>;
|
|
64
|
+
|
|
65
|
+
abstract map<O>(fn: (element: PageElement, index?: number, elements?: PageElements) => Promise<O> | O): Promise<O[]>;
|
|
66
|
+
|
|
67
|
+
abstract filter(fn: (element: PageElement, index?: number) => Promise<boolean> | boolean): PageElements;
|
|
68
|
+
|
|
69
|
+
abstract forEach(fn: (element: PageElement, index?: number) => Promise<void> | void): Promise<void>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Answerable, AnswersQuestions, LogicError, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
|
|
2
|
+
|
|
3
|
+
import { PageElement } from '../models';
|
|
4
|
+
import { ElementQuestion } from './ElementQuestion';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @desc
|
|
8
|
+
* Returns the value of the given HTML attribute of a given {@link WebElement},
|
|
9
|
+
* represented by Answerable<{@link @wdio/types~Element}>
|
|
10
|
+
*
|
|
11
|
+
* @example <caption>Example widget</caption>
|
|
12
|
+
* <ul id="shopping-list" data-items-left="2">
|
|
13
|
+
* <li data-state="bought">Coffee<li>
|
|
14
|
+
* <li data-state="buy">Honey<li>
|
|
15
|
+
* <li data-state="buy">Chocolate<li>
|
|
16
|
+
* </ul>
|
|
17
|
+
*
|
|
18
|
+
* @example <caption>Retrieve a HTML attribute of a given WebElement</caption>
|
|
19
|
+
* import { actorCalled } from '@serenity-js/core';
|
|
20
|
+
* import { Ensure, equals } from '@serenity-js/assertions';
|
|
21
|
+
* import { Attribute, by, BrowseTheWeb, Target } from '@serenity-js/webdriverio';
|
|
22
|
+
*
|
|
23
|
+
* const shoppingList = () =>
|
|
24
|
+
* Target.the('shopping list').located(by.id('shopping-list'))
|
|
25
|
+
*
|
|
26
|
+
* actorCalled('Lisa')
|
|
27
|
+
* .whoCan(BrowseTheWeb.using(browser))
|
|
28
|
+
* .attemptsTo(
|
|
29
|
+
* Ensure.that(Attribute.called('data-items-left').of(shoppingList()), equals('2')),
|
|
30
|
+
* )
|
|
31
|
+
*
|
|
32
|
+
* @example <caption>Find WebElements with a given attribute</caption>
|
|
33
|
+
* import { actorCalled } from '@serenity-js/core';
|
|
34
|
+
* import { Ensure, includes } from '@serenity-js/assertions';
|
|
35
|
+
* import { Attribute, BrowseTheWeb, by, Target } from '@serenity-js/webdriverio';
|
|
36
|
+
*
|
|
37
|
+
* class ShoppingList {
|
|
38
|
+
* static items = () =>
|
|
39
|
+
* Target.all('items')
|
|
40
|
+
* .located(by.css('#shopping-list li'))
|
|
41
|
+
*
|
|
42
|
+
* static outstandingItems = () =>
|
|
43
|
+
* ShoppingList.items
|
|
44
|
+
* .where(Attribute.called('data-state'), includes('buy'))
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* actorCalled('Lisa')
|
|
48
|
+
* .whoCan(BrowseTheWeb.using(browser))
|
|
49
|
+
* .attemptsTo(
|
|
50
|
+
* Ensure.that(
|
|
51
|
+
* Text.ofAll(ShoppingList.outstandingItems()),
|
|
52
|
+
* equals([ 'Honey', 'Chocolate' ])
|
|
53
|
+
* ),
|
|
54
|
+
* )
|
|
55
|
+
*
|
|
56
|
+
* @extends {ElementQuestion}
|
|
57
|
+
* @implements {@serenity-js/core/lib/screenplay/questions~MetaQuestion}
|
|
58
|
+
*/
|
|
59
|
+
export class Attribute
|
|
60
|
+
extends ElementQuestion<Promise<string>>
|
|
61
|
+
implements MetaQuestion<Answerable<PageElement>, Promise<string>>
|
|
62
|
+
{
|
|
63
|
+
/**
|
|
64
|
+
* @param {Answerable<string>} name
|
|
65
|
+
* @returns {Attribute}
|
|
66
|
+
*/
|
|
67
|
+
static called(name: Answerable<string>): Attribute {
|
|
68
|
+
return new Attribute(name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {Answerable<string>} name
|
|
73
|
+
* @param {@serenity-js/core/lib/screenplay~Answerable<Element>} [element]
|
|
74
|
+
*/
|
|
75
|
+
constructor(
|
|
76
|
+
private readonly name: Answerable<string>,
|
|
77
|
+
private readonly element?: Answerable<PageElement>,
|
|
78
|
+
) {
|
|
79
|
+
super(`"${ name }" attribute of ${ element }`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @desc
|
|
84
|
+
* Resolves to the value of a HTML attribute of the `target` element,
|
|
85
|
+
* located in the context of a `parent` element.
|
|
86
|
+
*
|
|
87
|
+
* @param {Answerable<PageElement>} parent
|
|
88
|
+
* @returns {Question<Promise<string[]>>}
|
|
89
|
+
*
|
|
90
|
+
* @see {@link Target.all}
|
|
91
|
+
* @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
|
|
92
|
+
*/
|
|
93
|
+
of(parent: Answerable<PageElement>): Question<Promise<string>> {
|
|
94
|
+
return new Attribute(
|
|
95
|
+
this.name,
|
|
96
|
+
this.element
|
|
97
|
+
? PageElement.of(this.element, parent)
|
|
98
|
+
: parent
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string> {
|
|
103
|
+
if (! this.element) {
|
|
104
|
+
throw new LogicError(`Target not specified`); // todo: better error message?
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const element = await actor.answer(this.element);
|
|
108
|
+
const name = await actor.answer(this.name);
|
|
109
|
+
|
|
110
|
+
return element.attribute(name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Answerable, AnswersQuestions, MetaQuestion, Question, UsesAbilities } from '@serenity-js/core';
|
|
2
|
+
import { formatted } from '@serenity-js/core/lib/io';
|
|
3
|
+
|
|
4
|
+
import { PageElement } from '../models';
|
|
5
|
+
import { ElementQuestion } from './ElementQuestion';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @desc
|
|
9
|
+
* Resolves to an array of [CSS classes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-class)
|
|
10
|
+
* of a given {@link WebElement}, represented by Answerable<{@link @wdio/types~Element}>.
|
|
11
|
+
*
|
|
12
|
+
* @example <caption>Example widget</caption>
|
|
13
|
+
* <ul id="shopping-list" class="active favourite">
|
|
14
|
+
* <li class="bought">Coffee<li>
|
|
15
|
+
* <li class="buy">Honey<li>
|
|
16
|
+
* <li class="buy">Chocolate<li>
|
|
17
|
+
* </ul>
|
|
18
|
+
*
|
|
19
|
+
* @example <caption>Retrieve CSS classes of a given WebElement</caption>
|
|
20
|
+
* import { actorCalled } from '@serenity-js/core';
|
|
21
|
+
* import { Ensure, equals } from '@serenity-js/assertions';
|
|
22
|
+
* import { BrowseTheWeb, by, CssClasses, Target } from '@serenity-js/webdriverio';
|
|
23
|
+
*
|
|
24
|
+
* const shoppingList = () =>
|
|
25
|
+
* Target.the('shopping list').located(by.id('shopping-list'))
|
|
26
|
+
*
|
|
27
|
+
* actorCalled('Lisa')
|
|
28
|
+
* .whoCan(BrowseTheWeb.using(browser))
|
|
29
|
+
* .attemptsTo(
|
|
30
|
+
* Ensure.that(CssClasses.of(shoppingList()), equals([ 'active', 'favourite' ])),
|
|
31
|
+
* )
|
|
32
|
+
*
|
|
33
|
+
* @example <caption>Find WebElements with a given class</caption>
|
|
34
|
+
* import { actorCalled } from '@serenity-js/core';
|
|
35
|
+
* import { Ensure, contain } from '@serenity-js/assertions';
|
|
36
|
+
* import { BrowseTheWeb, by, CssClasses, Target } from '@serenity-js/webdriverio';
|
|
37
|
+
*
|
|
38
|
+
* class ShoppingList {
|
|
39
|
+
* static items = () =>
|
|
40
|
+
* Target.all('items')
|
|
41
|
+
* .located(by.css('#shopping-list li'))
|
|
42
|
+
*
|
|
43
|
+
* static outstandingItems = () =>
|
|
44
|
+
* ShoppingList.items
|
|
45
|
+
* .where(CssClasses, contain('buy'))
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* actorCalled('Lisa')
|
|
49
|
+
* .whoCan(BrowseTheWeb.using(browser))
|
|
50
|
+
* .attemptsTo(
|
|
51
|
+
* Ensure.that(
|
|
52
|
+
* Text.ofAll(ShoppingList.outstandingItems()),
|
|
53
|
+
* equals([ 'Honey', 'Chocolate' ])
|
|
54
|
+
* ),
|
|
55
|
+
* )
|
|
56
|
+
*
|
|
57
|
+
* @extends {ElementQuestion}
|
|
58
|
+
* @implements {@serenity-js/core/lib/screenplay/questions~MetaQuestion}
|
|
59
|
+
*/
|
|
60
|
+
export class CssClasses
|
|
61
|
+
extends ElementQuestion<Promise<string[]>>
|
|
62
|
+
implements MetaQuestion<Answerable<PageElement>, Promise<string[]>>
|
|
63
|
+
{
|
|
64
|
+
/**
|
|
65
|
+
* @param {Question<PageElement> | PageElement} target
|
|
66
|
+
* @returns {CssClasses}
|
|
67
|
+
*/
|
|
68
|
+
static of(target: Answerable<PageElement>): CssClasses {
|
|
69
|
+
return new CssClasses(target);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {Question<PageElement> | PageElement} target
|
|
74
|
+
*/
|
|
75
|
+
constructor(private readonly target: Answerable<PageElement>) {
|
|
76
|
+
super(formatted `CSS classes of ${ target}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @desc
|
|
81
|
+
* Resolves to an array of CSS classes of the `target` element,
|
|
82
|
+
* located in the context of a `parent` element.
|
|
83
|
+
*
|
|
84
|
+
* @param {@serenity-js/core/lib/screenplay~Answerable<Element>} parent
|
|
85
|
+
* @returns {Question<Promise<string[]>>}
|
|
86
|
+
*
|
|
87
|
+
* @see {@link Target.all}
|
|
88
|
+
* @see {@link @serenity-js/core/lib/screenplay/questions~MetaQuestion}
|
|
89
|
+
*/
|
|
90
|
+
of(parent: Answerable<PageElement>): Question<Promise<string[]>> {
|
|
91
|
+
return new CssClasses(PageElement.of(this.target, parent));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @desc
|
|
96
|
+
* Makes the provided {@link @serenity-js/core/lib/screenplay/actor~Actor}
|
|
97
|
+
* answer this {@link @serenity-js/core/lib/screenplay~Question}.
|
|
98
|
+
*
|
|
99
|
+
* @param {AnswersQuestions & UsesAbilities} actor
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
*
|
|
102
|
+
* @see {@link @serenity-js/core/lib/screenplay/actor~Actor}
|
|
103
|
+
* @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
|
|
104
|
+
* @see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities}
|
|
105
|
+
*/
|
|
106
|
+
async answeredBy(actor: AnswersQuestions & UsesAbilities): Promise<string[]> {
|
|
107
|
+
const element = await this.resolve(actor, this.target);
|
|
108
|
+
|
|
109
|
+
return element.attribute('class')
|
|
110
|
+
.then(attribute => attribute ?? '')
|
|
111
|
+
.then(attribute => attribute
|
|
112
|
+
.replace(/\s+/, ' ')
|
|
113
|
+
.trim()
|
|
114
|
+
.split(' ')
|
|
115
|
+
.filter(cssClass => !! cssClass),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Answerable, AnswersQuestions, LogicError, Question } from '@serenity-js/core';
|
|
2
|
+
import { formatted } from '@serenity-js/core/lib/io';
|
|
3
|
+
|
|
4
|
+
import { PageElement, PageElements } from '../models';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @desc
|
|
8
|
+
* A base class for questions about {@link PageElement}s.
|
|
9
|
+
*
|
|
10
|
+
* @extends {@serenity-js/core/lib/screenplay~Question}
|
|
11
|
+
*/
|
|
12
|
+
// todo: remove
|
|
13
|
+
export abstract class ElementQuestion<T>
|
|
14
|
+
extends Question<T>
|
|
15
|
+
{
|
|
16
|
+
constructor(protected subject: string) {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @desc
|
|
22
|
+
* Changes the description of this question's subject.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} subject
|
|
25
|
+
* @returns {Question<T>}
|
|
26
|
+
*/
|
|
27
|
+
describedAs(subject: string): this {
|
|
28
|
+
this.subject = subject;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toString(): string {
|
|
33
|
+
return this.subject;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @desc
|
|
38
|
+
* Returns the resolved {@link PageElement}, or throws a {@link @serenity-js/core/lib/errors~LogicError}
|
|
39
|
+
* if the element is `undefined`.
|
|
40
|
+
*
|
|
41
|
+
* @param {@serenity-js/core/lib/screenplay/actor~AnswersQuestions} actor
|
|
42
|
+
* @param {@serenity-js/core/lib/screenplay~Answerable<Element|ElementList>} element
|
|
43
|
+
*
|
|
44
|
+
* @returns {Promise<PageElement|PageElements>}
|
|
45
|
+
*
|
|
46
|
+
* @protected
|
|
47
|
+
*/
|
|
48
|
+
protected async resolve<T=PageElement|PageElements>(
|
|
49
|
+
actor: AnswersQuestions,
|
|
50
|
+
element: Answerable<T>,
|
|
51
|
+
): Promise<T> {
|
|
52
|
+
const resolved = await actor.answer(element);
|
|
53
|
+
|
|
54
|
+
if (! resolved) {
|
|
55
|
+
throw new LogicError(formatted `Couldn't find ${ element }`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return resolved;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Question } from '@serenity-js/core';
|
|
2
|
+
|
|
3
|
+
import { BrowseTheWeb } from '../abilities';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @desc
|
|
7
|
+
* Returns the result of last script executed via {@link ExecuteScript}
|
|
8
|
+
*/
|
|
9
|
+
export class LastScriptExecution {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @desc
|
|
13
|
+
* Enables asserting on the result of a function executed via {@link ExecuteScript}.
|
|
14
|
+
*
|
|
15
|
+
* @returns {Question<R>}
|
|
16
|
+
*/
|
|
17
|
+
static result<R>(): Question<R> {
|
|
18
|
+
return Question.about(`last script execution result`, actor =>
|
|
19
|
+
BrowseTheWeb.as(actor).lastScriptExecutionResult());
|
|
20
|
+
}
|
|
21
|
+
}
|