@serenity-js/protractor
Version:
Adapter that integrates @serenity-js/web with Protractor, enabling Serenity/JS reporting and using the Screenplay Pattern to write end-to-end test scenarios
321 lines (320 loc) • 13.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtractorPage = void 0;
const node_url_1 = require("node:url");
const core_1 = require("@serenity-js/core");
const web_1 = require("@serenity-js/web");
const scripts = __importStar(require("@serenity-js/web/lib/scripts"));
const promised_1 = require("../promised");
const locators_1 = require("./locators");
const ProtractorSelectors_1 = require("./locators/ProtractorSelectors");
const ProtractorCookie_1 = require("./ProtractorCookie");
const ProtractorPageElement_1 = require("./ProtractorPageElement");
/**
* Protractor-specific implementation of [`Page`](https://serenity-js.org/api/web/class/Page/).
*
* @group Models
*/
class ProtractorPage extends web_1.Page {
browser;
errorHandler;
lastScriptExecutionSummary;
/* eslint-disable unicorn/consistent-function-scoping */
dehydrator = new web_1.ArgumentDehydrator((item) => item instanceof web_1.PageElement, async (item) => {
const nativeElement = await item.nativeElement();
return nativeElement.getWebElement();
});
/* eslint-enable */
constructor(session, browser, modalDialogHandler, errorHandler, pageId) {
super(session, new locators_1.ProtractorRootLocator(browser), modalDialogHandler, pageId);
this.browser = browser;
this.errorHandler = errorHandler;
}
createPageElement(nativeElement) {
return new ProtractorPageElement_1.ProtractorPageElement(new locators_1.ProtractorExistingElementLocator(this.rootLocator, ProtractorSelectors_1.ProtractorSelectors.selectorFrom(nativeElement.locator()), this.errorHandler, nativeElement));
}
locate(selector) {
return new ProtractorPageElement_1.ProtractorPageElement(new locators_1.ProtractorLocator(this.rootLocator, selector, this.errorHandler));
}
locateAll(selector) {
return core_1.List.of(new web_1.PageElementsLocator(new locators_1.ProtractorLocator(this.rootLocator, selector, this.errorHandler)));
}
/**
* If set to `false`, Protractor will not wait for Angular 1.x `$http` and `$timeout`
* tasks to complete before interacting with the browser.
*
* This can be useful when:
* - you need to switch to a non-Angular app during your tests, e.g. to sign in using an SSO gateway
* - your app continuously polls an API with `$timeout`
*
* If you're not testing an Angular app, it's better to disable Angular synchronisation completely
* in protractor configuration:
*
* ```js
* // protractor.conf.js
* exports.config = {
* onPrepare: function () {
* return browser.waitForAngularEnabled(false)
* },
*
* // ... other config
* }
* ```
*
* @param enable
*/
async enableAngularSynchronisation(enable) {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.waitForAngularEnabled(enable));
});
}
async navigateTo(destination) {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.get(destination));
});
}
async navigateBack() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.navigate().back());
});
}
async navigateForward() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.navigate().forward());
});
}
async reload() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.navigate().refresh());
});
}
async sendKeys(keys) {
function isModifier(maybeKey) {
return web_1.Key.isKey(maybeKey) && maybeKey.isModifier;
}
function asCodePoint(maybeKey) {
if (!web_1.Key.isKey(maybeKey)) {
return maybeKey;
}
return maybeKey.utf16codePoint;
}
return await this.inContextOfThisPage(() => {
// keyDown for any modifier keys and sendKeys otherwise
const keyDownActions = keys.reduce((actions, key) => {
return isModifier(key)
? actions.keyDown(asCodePoint(key))
: actions.sendKeys(asCodePoint(key));
}, this.browser.actions());
// keyUp for any modifier keys, ignore for regular keys
const keyUpActions = keys.reduce((actions, key) => {
return isModifier(key)
? actions.keyUp(asCodePoint(key))
: actions;
}, keyDownActions);
return (0, promised_1.promised)(keyUpActions.perform());
});
}
async executeScript(script, ...args) {
const serialisedScript = typeof script === 'function'
? String(script)
: String(`function script() { ${script} }`);
const executableScript = new Function(`
var parameters = (${scripts.rehydrate}).apply(null, arguments);
return (${serialisedScript}).apply(null, parameters);
`);
const result = await this.inContextOfThisPage(async () => {
const dehydratedArguments = await this.dehydrator.dehydrate(args);
return (0, promised_1.promised)(this.browser.executeScript(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(`
var args = Array.prototype.slice.call(arguments, 0, -1);
var callback = arguments[arguments.length - 1];
var parameters = (${scripts.rehydrate}).apply(null, args);
(${serialisedScript}).apply(null, parameters.concat(callback));
`);
const result = await this.inContextOfThisPage(async () => {
const dehydratedArguments = await this.dehydrator.dehydrate(args);
return (0, promised_1.promised)(this.browser.executeAsyncScript(executableScript, ...dehydratedArguments));
});
this.lastScriptExecutionSummary = new LastScriptExecutionSummary(result);
return result;
}
lastScriptExecutionResult() {
if (!this.lastScriptExecutionSummary) {
throw new core_1.LogicError(`Make sure to execute a script before checking on the result`);
}
// Selenium 3 returns `null` when the script it executed returns `undefined`
// so we're mapping the result back.
return this.lastScriptExecutionSummary.result === null
? undefined
: this.lastScriptExecutionSummary.result;
}
async takeScreenshot() {
return await this.inContextOfThisPage(() => {
try {
return (0, promised_1.promised)(this.browser.takeScreenshot());
}
catch (error) {
if (error.name && error.name === 'NoSuchSessionError') {
throw new web_1.BrowserWindowClosedError('Browser window is not available to take a screenshot', error);
}
throw error;
}
});
}
async cookie(name) {
return new ProtractorCookie_1.ProtractorCookie(this.browser, name);
}
async setCookie(cookieData) {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.manage().addCookie({
name: cookieData.name,
value: cookieData.value,
path: cookieData.path,
domain: cookieData.domain,
secure: cookieData.secure,
httpOnly: cookieData.httpOnly,
expiry: cookieData.expiry
? cookieData.expiry.toSeconds()
: undefined,
}));
});
}
async deleteAllCookies() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.manage().deleteAllCookies());
});
}
async title() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.executeScript('return document.title'));
});
}
async name() {
return await this.inContextOfThisPage(() => {
return (0, promised_1.promised)(this.browser.executeScript('return window.name'));
});
}
async url() {
return await this.inContextOfThisPage(async () => {
return new node_url_1.URL(await (0, promised_1.promised)(this.browser.executeScript('return window.location.href')));
});
}
async viewportSize() {
return await this.inContextOfThisPage(async () => {
const calculatedViewportSize = await (0, promised_1.promised)(this.browser.executeScript(`return {
width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
};`));
if (calculatedViewportSize.width > 0 && calculatedViewportSize.height > 0) {
return calculatedViewportSize;
}
// Chrome headless hard-codes window.innerWidth and window.innerHeight to 0
return await (0, promised_1.promised)(this.browser.manage().window().getSize());
});
}
async setViewportSize(size) {
return await this.inContextOfThisPage(async () => {
const desiredWindowSize = await (0, promised_1.promised)(this.browser.executeScript(`
var currentViewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
var currentViewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
return {
width: Math.max(window.outerWidth - currentViewportWidth + ${size.width}, ${size.width}),
height: Math.max(window.outerHeight - currentViewportHeight + ${size.height}, ${size.height}),
};
`));
return (0, promised_1.promised)(this.browser.manage().window().setSize(desiredWindowSize.width, desiredWindowSize.height));
});
}
async close() {
try {
await this.inContextOfThisPage(async () => {
await (0, promised_1.promised)(this.browser.close());
});
}
catch (error) {
if (error.name !== 'NoSuchWindowError') {
throw error;
}
}
}
async closeOthers() {
await this.session.closePagesOtherThan(this);
}
async isPresent() {
const allPages = await this.session.allPages();
for (const page of allPages) {
if (page === this) {
return true;
}
}
return false;
}
async inContextOfThisPage(action) {
let originalPage;
try {
originalPage = await this.session.currentPage();
await this.session.changeCurrentPageTo(this);
return await action();
}
catch (error) {
return await this.errorHandler.executeIfHandled(error, action);
}
finally {
if (originalPage) {
await this.session.changeCurrentPageTo(originalPage);
}
}
}
}
exports.ProtractorPage = ProtractorPage;
/**
* @package
*/
class LastScriptExecutionSummary {
result;
constructor(result) {
this.result = result;
}
}
//# sourceMappingURL=ProtractorPage.js.map