@serenity-js/playwright 3.0.0-rc.20

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 (62) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE.md +201 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +145 -0
  5. package/lib/PlaywrightOptions.d.ts +40 -0
  6. package/lib/PlaywrightOptions.js +3 -0
  7. package/lib/PlaywrightOptions.js.map +1 -0
  8. package/lib/index.d.ts +2 -0
  9. package/lib/index.js +19 -0
  10. package/lib/index.js.map +1 -0
  11. package/lib/screenplay/abilities/BrowseTheWebWithPlaywright.d.ts +71 -0
  12. package/lib/screenplay/abilities/BrowseTheWebWithPlaywright.js +86 -0
  13. package/lib/screenplay/abilities/BrowseTheWebWithPlaywright.js.map +1 -0
  14. package/lib/screenplay/abilities/index.d.ts +1 -0
  15. package/lib/screenplay/abilities/index.js +18 -0
  16. package/lib/screenplay/abilities/index.js.map +1 -0
  17. package/lib/screenplay/index.d.ts +2 -0
  18. package/lib/screenplay/index.js +19 -0
  19. package/lib/screenplay/index.js.map +1 -0
  20. package/lib/screenplay/models/PlaywrightBrowsingSession.d.ts +25 -0
  21. package/lib/screenplay/models/PlaywrightBrowsingSession.js +67 -0
  22. package/lib/screenplay/models/PlaywrightBrowsingSession.js.map +1 -0
  23. package/lib/screenplay/models/PlaywrightCookie.d.ts +8 -0
  24. package/lib/screenplay/models/PlaywrightCookie.js +39 -0
  25. package/lib/screenplay/models/PlaywrightCookie.js.map +1 -0
  26. package/lib/screenplay/models/PlaywrightModalDialogHandler.d.ts +13 -0
  27. package/lib/screenplay/models/PlaywrightModalDialogHandler.js +45 -0
  28. package/lib/screenplay/models/PlaywrightModalDialogHandler.js.map +1 -0
  29. package/lib/screenplay/models/PlaywrightPage.d.ts +58 -0
  30. package/lib/screenplay/models/PlaywrightPage.js +177 -0
  31. package/lib/screenplay/models/PlaywrightPage.js.map +1 -0
  32. package/lib/screenplay/models/PlaywrightPageElement.d.ts +24 -0
  33. package/lib/screenplay/models/PlaywrightPageElement.js +194 -0
  34. package/lib/screenplay/models/PlaywrightPageElement.js.map +1 -0
  35. package/lib/screenplay/models/index.d.ts +5 -0
  36. package/lib/screenplay/models/index.js +22 -0
  37. package/lib/screenplay/models/index.js.map +1 -0
  38. package/lib/screenplay/models/locators/PlaywrightLocator.d.ts +13 -0
  39. package/lib/screenplay/models/locators/PlaywrightLocator.js +77 -0
  40. package/lib/screenplay/models/locators/PlaywrightLocator.js.map +1 -0
  41. package/lib/screenplay/models/locators/PlaywrightRootLocator.d.ts +11 -0
  42. package/lib/screenplay/models/locators/PlaywrightRootLocator.js +26 -0
  43. package/lib/screenplay/models/locators/PlaywrightRootLocator.js.map +1 -0
  44. package/lib/screenplay/models/locators/index.d.ts +2 -0
  45. package/lib/screenplay/models/locators/index.js +19 -0
  46. package/lib/screenplay/models/locators/index.js.map +1 -0
  47. package/package.json +68 -0
  48. package/src/PlaywrightOptions.ts +43 -0
  49. package/src/index.ts +2 -0
  50. package/src/screenplay/abilities/BrowseTheWebWithPlaywright.ts +89 -0
  51. package/src/screenplay/abilities/index.ts +1 -0
  52. package/src/screenplay/index.ts +2 -0
  53. package/src/screenplay/models/PlaywrightBrowsingSession.ts +88 -0
  54. package/src/screenplay/models/PlaywrightCookie.ts +46 -0
  55. package/src/screenplay/models/PlaywrightModalDialogHandler.ts +53 -0
  56. package/src/screenplay/models/PlaywrightPage.ts +258 -0
  57. package/src/screenplay/models/PlaywrightPageElement.ts +235 -0
  58. package/src/screenplay/models/index.ts +5 -0
  59. package/src/screenplay/models/locators/PlaywrightLocator.ts +113 -0
  60. package/src/screenplay/models/locators/PlaywrightRootLocator.ts +29 -0
  61. package/src/screenplay/models/locators/index.ts +2 -0
  62. package/tsconfig.eslint.json +10 -0
