UNPKG

@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
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