@serenity-js/playwright
Version:
Adapter that integrates @serenity-js/web with Playwright, enabling Serenity/JS reporting and using the Screenplay Pattern to write component and end-to-end test scenarios
190 lines (189 loc) • 7.12 kB
JavaScript
import { URL } from 'node:url';
import { List, LogicError } from '@serenity-js/core';
import { ArgumentDehydrator, BrowserWindowClosedError, ByDeepCss, Key, Page, PageElement, PageElementsLocator } from '@serenity-js/web';
import * as scripts from '@serenity-js/web/scripts';
import { promised } from '../promised.js';
import { PlaywrightExistingElementLocator, PlaywrightLocator, PlaywrightRootLocator } from './locators/index.js';
import { PlaywrightModalDialogHandler } from './PlaywrightModalDialogHandler.js';
import { PlaywrightPageElement } from './PlaywrightPageElement.js';
/**
* Playwright-specific implementation of [`Page`](https://serenity-js.org/api/web/class/Page/).
*
* @group Models
*/
export class PlaywrightPage extends Page {
page;
options;
lastScriptExecutionSummary;
dehydrator = new ArgumentDehydrator((item) => item instanceof PageElement, async (item) => {
const nativeElement = await item.nativeElement();
return nativeElement.elementHandle();
});
static current() {
return super.current();
}
constructor(session, page, options, pageId) {
super(session, new PlaywrightRootLocator(page), new PlaywrightModalDialogHandler(page), pageId);
this.page = page;
this.options = options;
}
createPageElement(nativeElement) {
return new PlaywrightPageElement(new PlaywrightExistingElementLocator(this.rootLocator, new ByDeepCss(nativeElement._selector), nativeElement));
}
locate(selector) {
return new PlaywrightPageElement(new PlaywrightLocator(this.rootLocator, selector));
}
locateAll(selector) {
return List.of(new PageElementsLocator(new PlaywrightLocator(this.rootLocator, selector)));
}
async navigateTo(destination) {
await this.page.goto(destination, { waitUntil: this.options?.defaultNavigationWaitUntil });
await this.resetState();
}
async navigateBack() {
await this.page.goBack({ waitUntil: this.options?.defaultNavigationWaitUntil });
await this.resetState();
}
async navigateForward() {
await this.page.goForward({ waitUntil: this.options?.defaultNavigationWaitUntil });
await this.resetState();
}
async reload() {
await this.page.reload({ waitUntil: this.options?.defaultNavigationWaitUntil });
await this.resetState();
}
async sendKeys(keys) {
const keySequence = keys.map(key => {
if (!Key.isKey(key)) {
return key;
}
return key.devtoolsName;
});
await this.page.keyboard.press(keySequence.join('+'));
}
async executeScript(script, ...args) {
const serialisedScript = typeof script === 'function'
? String(script)
: String(`function script() { ${script} }`);
const executableScript = new Function(`
const parameters = (${scripts.rehydrate}).apply(null, arguments[0]);
return (${serialisedScript}).apply(null, parameters);
`);
const dehydratedArguments = await this.dehydrator.dehydrate(args);
const result = await this.rootLocator.evaluate(executableScript, dehydratedArguments);
this.lastScriptExecutionSummary = new LastScriptExecutionSummary(result);
return result;
}
async executeAsyncScript(script, ...args) {
const serialisedScript = typeof script === 'function'
? String(script)
: String(`function script() { ${script} }`);
const executableScript = new Function(`
const parameters = (${scripts.rehydrate}).apply(null, arguments[0]);
return new Promise((resolve, reject) => {
try {
return (${serialisedScript}).apply(null, parameters.concat(resolve));
} catch (error) {
return reject(error);
}
})
`);
const dehydratedArguments = await this.dehydrator.dehydrate(args);
const result = await this.rootLocator.evaluate(executableScript, dehydratedArguments);
this.lastScriptExecutionSummary = new LastScriptExecutionSummary(result);
return result;
}
lastScriptExecutionResult() {
if (!this.lastScriptExecutionSummary) {
throw new LogicError(`Make sure to execute a script before checking on the result`);
}
return this.lastScriptExecutionSummary.result;
}
async takeScreenshot() {
try {
const screenshot = await this.page.screenshot();
return screenshot.toString('base64');
}
catch (error) {
if (error?.message.includes('Target page, context or browser has been closed')) {
throw new BrowserWindowClosedError(`Couldn't take screenshot since the browser window is already closed`, error);
}
throw error;
}
}
async cookie(name) {
return this.session.cookie(name);
}
async setCookie(cookieData) {
const url = await this.page.url();
const cookie = {
name: cookieData.name,
value: cookieData.value,
domain: cookieData.domain,
path: cookieData.path,
url: !(cookieData.domain && cookieData.path)
? url
: undefined,
secure: cookieData.secure,
httpOnly: cookieData.httpOnly,
expires: cookieData.expiry
? cookieData.expiry.toSeconds()
: undefined,
sameSite: cookieData.sameSite,
};
return this.session.setCookie(cookie);
}
async deleteAllCookies() {
await this.session.deleteAllCookies();
}
async title() {
const currentFrame = await this.currentFrame();
return currentFrame.title();
}
async name() {
const currentFrame = await this.currentFrame();
return currentFrame.evaluate(() => window.name);
}
async url() {
const currentFrame = await this.currentFrame();
return new URL(currentFrame.url());
}
async viewportSize() {
return this.page.viewportSize();
}
async setViewportSize(size) {
await this.page.setViewportSize(size);
}
async close() {
await this.resetState();
await this.modalDialogHandler.discard();
await this.page.close();
}
async closeOthers() {
await this.session.closePagesOtherThan(this);
}
isPresent() {
return promised(!this.page.isClosed());
}
async nativePage() {
return promised(this.page);
}
async resetState() {
this.lastScriptExecutionSummary = undefined;
await this.rootLocator.switchToMainFrame();
await this.modalDialogHandler.reset();
}
async currentFrame() {
return await this.rootLocator.nativeElement();
}
}
/**
* @package
*/
class LastScriptExecutionSummary {
result;
constructor(result) {
this.result = result;
}
}
//# sourceMappingURL=PlaywrightPage.js.map