@@ -0,0 +1,88 @@
1
+ import { CorrelationId } from '@serenity-js/core/lib/model';
2
+ import { BrowsingSession, Cookie, CookieData } from '@serenity-js/web';
3
+ import * as playwright from 'playwright-core';
4
+
5
+ import { PlaywrightOptions } from '../../PlaywrightOptions';
6
+ import { PlaywrightCookie, PlaywrightPage } from '../models';
7
+
8
+ /**
9
+ * @desc
10
+ * Playwright-specific implementation of the {@link @serenity-js/web/lib/screenplay/models~BrowsingSession}.
11
+ *
12
+ * @see {@link @serenity-js/web/lib/screenplay/models~BrowsingSession}
13
+ */
14
+ export class PlaywrightBrowsingSession extends BrowsingSession<PlaywrightPage> {
15
+
16
+ private currentPlaywrightBrowserContext: playwright.BrowserContext;
17
+
18
+ constructor(
19
+ protected readonly browser: playwright.Browser,
20
+ protected readonly browserContextOptions: PlaywrightOptions,
21
+ ) {
22
+ super();
23
+ }
24
+
25
+ protected async registerCurrentPage(): Promise<PlaywrightPage> {
26
+ const context = await this.browserContext();
27
+
28
+ await context.newPage();
29
+
30
+ // calling context.newPage() triggers a callback registered via browserContext(),
31
+ // which wraps playwright.Page in PlaywrightPage and adds it to the list of pages
32
+ // returned by this.allPages()
33
+
34
+ const allPages = await this.allPages()
35
+
36
+ return allPages[allPages.length-1];
37
+ }
38
+
39
+ /**
40
+ * @override
41
+ */
42
+ async closeAllPages(): Promise<void> {
43
+ await super.closeAllPages();
44
+
45
+ const context = await this.browserContext();
46
+ await context.close();
47
+ }
48
+
49
+ async cookie(name: string): Promise<Cookie> {
50
+ const context = await this.browserContext();
51
+ return new PlaywrightCookie(context, name);
52
+ }
53
+
54
+ async setCookie(cookie: CookieData): Promise<void> {
55
+ const context = await this.browserContext();
56
+
57
+ await context.addCookies([ cookie ]);
58
+ }
59
+
60
+ async deleteAllCookies(): Promise<void> {
61
+ const context = await this.browserContext()
62
+ await context.clearCookies();
63
+ }
64
+
65
+ private async browserContext(): Promise<playwright.BrowserContext> {
66
+ if (! this.currentPlaywrightBrowserContext) {
67
+ this.currentPlaywrightBrowserContext = await this.browser.newContext(this.browserContextOptions);
68
+
69
+ this.currentPlaywrightBrowserContext.on('page', async page => {
70
+ await page.waitForLoadState();
71
+
72
+ this.register(
73
+ new PlaywrightPage(this, page, this.browserContextOptions, CorrelationId.create())
74
+ );
75
+ });
76
+
77
+ if (this.browserContextOptions?.defaultNavigationTimeout) {
78
+ this.currentPlaywrightBrowserContext.setDefaultNavigationTimeout(this.browserContextOptions?.defaultNavigationTimeout);
79
+ }
80
+
81
+ if (this.browserContextOptions?.defaultTimeout) {
82
+ this.currentPlaywrightBrowserContext.setDefaultTimeout(this.browserContextOptions?.defaultTimeout);
83
+ }
84
+ }
85
+
86
+ return this.currentPlaywrightBrowserContext;
87
+ }
88
+ }
@@ -0,0 +1,46 @@
1
+ import { Timestamp } from '@serenity-js/core';
2
+ import { Cookie, CookieData, CookieMissingError } from '@serenity-js/web';
3
+ import * as playwright from 'playwright-core';
4
+ import { ensure, isDefined } from 'tiny-types';
5
+
6
+ export class PlaywrightCookie extends Cookie {
7
+
8
+ constructor(
9
+ private readonly context: playwright.BrowserContext,
10
+ cookieName: string,
11
+ ) {
12
+ super(cookieName);
13
+ ensure('context', context, isDefined());
14
+ }
15
+
16
+ async delete(): Promise<void> {
17
+ // see https://github.com/microsoft/playwright/issues/10143
18
+ const cookies = await this.context.cookies();
19
+ await this.context.clearCookies();
20
+ await this.context.addCookies(
21
+ cookies.filter(cookie => cookie.name !== this.cookieName)
22
+ );
23
+ }
24
+
25
+ protected async read(): Promise<CookieData> {
26
+ const cookies = await this.context.cookies();
27
+
28
+ const found = cookies.find(cookie => cookie.name === this.cookieName);
29
+
30
+ if (! found) {
31
+ throw new CookieMissingError(`Cookie '${ this.cookieName }' not set`);
32
+ }
33
+
34
+ return {
35
+ name: found.name,
36
+ value: found.value,
37
+ domain: found.domain,
38
+ path: found.path,
39
+ expiry: found.expires !== undefined
40
+ ? Timestamp.fromTimestampInSeconds(Math.round(found.expires))
41
+ : undefined,
42
+ httpOnly: found.httpOnly,
43
+ secure: found.secure,
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,53 @@
1
+ import { AbsentModalDialog, AcceptedModalDialog, DismissedModalDialog, ModalDialog, ModalDialogHandler } from '@serenity-js/web';
2
+ import * as playwright from 'playwright-core';
3
+
4
+ export class PlaywrightModalDialogHandler extends ModalDialogHandler {
5
+
6
+ private readonly defaultHandler: (dialog: playwright.Dialog) => Promise<ModalDialog> =
7
+ async (dialog: playwright.Dialog) => {
8
+ await dialog.dismiss();
9
+ return new DismissedModalDialog(dialog.message());
10
+ }
11
+
12
+ private currentHandler: (dialog: playwright.Dialog) => Promise<ModalDialog>;
13
+
14
+ constructor(private readonly page: playwright.Page) {
15
+ super();
16
+
17
+ this.currentHandler = this.defaultHandler;
18
+
19
+ this.page.on('dialog', async (dialog: playwright.Dialog) => {
20
+ this.modalDialog = await this.currentHandler(dialog);
21
+ });
22
+ }
23
+
24
+ async acceptNext(): Promise<void> {
25
+ this.currentHandler = async (dialog: playwright.Dialog) => {
26
+ await dialog.accept(dialog.defaultValue());
27
+ return new AcceptedModalDialog(dialog.message());
28
+ }
29
+ }
30
+
31
+ async acceptNextWithValue(text: string | number): Promise<void> {
32
+ this.currentHandler = async (dialog: playwright.Dialog) => {
33
+ await dialog.accept(String(text));
34
+ return new AcceptedModalDialog(dialog.message());
35
+ }
36
+ }
37
+
38
+ async dismissNext(): Promise<void> {
39
+ this.currentHandler = async (dialog: playwright.Dialog) => {
40
+ await dialog.dismiss();
41
+ return new DismissedModalDialog(dialog.message());
42
+ }
43
+ }
44
+
45
+ async reset(): Promise<void> {
46
+ this.modalDialog = new AbsentModalDialog();
47
+ this.currentHandler = this.defaultHandler;
48
+ }
49
+
50
+ async discard(): Promise<void> {
51
+ this.page.off('dialog', this.currentHandler);
52
+ }
53
+ }
@@ -0,0 +1,258 @@
1
+ import { LogicError } from '@serenity-js/core';
2
+ import { CorrelationId } from '@serenity-js/core/lib/model';
3
+ import { Cookie, CookieData, Key, Page, PageElement, PageElements, Selector } from '@serenity-js/web';
4
+ import * as Buffer from 'buffer';
5
+ import * as playwright from 'playwright-core';
6
+ import * as structs from 'playwright-core/types/structs';
7
+ import { URL } from 'url';
8
+
9
+ import { PlaywrightOptions } from '../../PlaywrightOptions';
10
+ import { PlaywrightLocator, PlaywrightRootLocator } from './locators';
11
+ import { PlaywrightBrowsingSession } from './PlaywrightBrowsingSession';
12
+ import { PlaywrightModalDialogHandler } from './PlaywrightModalDialogHandler';
13
+ import { PlaywrightPageElement } from './PlaywrightPageElement';
14
+
15
+ /**
16
+ * @desc
17
+ * Playwright-specific implementation of the {@link @serenity-js/web/lib/screenplay/models~Page}.
18
+ *
19
+ * @see {@link @serenity-js/web/lib/screenplay/models~Page}
20
+ */
21
+ export class PlaywrightPage extends Page<playwright.ElementHandle> {
22
+ /**
23
+ * @private
24
+ */
25
+ private lastScriptExecutionSummary: LastScriptExecutionSummary;
26
+
27
+ constructor(
28
+ session: PlaywrightBrowsingSession,
29
+ private readonly page: playwright.Page,
30
+ private readonly options: PlaywrightOptions,
31
+ pageId: CorrelationId,
32
+ ) {
33
+ super(
34
+ session,
35
+ new PlaywrightRootLocator(page),
36
+ new PlaywrightModalDialogHandler(page),
37
+ pageId
38
+ );
39
+ }
40
+
41
+ locate(selector: Selector): PageElement<playwright.ElementHandle> {
42
+ return new PlaywrightPageElement(
43
+ new PlaywrightLocator(
44
+ this.rootLocator,
45
+ selector,
46
+ ),
47
+ );
48
+ }
49
+
50
+ locateAll(selector: Selector): PageElements<playwright.ElementHandle> {
51
+ return new PageElements(
52
+ new PlaywrightLocator(
53
+ this.rootLocator,
54
+ selector
55
+ )
56
+ );
57
+ }
58
+
59
+ async navigateTo(destination: string): Promise<void> {
60
+ await this.page.goto(destination, { waitUntil: this.options?.defaultNavigationWaitUntil });
61
+ await this.resetState();
62
+ }
63
+
64
+ async navigateBack(): Promise<void> {
65
+ await this.page.goBack({ waitUntil: this.options?.defaultNavigationWaitUntil });
66
+ await this.resetState();
67
+ }
68
+
69
+ async navigateForward(): Promise<void> {
70
+ await this.page.goForward({ waitUntil: this.options?.defaultNavigationWaitUntil });
71
+ await this.resetState();
72
+ }
73
+
74
+ async reload(): Promise<void> {
75
+ await this.page.reload({ waitUntil: this.options?.defaultNavigationWaitUntil });
76
+ await this.resetState();
77
+ }
78
+
79
+ async sendKeys(keys: (string | Key)[]): Promise<void> {
80
+ const keySequence = keys.map(key => {
81
+ if (! Key.isKey(key)) {
82
+ return key;
83
+ }
84
+
85
+ return key.devtoolsName;
86
+ });
87
+
88
+ await this.page.keyboard.press(
89
+ keySequence.join('+'),
90
+ );
91
+ }
92
+
93
+ async executeScript<Result, InnerArguments extends any[]>(script: string | ((...parameters: InnerArguments) => Result), ...args: InnerArguments): Promise<Result> {
94
+
95
+ const nativeArguments = await Promise.all(
96
+ args.map(arg =>
97
+ arg instanceof PlaywrightPageElement
98
+ ? arg.nativeElement()
99
+ : arg
100
+ )
101
+ ) as InnerArguments;
102
+
103
+ const serialisedScript = typeof script === 'function'
104
+ ? String(script)
105
+ : String(`function script() { ${ script } }`);
106
+
107
+ const result = await this.page.evaluate<Result, typeof nativeArguments>(
108
+ new Function(`
109
+ const parameters = arguments[0];
110
+ return (${ serialisedScript }).apply(null, parameters);
111
+ `) as structs.PageFunction<typeof nativeArguments, Result>,
112
+ nativeArguments
113
+ );
114
+
115
+ this.lastScriptExecutionSummary = new LastScriptExecutionSummary(
116
+ result,
117
+ );
118
+
119
+ return result;
120
+ }
121
+
122
+ async executeAsyncScript<Result, Parameters extends any[]>(script: string | ((...args: [...parameters: Parameters, callback: (result: Result) => void]) => void), ...args: Parameters): Promise<Result> {
123
+
124
+ const nativeArguments = await Promise.all(
125
+ args.map(arg =>
126
+ arg instanceof PlaywrightPageElement
127
+ ? arg.nativeElement()
128
+ : arg
129
+ )
130
+ ) as Parameters;
131
+
132
+ const serialisedScript = typeof script === 'function'
133
+ ? String(script)
134
+ : String(`function script() { ${ script } }`);
135
+
136
+ const result = await this.page.evaluate<Result, typeof nativeArguments>(
137
+ new Function(`
138
+ const parameters = arguments[0];
139
+
140
+ return new Promise((resolve, reject) => {
141
+ try {
142
+ return (${ serialisedScript }).apply(null, parameters.concat(resolve));
143
+ } catch (error) {
144
+ return reject(error);
145
+ }
146
+ })
147
+ `) as structs.PageFunction<typeof nativeArguments, Result>,
148
+ nativeArguments
149
+ );
150
+
151
+ this.lastScriptExecutionSummary = new LastScriptExecutionSummary(
152
+ result,
153
+ );
154
+
155
+ return result;
156
+ }
157
+
158
+ /**
159
+ * @desc
160
+ * Returns the last result of calling {@link BrowseTheWebWithPlaywright#executeAsyncScript}
161
+ * or {@link BrowseTheWebWithPlaywright#executeScript}
162
+ *
163
+ * @returns {any}
164
+ */
165
+ lastScriptExecutionResult<Result = any>(): Result {
166
+ if (! this.lastScriptExecutionSummary) {
167
+ throw new LogicError(`Make sure to execute a script before checking on the result`);
168
+ }
169
+
170
+ return this.lastScriptExecutionSummary.result as Result;
171
+ }
172
+
173
+ async takeScreenshot(): Promise<string> {
174
+ const screenshot: Buffer = await this.page.screenshot();
175
+
176
+ return screenshot.toString('base64');
177
+ }
178
+
179
+ async cookie(name: string): Promise<Cookie> {
180
+ return (this.session as PlaywrightBrowsingSession).cookie(name);
181
+ }
182
+
183
+ async setCookie(cookieData: CookieData): Promise<void> {
184
+ const url = await this.page.url();
185
+
186
+ const cookie = {
187
+ name: cookieData.name,
188
+ value: cookieData.value,
189
+ domain: cookieData.domain,
190
+ path: cookieData.path,
191
+ url: !(cookieData.domain && cookieData.path)
192
+ ? url
193
+ : undefined,
194
+ secure: cookieData.secure,
195
+ httpOnly: cookieData.httpOnly,
196
+ expires: cookieData.expiry
197
+ ? cookieData.expiry.toSeconds()
198
+ : undefined,
199
+ sameSite: cookieData.sameSite,
200
+ };
201
+
202
+ return (this.session as PlaywrightBrowsingSession).setCookie(cookie);
203
+ }
204
+
205
+ async deleteAllCookies(): Promise<void> {
206
+ await (this.session as PlaywrightBrowsingSession).deleteAllCookies();
207
+ }
208
+
209
+ title(): Promise<string> {
210
+ return this.page.title();
211
+ }
212
+
213
+ name(): Promise<string> {
214
+ return this.page.evaluate(
215
+ /* istanbul ignore next */
216
+ () => window.name
217
+ );
218
+ }
219
+
220
+ async url(): Promise<URL> {
221
+ return new URL(this.page.url());
222
+ }
223
+
224
+ async viewportSize(): Promise<{ width: number, height: number }> {
225
+ return this.page.viewportSize();
226
+ }
227
+
228
+ setViewportSize(size: { width: number, height: number }): Promise<void> {
229
+ return this.page.setViewportSize(size);
230
+ }
231
+
232
+ async close(): Promise<void> {
233
+ await this.resetState();
234
+ await (this.modalDialogHandler as PlaywrightModalDialogHandler).discard();
235
+ await this.page.close();
236
+ }
237
+
238
+ async closeOthers(): Promise<void> {
239
+ await this.session.closePagesOtherThan(this);
240
+ }
241
+
242
+ async isPresent(): Promise<boolean> {
243
+ return ! this.page.isClosed();
244
+ }
245
+
246
+ private async resetState() {
247
+ this.lastScriptExecutionSummary = undefined;
248
+ await this.rootLocator.switchToMainFrame()
249
+ await this.modalDialogHandler.reset();
250
+ }
251
+ }
252
+
253
+ /**
254
+ * @package
255
+ */
256
+ class LastScriptExecutionSummary<Result = any> {
257
+ constructor(public readonly result: Result) {}
258
+ }
@@ -0,0 +1,235 @@
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 * as playwright from 'playwright-core';
5
+ import { ensure, isDefined } from 'tiny-types';
6
+
7
+ import { PlaywrightLocator } from './locators';
8
+
9
+ export class PlaywrightPageElement extends PageElement<playwright.ElementHandle> {
10
+ of(parent: PageElement<playwright.ElementHandle>): PageElement<playwright.ElementHandle> {
11
+ return new PlaywrightPageElement(this.locator.of(parent.locator));
12
+ }
13
+
14
+ async enterValue(value: string | number | Array<string | number>): Promise<void> {
15
+ const text = [].concat(value).join('');
16
+
17
+ const element = await this.nativeElement();
18
+ return element.fill(text);
19
+ }
20
+
21
+ async clearValue(): Promise<void> {
22
+ try {
23
+ const element = await this.nativeElement();
24
+ await element.fill('');
25
+ }
26
+ catch(error) {
27
+ throw new LogicError(`The input field doesn't seem to have a 'value' attribute that could be cleared.`, error);
28
+ }
29
+ }
30
+
31
+ async click(): Promise<void> {
32
+ const element = await this.nativeElement();
33
+ return element.click();
34
+ }
35
+
36
+ async doubleClick(): Promise<void> {
37
+ const element = await this.nativeElement();
38
+ return element.dblclick();
39
+ }
40
+
41
+ async scrollIntoView(): Promise<void> {
42
+ const element = await this.nativeElement();
43
+ return element.scrollIntoViewIfNeeded();
44
+ }
45
+
46
+ async hoverOver(): Promise<void> {
47
+ const element = await this.nativeElement();
48
+ return element.hover();
49
+ }
50
+
51
+ async rightClick(): Promise<void> {
52
+ const element = await this.nativeElement();
53
+ return element.click({ button: 'right' });
54
+ }
55
+
56
+ async selectOptions(...options: Array<SelectOption>): Promise<void> {
57
+ const element = await this.nativeElement();
58
+
59
+ const optionsToSelect = options.map(option =>
60
+ ({
61
+ value: option.value,
62
+ label: option.label,
63
+ })
64
+ );
65
+
66
+ await element.selectOption(optionsToSelect);
67
+ }
68
+
69
+ async selectedOptions(): Promise<Array<SelectOption>> {
70
+ const element = await this.nativeElement();
71
+
72
+ const options = await element.$$eval(
73
+ 'option',
74
+ /* istanbul ignore next */
75
+ (optionNodes: Array<HTMLOptionElement>) =>
76
+ optionNodes.map((optionNode: HTMLOptionElement) => {
77
+ return {
78
+ selected: optionNode.selected,
79
+ disabled: optionNode.disabled,
80
+ label: optionNode.label,
81
+ value: optionNode.value,
82
+ }
83
+ })
84
+ );
85
+
86
+ return options.map(option =>
87
+ new SelectOption(option.label, option.value, option.selected, option.disabled)
88
+ );
89
+ }
90
+
91
+ async attribute(name: string): Promise<string> {
92
+ const element = await this.nativeElement();
93
+ return element.getAttribute(name);
94
+ }
95
+
96
+ async text(): Promise<string> {
97
+ const element = await this.nativeElement();
98
+ return element.innerText(); // eslint-disable-line unicorn/prefer-dom-node-text-content
99
+ }
100
+
101
+ async value(): Promise<string> {
102
+ const element = await this.nativeElement();
103
+ return element.inputValue();
104
+ }
105
+
106
+ async switchTo(): Promise<SwitchableOrigin> {
107
+ try {
108
+ const element = await this.nativeElement();
109
+
110
+ const frame = await element.contentFrame();
111
+
112
+ if (frame) {
113
+ const locator = (this.locator as PlaywrightLocator);
114
+
115
+ await locator.switchToFrame(element);
116
+
117
+ return {
118
+ switchBack: async (): Promise<void> => {
119
+ await locator.switchToParentFrame();
120
+ }
121
+ }
122
+ }
123
+
124
+ const previouslyFocusedElement = await element.evaluateHandle(
125
+ /* istanbul ignore next */
126
+ (domNode: HTMLElement) => {
127
+ const currentlyFocusedElement = document.activeElement;
128
+ domNode.focus();
129
+ return currentlyFocusedElement;
130
+ }
131
+ );
132
+
133
+ return new PreviouslyFocusedElementSwitcher(previouslyFocusedElement);
134
+ } catch(error) {
135
+ throw new LogicError(`Couldn't switch to page element located ${ this.locator }`, error);
136
+ }
137
+ }
138
+
139
+ async isActive(): Promise<boolean> {
140
+ try {
141
+ const element = await this.nativeElement();
142
+ return element.evaluate(
143
+ /* istanbul ignore next */
144
+ domNode => domNode === document.activeElement
145
+ );
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ async isClickable(): Promise<boolean> {
152
+ try {
153
+ const element = await this.nativeElement();
154
+ await element.click({ trial: true });
155
+
156
+ return true;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+
162
+ async isEnabled(): Promise<boolean> {
163
+ try {
164
+ const element = await this.nativeElement();
165
+ return element.isEnabled();
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ async isPresent(): Promise<boolean> {
172
+ try {
173
+ const element = await this.nativeElement();
174
+ return element !== null;
175
+ }
176
+ catch(error) {
177
+ if (error.name === 'TimeoutError') {
178
+ return false;
179
+ }
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ async isSelected(): Promise<boolean> {
185
+
186
+ try {
187
+ const element: playwright.ElementHandle = await this.nativeElement();
188
+
189
+ // works for <option />
190
+ const selected = await element.getAttribute('selected');
191
+ if (selected !== null) {
192
+ return true;
193
+ }
194
+
195
+ // works only for checkboxes and radio buttons, throws for other elements
196
+ return await element.isChecked();
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+
202
+ async isVisible(): Promise<boolean> {
203
+ try {
204
+ const element = await this.nativeElement();
205
+
206
+ const isVisible = await element.isVisible();
207
+ if (! isVisible) {
208
+ return false;
209
+ }
210
+
211
+ return await element.evaluate(scripts.isVisible);
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * @private
220
+ */
221
+ class PreviouslyFocusedElementSwitcher implements SwitchableOrigin {
222
+ constructor(private readonly node: playwright.JSHandle) {
223
+ ensure('DOM element', node, isDefined());
224
+ }
225
+
226
+ async switchBack (): Promise<void> {
227
+ await this.node.evaluate(
228
+ /* istanbul ignore next */
229
+ (domNode: HTMLElement) => {
230
+ domNode.focus();
231
+ },
232
+ this.node
233
+ );
234
+ }
235
+ }
@@ -0,0 +1,5 @@
1
+ export * from './locators';
2
+ export * from './PlaywrightBrowsingSession';
3
+ export * from './PlaywrightCookie';
4
+ export * from './PlaywrightPage';
5
+ export * from './PlaywrightPageElement';