UNPKG

@uuv/playwright

Version:

A solution to facilitate the writing and execution of E2E tests understandable by any human being using cucumber(BDD) and playwright

623 lines (622 loc) 28.5 kB
"use strict"; /** * Software Name : UUV * * SPDX-License-Identifier: MIT * * This software is distributed under the MIT License, * see the "LICENSE" file for more details * * Authors: NJAKO MOLOM Louis Fredice & SERVICAL Stanley * Software description: Make test writing fast, understandable by any human * understanding English or French. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TimeoutCookie = exports.SelectedElementCookie = exports.MockCookie = exports.CustomCookie = exports.FILTER_TYPE = exports.COOKIE_VALUE = exports.COOKIE_NAME = void 0; exports.getPageOrElement = getPageOrElement; exports.addCookie = addCookie; exports.getCookie = getCookie; exports.deleteCookieByName = deleteCookieByName; exports.findWithRoleAndName = findWithRoleAndName; exports.withinRoleAndName = withinRoleAndName; exports.withinSelector = withinSelector; exports.notFoundWithRoleAndName = notFoundWithRoleAndName; exports.findWithSelector = findWithSelector; exports.getWithContent = getWithContent; exports.getWithTestId = getWithTestId; exports.findWithTestId = findWithTestId; exports.getWithAriaLabel = getWithAriaLabel; exports.findWithAriaLabel = findWithAriaLabel; exports.findWithRoleAndNameAndContent = findWithRoleAndNameAndContent; exports.findWithRoleAndNameFocused = findWithRoleAndNameFocused; exports.findWithRoleAndNameAndChecked = findWithRoleAndNameAndChecked; exports.findWithRoleAndNameAndUnchecked = findWithRoleAndNameAndUnchecked; exports.findWithRoleAndNameAndContentDisabled = findWithRoleAndNameAndContentDisabled; exports.findWithRoleAndNameDisabled = findWithRoleAndNameDisabled; exports.findWithRoleAndNameAndContentEnabled = findWithRoleAndNameAndContentEnabled; exports.findWithRoleAndNameEnabled = findWithRoleAndNameEnabled; exports.findWithRoleAndNameAndValue = findWithRoleAndNameAndValue; exports.showAttributesInLocator = showAttributesInLocator; exports.checkTextContentLocator = checkTextContentLocator; exports.click = click; exports.clickFocusedElement = clickFocusedElement; exports.type = type; exports.typeFocusedElement = typeFocusedElement; exports.keyBoardFocusTarget = keyBoardFocusTarget; exports.getTimeout = getTimeout; exports.setTimeout = setTimeout; const test_1 = require("@playwright/test"); const runner_commons_1 = require("@uuv/runner-commons"); var COOKIE_NAME; (function (COOKIE_NAME) { COOKIE_NAME["TIMEOUT"] = "uuv.timeout"; COOKIE_NAME["SELECTED_ELEMENT"] = "uuv.withinFocusedElement"; COOKIE_NAME["MOCK_URL"] = "uuv.mockUrl"; })(COOKIE_NAME || (exports.COOKIE_NAME = COOKIE_NAME = {})); var COOKIE_VALUE; (function (COOKIE_VALUE) { COOKIE_VALUE["NOT_EXIST"] = "notExist"; })(COOKIE_VALUE || (exports.COOKIE_VALUE = COOKIE_VALUE = {})); var FILTER_TYPE; (function (FILTER_TYPE) { FILTER_TYPE["SELECTOR"] = "bySelector"; FILTER_TYPE["ROLE"] = "byRole"; FILTER_TYPE["TEXT"] = "byText"; FILTER_TYPE["ARIA_LABEL"] = "byAriaLabel"; FILTER_TYPE["TEST_ID"] = "byTestId"; FILTER_TYPE["SELECTOR_PARENT"] = "bySelectorParent"; })(FILTER_TYPE || (exports.FILTER_TYPE = FILTER_TYPE = {})); class CustomCookie { name = `${COOKIE_VALUE.NOT_EXIST.toString()}`; value = "[]"; domain = ".github.com"; expires = -1; httpOnly = false; path = "/"; sameSite = "Lax"; secure = false; isValid() { return !this.name.includes(COOKIE_VALUE.NOT_EXIST.toString()); } } exports.CustomCookie = CustomCookie; class MockCookie { name; url; verb; isConsumed = false; constructor(name, url, verb) { this.name = name; this.url = url; this.verb = verb; this.isConsumed = false; } } exports.MockCookie = MockCookie; class SelectedElementCookie { name; value; constructor(name, value) { this.name = name; this.value = value; } } exports.SelectedElementCookie = SelectedElementCookie; class TimeoutCookie { name; value; constructor(name, value) { this.name = name; this.value = value; } } exports.TimeoutCookie = TimeoutCookie; /** * Retrieves the current page or element based on the selected element cookie filters. * Applies all filters from the cookie to locate the specific element or page. * @param world - The Cucumber world object containing context and test info * @returns A Promise that resolves to either a Locator or Page instance */ async function getPageOrElement(world) { let pointer = world.page; const cookie = await getCookie(world, COOKIE_NAME.SELECTED_ELEMENT); // console.debug("cookieGetPageOrElement", cookie); if (cookie.isValid()) { const filters = JSON.parse(cookie.value); // console.debug("filters",filters); for (const filter of filters) { switch (filter.name) { case FILTER_TYPE.SELECTOR: pointer = pointer.locator(filter.value); break; case FILTER_TYPE.ARIA_LABEL: pointer = pointer.getByLabel(filter.value, { exact: true }); break; case FILTER_TYPE.ROLE: pointer = pointer.getByRole(filter.value, { exact: true }); break; case FILTER_TYPE.TEST_ID: pointer = pointer.getByTestId(filter.value); break; case FILTER_TYPE.TEXT: pointer = pointer.getByText(filter.value, { exact: true }); break; case FILTER_TYPE.SELECTOR_PARENT: pointer = pointer.locator(filter.value); break; default: break; } // console.debug("locatorGetPageOrElement", pointer, filter) await (0, test_1.expect)(pointer).toHaveCount(1); } } return pointer; } /** * Adds a cookie to the browser context with a test-specific name. * Handles different cookie types: MOCK_URL (with consumption tracking), SELECTED_ELEMENT, and TIMEOUT. * @param world - The Cucumber world object containing context * @param cookieName - The type of cookie to add (from COOKIE_NAME enum) * @param newCookie - The cookie value object to add */ async function addCookie(world, cookieName, newCookie) { // console.debug("value", value) const cookieNameStr = `${cookieName.toString()}_${world.testInfo.testId}`; const cookie = await getCookie(world, cookieName); let cookieValue = JSON.parse(cookie.value); const mockCookie = cookieValue.find(cookie => cookie.name === newCookie.name); switch (cookieName) { case COOKIE_NAME.MOCK_URL: if (mockCookie) { cookieValue.filter(cookie => cookie.name === newCookie.name).map(cookie => (cookie.isConsumed = true)); } else { cookieValue = []; cookieValue.push(newCookie); } // console.debug("cookieValueJSON", JSON.stringify(cookieValue)); break; case COOKIE_NAME.SELECTED_ELEMENT: cookieValue.push(newCookie); break; case COOKIE_NAME.TIMEOUT: cookieValue.push(newCookie); break; } await world.context.addCookies([{ name: cookieNameStr, value: JSON.stringify(cookieValue), path: "/", domain: ".github.com" }]); } /** * Retrieves a cookie from the browser context by name. * Returns a CustomCookie instance with the cookie data or a default empty cookie if not found. * @param world - The Cucumber world object containing context * @param cookieName - The name of the cookie to retrieve (from COOKIE_NAME enum) * @returns A Promise that resolves to the CustomCookie instance */ async function getCookie(world, cookieName) { const cookieNameStr = `${cookieName.toString()}_${world.testInfo.testId}`; const cookies = await world.context.cookies(); if (cookies) { const cookieInContext = cookies.filter(cookie => cookie.name === cookieNameStr)[0]; // console.debug("selector", cookieInContext) if (cookieInContext) { return Object.assign(new CustomCookie(), cookieInContext); } } return new CustomCookie(); } /** * Deletes a cookie by setting its expiration date to 0. * @param world - The Cucumber world object containing context * @param cookieName - The name of the cookie to delete (from COOKIE_NAME enum) */ async function deleteCookieByName(world, cookieName) { const cookieToDelete = await getCookie(world, cookieName); await world.context.addCookies([ { ...cookieToDelete, expires: 0, }, ]); } /** * Finds exactly one element by role and name. * @param world - The Cucumber world object containing context * @param role - The accessible role to search for * @param name - The accessible name of the element to find * @param otherRoleOptions - Additional role options to pass to Playwright * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndName(world, role, name, otherRoleOptions = {}) { return await findWithRoleAndNameAndContent(world, role, name, undefined, otherRoleOptions); } /** * Locates an element within a specific role and name, then saves it as the selected element cookie. * Subsequent operations will be relative to this element until cleared. * @param world - The Cucumber world object containing context * @param role - The accessible role to search for * @param name - The accessible name of the element to find */ async function withinRoleAndName(world, role, name) { await findWithRoleAndNameAndContent(world, role, name, undefined); await addCookie(world, COOKIE_NAME.SELECTED_ELEMENT, new SelectedElementCookie(FILTER_TYPE.SELECTOR, `role=${role}[name="${name}"]`)); } /** * Locates an element by CSS selector and saves it as the selected element cookie. * Subsequent operations will be relative to this element until cleared. * @param world - The Cucumber world object containing context * @param selector - The CSS selector string to find the element * @returns A Promise that resolves when the element is found and cookie is set */ async function withinSelector(world, selector) { return await findWithSelector(world, selector).then(async () => { await addCookie(world, COOKIE_NAME.SELECTED_ELEMENT, new SelectedElementCookie(FILTER_TYPE.SELECTOR, selector)); }); } /** * Finds an element by role and name, verifying it does NOT exist (count should be 0). * @param world - The Cucumber world object containing context * @param role - The accessible role to search for * @param name - The accessible name of the element to verify doesn't exist */ async function notFoundWithRoleAndName(world, role, name) { role = encodeURIComponent(role); await (0, test_1.expect)((await getPageOrElement(world)).getByRole(role, { name: name, exact: true, })).toHaveCount(0, { timeout: await getTimeout(world) }); } /** * Finds exactly one element by CSS selector relative to the current selected element or page. * Verifies the element exists and focuses it. * @param world - The Cucumber world object containing context * @param selector - The CSS selector string to find the element * @returns A Promise that resolves to the LocatorTest instance */ async function findWithSelector(world, selector) { const locator = (await getPageOrElement(world)).locator(selector); await (0, test_1.expect)(locator).toHaveCount(1, { timeout: await getTimeout(world) }); await locator.focus({ timeout: await getTimeout(world) }); return locator; } /** * Gets all elements matching the text content relative to the current selected element or page. * Use this when you expect multiple matching elements. * @param world - The Cucumber world object containing context * @param textContent - The text content to search for * @returns A Promise that resolves to the LocatorTest instance */ async function getWithContent(world, textContent) { return (await getPageOrElement(world)).getByText(textContent); } /** * Gets all elements matching the test ID relative to the current selected element or page. * Use this when you expect multiple matching elements. * @param world - The Cucumber world object containing context * @param testId - The test ID attribute value to search for * @returns A Promise that resolves to the LocatorTest instance */ async function getWithTestId(world, testId) { return (await getPageOrElement(world)).getByTestId(testId); } /** * Finds exactly one element by test ID relative to the current selected element or page. * Verifies the element exists and returns its locator. * @param world - The Cucumber world object containing context * @param testId - The test ID attribute value to search for * @returns A Promise that resolves to the LocatorTest instance */ async function findWithTestId(world, testId) { const locator = await getWithTestId(world, testId); await (0, test_1.expect)(locator).toHaveCount(1, { timeout: await getTimeout(world) }); return locator; } /** * Gets all elements matching the ARIA label relative to the current selected element or page. * Use this when you expect multiple matching elements. * @param world - The Cucumber world object containing context * @param expectedAriaLabel - The ARIA label to search for * @returns A Promise that resolves to the LocatorTest instance */ async function getWithAriaLabel(world, expectedAriaLabel) { return (await getPageOrElement(world)).getByLabel(expectedAriaLabel, { exact: true }); } /** * Finds exactly one element by ARIA label relative to the current selected element or page. * Verifies the element exists and returns its locator. * @param world - The Cucumber world object containing context * @param expectedAriaLabel - The ARIA label to search for * @returns A Promise that resolves to the LocatorTest instance */ async function findWithAriaLabel(world, expectedAriaLabel) { const locator = await getWithAriaLabel(world, expectedAriaLabel); await (0, test_1.expect)(locator).toHaveCount(1, { timeout: await getTimeout(world) }); return locator; } /** * Finds exactly one element by role and name, with optional text content match. * Verifies the element count is 1, optionally checks text content, and focuses the element. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @param expectedTextContent - Optional text content to verify * @param otherRoleOptions - Additional role options to pass to Playwright * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndContent(world, expectedRole, name, expectedTextContent = undefined, otherRoleOptions = {}) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true, ...otherRoleOptions }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); if (expectedTextContent !== undefined) { await checkTextContentLocator(byRole, expectedTextContent); } await byRole.focus({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that is currently focused. * Verifies the element exists and is focused. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameFocused(world, expectedRole, name) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).toBeFocused({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that is currently checked (e.g., checkbox/radio). * Verifies the element exists and is checked. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndChecked(world, expectedRole, name) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).toBeChecked({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that is currently unchecked (e.g., checkbox/radio). * Verifies the element exists and is unchecked. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndUnchecked(world, expectedRole, name) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).not.toBeChecked({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name with specific text content that is disabled. * Verifies the element exists, has the specified text content, and is disabled. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @param expectedTextContent - The expected text content to verify * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndContentDisabled(world, expectedRole, name, expectedTextContent) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await checkTextContentLocator(byRole, expectedTextContent); await (0, test_1.expect)(byRole).toBeDisabled({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that is disabled. * Verifies the element exists and is disabled. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameDisabled(world, expectedRole, name) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).toBeDisabled({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name with specific text content that is enabled. * Verifies the element exists, has the specified text content, and is enabled. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @param expectedTextContent - The expected text content to verify * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndContentEnabled(world, expectedRole, name, expectedTextContent) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await checkTextContentLocator(byRole, expectedTextContent); await (0, test_1.expect)(byRole).toBeEnabled({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that is enabled. * Verifies the element exists and is enabled. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameEnabled(world, expectedRole, name) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).toBeEnabled({ timeout: await getTimeout(world) }); return byRole; } /** * Finds exactly one element by role and name that has a specific value. * Verifies the element exists and has the expected value attribute. * @param world - The Cucumber world object containing context * @param expectedRole - The accessible role to search for * @param name - The accessible name of the element to find * @param expectedValue - The expected value attribute to verify * @returns A Promise that resolves to the LocatorTest instance */ async function findWithRoleAndNameAndValue(world, expectedRole, name, expectedValue) { expectedRole = encodeURIComponent(expectedRole); const byRole = (await getPageOrElement(world)).getByRole(expectedRole, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); await (0, test_1.expect)(byRole).toHaveValue(expectedValue, { timeout: await getTimeout(world) }); return byRole; } /** * Displays all attribute names and values of a DOM element for debugging purposes. * Logs the attributes to the console. * @param element - The Playwright Locator element to inspect */ async function showAttributesInLocator(element) { const attributes = await element.evaluateHandle(aElement => { const attributeNames = aElement.getAttributeNames(); const result = {}; attributeNames.forEach(name => { result[name] = aElement.getAttribute(name); }); return result; }); console.debug("attributes of ", element, await attributes.jsonValue()); } /** * Checks if a locator has the expected text content by trying multiple methods. * Tries value, attribute value, checked state, and text content in sequence. * @param locator - The Playwright Locator to check * @param expectedTextContent - The expected text content to verify * @returns A Promise that resolves when the check is complete */ async function checkTextContentLocator(locator, expectedTextContent) { // await showAttributesInLocator(locator); try { await (0, test_1.expect)(locator).toHaveValue(expectedTextContent); } catch (err) { console.error("No value found for locator: ", locator); try { await (0, test_1.expect)(locator).toHaveAttribute("value", expectedTextContent); } catch (err) { console.error("No attribute value found for locator: ", locator); try { if (expectedTextContent === "true") { await (0, test_1.expect)(locator).toBeChecked(); } else { await (0, test_1.expect)(locator).not.toBeChecked(); } } catch (err) { console.error("Can't verify check for locator: ", locator); try { await (0, test_1.expect)(locator).toHaveText(expectedTextContent); } catch (err) { console.error("No text found for locator: ", locator); throw new Error(`Content '${expectedTextContent}' isn't present in locator '${locator}'`); } } } } } /** * Clicks on an element by role and name, then clears the selected element cookie. * @param world - The Cucumber world object containing context * @param role - The accessible role of the element to click * @param name - The accessible name of the element to click */ async function click(world, role, name) { const byRole = (await getPageOrElement(world)).getByRole(role, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); byRole.click({ timeout: await getTimeout(world) }); await deleteCookieByName(world, COOKIE_NAME.SELECTED_ELEMENT); return byRole; } /** * Clicks on the currently focused element in the browser. * Uses the :focus selector to identify the active element and clicks it. * If no element is focused, clicks on the current page/selected element. * @param world - The Cucumber world object containing context */ async function clickFocusedElement(world) { const keyBoardFocusTargetObj = keyBoardFocusTarget(world); if ((await keyBoardFocusTargetObj.count()) === 1) { await keyBoardFocusTargetObj.click({ timeout: await getTimeout(world) }); } else { (await getPageOrElement(world)).click({ timeout: await getTimeout(world) }); } } /** * Types text into an element by role and name, then clears the selected element cookie. * @param world - The Cucumber world object containing context * @param role - The accessible role of the element to type into * @param name - The accessible name of the element to type into * @param textToType - The text to type into the element * @returns A Promise that resolves to the LocatorTest instance */ async function type(world, role, name, textToType) { const byRole = (await getPageOrElement(world)).getByRole(role, { name: name, exact: true }); await (0, test_1.expect)(byRole).toHaveCount(1, { timeout: await getTimeout(world) }); byRole.type(textToType, { timeout: await getTimeout(world) }); await deleteCookieByName(world, COOKIE_NAME.SELECTED_ELEMENT); return byRole; } /** * Types text into the currently focused element. * If no element is focused, focuses the current page/selected element first. * @param world - The Cucumber world object containing context * @param textToType - The text to type into the focused element */ async function typeFocusedElement(world, textToType) { const keyBoardFocusTargetObj = keyBoardFocusTarget(world); if ((await keyBoardFocusTargetObj.count()) === 1) { await keyBoardFocusTargetObj.type(textToType); } else { const selector = (await getPageOrElement(world)); selector.focus({ timeout: await getTimeout(world) }); selector.type(textToType); } } function keyBoardFocusTarget(world) { return world.page.locator(":focus"); } /** * Gets the current timeout value from the TIMEOUT cookie, or returns the default timeout. * @param world - The Cucumber world object containing context * @returns A Promise that resolves to the timeout value in milliseconds */ async function getTimeout(world) { const cookieTimeout = await getCookie(world, COOKIE_NAME.TIMEOUT); if (cookieTimeout?.isValid()) { const timeoutCookies = JSON.parse(cookieTimeout.value); if (timeoutCookies.length > 0) { return timeoutCookies[0].value; } } return runner_commons_1.DEFAULT_TIMEOUT; } /** * Sets a new timeout value by storing it in the TIMEOUT cookie. * @param world - The Cucumber world object containing context * @param newTimeout - The new timeout value in milliseconds */ async function setTimeout(world, newTimeout) { await addCookie(world, COOKIE_NAME.TIMEOUT, new TimeoutCookie("timeout", newTimeout)); }