UNPKG

playwright-fluent

Version:
608 lines (607 loc) 23.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectorFluent = void 0; const tslib_1 = require("tslib"); const action = (0, tslib_1.__importStar)(require("../actions")); const actions_1 = require("../actions"); class SelectorFluent { /** * */ constructor(selector, pwf, stringifiedState) { this.chainingHistory = ''; this.actionInfos = []; this.pwf = pwf; if (stringifiedState) { const state = JSON.parse(stringifiedState); this.chainingHistory = state.chainingHistory; this.actionInfos = state.actions; return; } this.chainingHistory = `selector(${selector})`; this.actionInfos.push({ name: 'querySelectorAllInPage', selector }); } getActionFrom(actionInfo) { switch (actionInfo.name) { case 'querySelectorAllInPage': return () => action.querySelectorAllInPage(actionInfo.selector, this.pwf.currentPageOrFrame()); case 'find': return (handles) => action.querySelectorAllFromHandles(actionInfo.selector, [...handles]); case 'nth': return (handles) => action.getNthHandle(actionInfo.index, [...handles]); case 'parent': return (handles) => action.getParentsOf([...handles]); case 'nextSibling': return (handles) => action.getNextSiblingsOf([...handles]); case 'previousSibling': return (handles) => action.getPreviousSiblingsOf([...handles]); case 'withText': return (handles) => action.getHandlesWithText(actionInfo.text, [...handles]); case 'withExactText': return (handles) => action.getHandlesWithExactText(actionInfo.text, [...handles]); case 'withValue': return (handles) => action.getHandlesWithValue(actionInfo.text, [...handles]); case 'withPlaceholder': return (handles) => action.getHandlesWithPlaceholder(actionInfo.text, [...handles]); default: throw new Error(`Action '${actionInfo.name}' is not yet implemented`); } } async executeActions() { let handles = []; for (let index = 0; index < this.actionInfos.length; index++) { const action = this.getActionFrom(this.actionInfos[index]); handles = await action([...handles]); } return handles; } /** * Executes the search. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @returns {Promise<ElementHandle<Element>[]>} will return an empty array if no elements are found, will return all found elements otherwise. * @memberof SelectorFluent */ async getAllHandles() { const handles = await this.executeActions(); return handles; } /** * Iterate over each found selector * The index is the 1-based index of the selector in the list of selectors * @param {(selector: SelectorFluent, index: number) => Promise<void>} func * @returns {Promise<void>} * @memberof SelectorFluent * @example * const rows = p.selector('[role="row"]'); * await rows.forEach(async (row) => { * const checkbox = row.find('input[type="checkbox"]'); * await p.hover(checkbox).check(checkbox); * }); */ async forEach(func) { const selectorsCount = await this.count(); for (let i = 1; i <= selectorsCount; i++) { const selectorItem = this.nth(i); await func(selectorItem, i); } } /** * Obsolete: please use the getHandle() method. * Executes the search and returns the first found element. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @returns {Promise<ElementHandle<Element> | null>} will return null if no elements are found, will return first found element otherwise. * @memberof SelectorFluent * @obsolete */ async getFirstHandleOrNull() { const handles = await this.executeActions(); if (handles.length === 0) { return null; } return handles[0]; } /** * Executes the search and returns the first found element. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @returns {Promise<ElementHandle<Element> | null>} will return null if no elements are found, will return first found element otherwise. * @memberof SelectorFluent */ async getHandle() { const handles = await this.executeActions(); if (handles.length === 0) { return null; } return handles[0]; } /** * Gets the number of found elements. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @returns {Promise<number>} will return 0 if no elements are found. * @memberof SelectorFluent */ async count() { const handles = await this.executeActions(); return handles.length; } toString() { return this.chainingHistory; } createSelectorFrom(selector, actions, chainingHistory) { const state = { actions, chainingHistory, }; return new SelectorFluent(selector, this.pwf, JSON.stringify(state)); } find(selector) { const actions = [...this.actionInfos]; actions.push({ name: 'find', selector }); const chainingHistory = `${this.chainingHistory} .find(${selector})`; return this.createSelectorFrom(selector, actions, chainingHistory); } /** * Finds, from previous search, all elements whose innerText contains the specified text * * @param {string} text * @returns {SelectorFluent} * @memberof SelectorFluent */ withText(text) { const actions = [...this.actionInfos]; actions.push({ name: 'withText', text }); const chainingHistory = `${this.chainingHistory} .withText(${text})`; return this.createSelectorFrom(text, actions, chainingHistory); } /** * Finds, from previous search, all elements whose innerText match exactly the specified text. * Use that method when you need to find elements with empty content. * @param {string} text * @returns {SelectorFluent} * @memberof SelectorFluent */ withExactText(text) { const actions = [...this.actionInfos]; actions.push({ name: 'withExactText', text }); const chainingHistory = `${this.chainingHistory} .withExactText(${text})`; return this.createSelectorFrom(text, actions, chainingHistory); } /** * Finds, from previous search, all elements whose value contains the specified text * * @param {string} text * @returns {SelectorFluent} * @memberof SelectorFluent */ withValue(text) { const actions = [...this.actionInfos]; actions.push({ name: 'withValue', text }); const chainingHistory = `${this.chainingHistory} .withValue(${text})`; return this.createSelectorFrom(text, actions, chainingHistory); } /** * Finds, from previous search, all elements whose placeholder contains the specified text * * @param {string} text * @returns {SelectorFluent} * @memberof SelectorFluent */ withPlaceholder(text) { const actions = [...this.actionInfos]; actions.push({ name: 'withPlaceholder', text }); const chainingHistory = `${this.chainingHistory} .withPlaceholder(${text})`; return this.createSelectorFrom(text, actions, chainingHistory); } parent() { const actions = [...this.actionInfos]; actions.push({ name: 'parent' }); const chainingHistory = `${this.chainingHistory} .parent()`; return this.createSelectorFrom('', actions, chainingHistory); } nextSibling() { const actions = [...this.actionInfos]; actions.push({ name: 'nextSibling' }); const chainingHistory = `${this.chainingHistory} .nextSibling()`; return this.createSelectorFrom('', actions, chainingHistory); } previousSibling() { const actions = [...this.actionInfos]; actions.push({ name: 'previousSibling' }); const chainingHistory = `${this.chainingHistory} .previousSibling()`; return this.createSelectorFrom('', actions, chainingHistory); } /** * Takes the nth element found at the previous step * * @param {number} index : 1-based index * @returns {SelectorFluent} * @memberof SelectorFluent * @example * nth(1): take the first element found at previous step. * nth(-1): take the last element found at previous step. */ nth(index) { const actions = [...this.actionInfos]; actions.push({ name: 'nth', index }); const chainingHistory = `${this.chainingHistory} .nth(${index})`; return this.createSelectorFrom('', actions, chainingHistory); } /** * Checks if selector exists. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the disability status is the one known when executing this method. * * @returns {Promise<boolean>} * @memberof SelectorFluent */ async exists() { const handle = await this.getHandle(); if (handle === null) { return false; } return true; } /** * Checks if selector is not in the DOM. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the existence status is the one known when executing this method. * * @returns {Promise<boolean>} * @memberof SelectorFluent */ async doesNotExist() { const handle = await this.getHandle(); if (handle === null) { return true; } return false; } /** * Checks if the selector is visible. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the visibilty status is the one known when executing this method. * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isVisible(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const isElementVisible = await action.isHandleVisible(handle, verboseOptions); return isElementVisible; } /** * Checks that the selector is not visible. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the visibilty status is the one known when executing this method. * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isNotVisible(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const isElementNotVisible = await action.isHandleNotVisible(handle, verboseOptions); return isElementNotVisible; } /** * Checks if the selector is enabled. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the enability status is the one known when executing this method. * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isEnabled(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const isElementEnabled = await action.isHandleEnabled(handle, verboseOptions); return isElementEnabled; } /** * Checks if the selector is disabled. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the disability status is the one known when executing this method. * * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isDisabled(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const isElementDisabled = await action.isHandleDisabled(handle, verboseOptions); return isElementDisabled; } /** * Checks if the selector is read-only. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the disability status is the one known when executing this method. * * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isReadOnly(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const isElementReadOnly = await action.isHandleReadOnly(handle, verboseOptions); return isElementReadOnly; } /** * Checks if the selector is not read-only. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the disability status is the one known when executing this method. * * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isNotReadOnly(options = actions_1.defaultVerboseOptions) { const isReadOnly = await this.isReadOnly(options); return !isReadOnly; } async innerText() { const handle = await this.getHandle(); const innerText = await action.getInnerTextOfHandle(handle); return innerText; } async value() { const handle = await this.getHandle(); const value = await action.getValueOfHandle(handle); return value; } async classList() { const handle = await this.getHandle(); const result = await action.getClassListOfHandle(handle); return result; } async getAttribute(attributeName) { const handle = await this.getHandle(); const result = await action.getAttributeOfHandle(attributeName, handle); return result; } /** * Get the placeholder content * * @returns {(Promise<string | null>)} * @memberof SelectorFluent */ async placeholder() { return this.getAttribute('placeholder'); } /** * Get the client rectangle of the selector * * @returns {(Promise<SerializableDOMRect | null>)} * @memberof SelectorFluent */ async clientRectangle() { const handle = await this.getHandle(); const result = await action.getClientRectangleOfHandle(handle); return result; } /** * Get the position of the center of selector's bounding box. * * @returns {(Promise<Point | null>)} * @memberof SelectorFluent */ async position() { const handle = await this.getHandle(); const result = await action.getClientRectangleOfHandle(handle); if (result) { const x = result.left + result.width / 2; const y = result.top + result.height / 2; return { x, y, }; } return null; } /** * Get the position of the left centered point of the selector's bounding box. * * @returns {(Promise<Point | null>)} * @memberof SelectorFluent */ async leftPosition() { const handle = await this.getHandle(); const result = await action.getClientRectangleOfHandle(handle); if (result) { const x = result.left; const y = result.top + result.height / 2; return { x, y, }; } return null; } /** * Get the position of the right centered point of the selector's bounding box. * * @returns {(Promise<Point | null>)} * @memberof SelectorFluent */ async rightPosition() { const handle = await this.getHandle(); const result = await action.getClientRectangleOfHandle(handle); if (result) { const x = result.left + result.width; const y = result.top + result.height / 2; return { x, y, }; } return null; } /** * Checks that selector has the an attribute with an expected value * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @param {string} attributeName * @param {string} expectedAttributeValue * @returns {Promise<boolean>} * @memberof SelectorFluent */ async hasAttributeWithValue(attributeName, expectedAttributeValue) { const handle = await this.getHandle(); const result = await action.hasHandleAttribute(handle, attributeName, expectedAttributeValue); return result; } /** * Checks that selector has the specified class * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @param {string} expectedClass * @returns {Promise<boolean>} * @memberof SelectorFluent */ async hasClass(expectedClass) { const handle = await this.getHandle(); const result = await action.hasHandleClass(handle, expectedClass); return result; } /** * Checks that selector does not have the specified class * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * * @param {string} expectedClass * @returns {Promise<boolean>} * @memberof SelectorFluent */ async doesNotHaveClass(expectedClass) { const handle = await this.getHandle(); const result = await action.hasNotHandleClass(handle, expectedClass); return result; } /** * Checks that the selector is checked. * If the selector targets multiple DOM elements, this check is done only on the first one found. * The result may differ from one execution to another * especially if targeted element is rendered lately because its data is based on some backend response. * So the checked status is the one known when executing this method. * * @param {Partial<VerboseOptions>} [options=defaultVerboseOptions] * @returns {Promise<boolean>} * @memberof SelectorFluent */ async isChecked(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const result = await action.isHandleChecked(handle, verboseOptions); return result; } async isUnchecked(options = actions_1.defaultVerboseOptions) { const verboseOptions = { ...actions_1.defaultVerboseOptions, options, }; const handle = await this.getHandle(); const result = await action.isHandleUnchecked(handle, verboseOptions); return result; } async options() { const handle = await this.getHandle(); const result = await action.getAllOptionsOfHandle(handle, this.toString()); return result; } async allSelectedOptions() { const handle = await this.getHandle(); const allOptions = await action.getAllOptionsOfHandle(handle, this.toString()); return allOptions.filter((option) => option.selected); } async selectedOption() { const handle = await this.getHandle(); const allOptions = await action.getAllOptionsOfHandle(handle, this.toString()); const selectedOption = allOptions.find((option) => option.selected); return selectedOption; } /** * hover over selector * @param {Partial<HoverOptions>} [options=defaultHoverOptions] * @returns {Promise<void>} * @memberof SelectorFluent */ async hover(options = actions_1.defaultHoverOptions) { const handle = await this.getHandle(); const hoverOptions = { ...actions_1.defaultHoverOptions, ...options, }; await action.hoverOnHandle(handle, this.toString(), this.pwf.currentPageOrFrame(), hoverOptions); } /** * click on selector * * @param {Partial<ClickOptions>} [options=defaultClickOptions] * @returns {Promise<void>} * @memberof SelectorFluent */ async click(options = actions_1.defaultClickOptions) { const handle = await this.getHandle(); const clickOptions = { ...actions_1.defaultClickOptions, ...options, }; await action.clickOnHandle(handle, this.toString(), this.pwf.currentPageOrFrame(), clickOptions); } } exports.SelectorFluent = SelectorFluent;