UNPKG

@cuppet/core

Version:

Core testing framework components for Cuppet - BDD framework based on Cucumber and Puppeteer

289 lines (274 loc) 9.65 kB
/** * @module mainFunctions * @typedef {import('puppeteer').Page} Page * @typedef {import('puppeteer').Browser} Browser */ const config = require('config'); module.exports = { /** * Prepare the URL using the config to get the domain and then * based on the path variable to either generate a full path or replace it * when path is absolute URL. * @param path * @returns {Promise<string>} */ prepareUrl: async function (path) { if (path.startsWith('http')) { return path; } let baseUrl = config.get('credentials.baseUrl').toString(); if (baseUrl.endsWith('/')) { baseUrl = baseUrl.slice(0, -1); } if (path === '/' || path === 'homepage' || path === 'home') { return baseUrl; } return baseUrl + path; }, /** * Return current page URL - absolute or relative * @param {Page} page * @param {boolean} absolute - set to true if you want to extract the full path (with domain) * @returns {string} */ extractPath: function (page, absolute = false) { const url = new URL(page.url()); const href = url.href; const origin = url.origin; if (absolute) { return href; } return href.replace(origin, ''); }, /** * Visit a certain URL. Can be relative or absolute. * The step also supports auto select of a cookie consent if configured. * @param {Page} page * @param path * @returns {Promise<void>} */ visitPath: async function (page, path) { const url = await this.prepareUrl(path); try { await page.goto(url, { waitUntil: 'domcontentloaded' }); } catch (error) { throw new Error(`The requested page cannot be opened!: ${error}`); } if (config.has('blockingCookie')) { const selector = config.get('blockingCookie'); await new Promise(function (resolve) { setTimeout(resolve, 1000); }); const cookie = await page.$(selector); if (cookie) { await cookie.click(); } } }, /** * Visit current page concatenated with another path. * Example localhost.com/listing -> localhost.com/listing/page-1 * @param {Page} page * @param plus * @returns {Promise<void>} */ visitCurrentPathPlus: async function (page, plus) { if (!plus.startsWith('/')) { throw new Error('The path alias must start with a slash!'); } let path = page.url(); if (path.endsWith('/')) { path = path.slice(0, -1); } await page.goto(path + plus, { waitUntil: 'domcontentloaded' }); }, /** * Reload the current page * @param {Page} page * @returns {Promise<void>} */ reloadPage: async function (page) { await page.reload({ waitUntil: 'domcontentloaded' }); }, /** * Reload the current page and add get params * @param {Page} page * @param params * @returns {Promise<void>} */ reloadPageWithParams: async function (page, params) { const currentUrl = this.extractPath(page); if (!params.startsWith('?')) { throw new Error("Invalid get param provided. Use '?' as first character."); } const newPath = currentUrl + params; await this.visitPath(page, newPath); }, /** * Validate whether the current page is the one you should be on * @param {Page} page * @param path * @returns {void} */ validatePath: function (page, path) { const pathAlias = this.extractPath(page); if (pathAlias !== path) { throw new Error(`The current path ${pathAlias} does not match the expected: ${path}!`); } }, /** * Validate the last path in an alias * @param {Page} page * @param path * @returns {void} */ validatePathEnding: function (page, path) { let pathAlias = this.extractPath(page); if (pathAlias.endsWith('/')) { pathAlias = pathAlias.slice(0, -1); } const splitAlias = pathAlias.split('/'); let lastElement; if (Array.isArray(splitAlias)) { lastElement = splitAlias[splitAlias.length - 1]; } if (lastElement !== path) { throw new Error(`The last path alias of ${pathAlias} does not match the expected: ${path}!`); } }, /** * Validate http response code * @param {Page} page * @param code * @param path * @returns {Promise<void>} */ validateStatusCode: async function (page, code, path) { let baseUrl = config.get('credentials.baseUrl'); if (baseUrl.endsWith('/')) { baseUrl = baseUrl.slice(0, -1); } const response = await page.goto(baseUrl + path); const statusCode = response.status(); if (statusCode !== Number(code)) { throw new Error(`The status code of the response: ${statusCode} does not match the expected!`); } }, /** * Open new tab and switch to it (manually open tab and load a page) * @param {Browser} browser * @param url * @returns {Promise<Object>} */ openNewTab: async function (browser, url) { const page = await browser.newPage(); await this.visitPath(page, url); // Get all pages const pages = await browser.pages(); // Switch to the new tab return pages[pages.length - 1]; }, /** * Validate current page response headers. * @param {Page} page * @param header * @param value * @returns {Promise<void>} */ validatePageResponseHeaders: async function (page, header, value) { const refreshPage = await page.reload({ waitUntil: 'domcontentloaded' }); const responseHeaders = refreshPage.headers(); if (responseHeaders[header.toLowerCase()] !== value) { throw new Error('Response headers do not match the requirement!'); } }, /** * Verify cookie existence by name * @param {object} page * @param {string} cookieName * @param {boolean} presence * @returns {Promise<void>} */ verifyCookiePresence: async function (page, cookieName, presence) { let result; const jsCode = `(function (name) { var dc = document.cookie; var prefix = name + '='; var begin = dc.indexOf('; ' + prefix); if (begin == -1) { begin = dc.indexOf(prefix); if (begin != 0) return null; } else { begin += 2; var end = document.cookie.indexOf(';', begin); if (end == -1) { end = dc.length; } } return decodeURI(dc.substring(begin + prefix.length, end)); })('${cookieName}');`; try { result = await page.evaluate(jsCode); } catch (error) { throw new Error(`There was an error when evaluating the code. ${error}`); } if (result) { if (!presence) { throw new Error(`The cookie ${cookieName} is present, but it shouldn't!`); } } else if (!result) { if (presence) { throw new Error(`The cookie ${cookieName} is not present, but it should!`); } } }, /** * Sets the viewport of the given page to match the specified device's dimensions. * * @param {Object} page - The page object where the viewport should be set. * @param {string} device - The name of the device whose viewport dimensions should be applied. * @throws {Error} Throws an error if the specified device is not defined in the configuration. */ setViewport: async function (page, device) { const viewport = config.get('viewport'); if (!viewport[device]) { throw new Error( `Viewport for device "${device}" is not defined in config.\nAvailable devices are: ${Object.keys(viewport).join(', ')}` ); } await page.setViewport({ width: viewport[device]['width'], height: viewport[device]['height'], }); }, /** * Handle browser alert dialog and validate its text * @param {Page} page * @param {boolean} accept - true to accept/confirm, false to dismiss/cancel * @param {string} expectedText - the expected text in the dialog * @returns {Promise<void>} */ handleAlert: async function (page, accept, expectedText = null) { try { page.on('dialog', async (dialog) => { if (expectedText !== null) { const actualText = dialog.message(); if (actualText !== expectedText) { throw new Error( `Alert dialog text mismatch. Expected: "${expectedText}", Got: "${actualText}"` ); } } if (accept) { await dialog.accept(); } else { await dialog.dismiss(); } }); } catch (error) { throw new Error(`Could not handle alert dialog: ${error}`); } }, };