While my question is more general and not directly related to automation tests architecture, POM, and any framework I will use some real-world examples.
In my test framework I use Cypress and POM architecture. Please see example of page object:
export const homePage = getPageObjectProxy({
/** @type {Cypress.Chainable<JQuery<HTMLElement>>} */
searchField: 'input',
/** @type {Cypress.Chainable<JQuery<HTMLElement>>} */
logOutButton: '[data-testid=logout-button]',
/** @type {Cypress.Chainable<JQuery<HTMLElement>>} */
modeDropdown: '[data-testid=mode-dropdown]',
setMode(name) {
this.modeDropdown.click();
this.modeDropdown.children().contains(name).click();
}
});
Proxy function (as you can see I change type of string
properties to Cypress.Chainable<JQuery<HTMLElement>>
):
/**
* @template T
* @param {T} pageObject a regular page object
* @returns {T} proxied page object
*/
export const getPageObjectProxy = pageObject => new Proxy(pageObject, {
get(target, prop) {
if (typeof target[prop] === 'string') {
return cy.get(target[prop]);
}
return target[prop];
}
});
As a result after applying proxy I get an object with DOM elements (instead of selectors) and methods. These works perfectly (IDE intellisense also works).
With this approach I see 1 drawback. I must explicitly set type for each object property using JSDoc:
/** @type {Cypress.Chainable<JQuery<HTMLElement>>} */
I am curious is it possible using JSDoc somehow avoid this repetitive @type
definitions for each property?
Proxy (used conditional mapped types in JSDoc):
/**
* @template T
* @param {T} pageObject a page object with string selectors
* @returns {{[key in keyof T]: T[key] extends string ? Cypress.Chainable<JQuery<HTMLElement>> : T[key]}} page object with DOM elements
*/
export const getPageObjectProxy = pageObject => new Proxy(pageObject, {
get(target, prop) {
if (typeof target[prop] === 'string') {
return cy.get(target[prop]);
}
return target[prop];
}
});
Page object (used proxied object instead of this
in the methods):
export const homePage = getPageObjectProxy({
searchField: 'input',
logOutButton: '[data-testid=logout-button]',
modeDropdown: '[data-testid=mode-dropdown]',
setMode(name) {
homePage.modeDropdown.click();
homePage.modeDropdown.children().contains(name).click();
}
});