playwright-fluent
Version:
Fluent API around playwright
608 lines (607 loc) • 23.8 kB
JavaScript
"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;