I'm attempting to abstract some Cypress methods into a helper object using getters.
The intended behavior is that I can do something like this:
todoApp.todoPage.todoApp.main.rows.row
.first().should('have.text', 'Pay electric bill');
todoApp.todoPage.todoApp.main.rows.row
.last().should('have.text', 'Walk the dog');
and it should simply return a nested property, UNLESS said property comes from Cypress, in which case it should call cy.get on the selector, and then get the property from that object. Behavior should be effectively identical to:
cy.get('[data-test=todo_app] [data-test=todo_page] [data-test=todo_app] [data-test=main] [data-test=rows] [data-test=row]')
.first().should('have.text', 'Pay electric bill');
cy.get('[data-test=todo_app] [data-test=todo_page] [data-test=todo_app] [data-test=main] [data-test=rows] [data-test=row]')
.last().should('have.text', 'Walk the dog');
Unfortunately, this isn't quite working as expected. If I log the property being retrieved, I see specWindow
and chainerId
as property... even though I never used those properties! You can see this in the log, as the get command is repeated.
This does not work as expected:
// filled algorithmically in the real code, but to keep the sample simple, hardcoded here
const cypressChainableMethodsAndProperties = ['first', 'last', 'within', 'should', 'specWindow', 'chainerId'];
// roughly: used with Object.defineProperties(proxifiedGet(selector), {...}) to generate at runtime
function proxifiedGet(selector: string) {
return new Proxy(
new Object(),
{
get: function (target, property) {
const propertyStr = property.toString();
if (cypressChainableMethodsAndProperties.includes(propertyStr)) {
return cy.get(selector)[property];
}
return target[property as keyof typeof target];
},
});
}
todoApp.todoPage.todoApp.main.rows.row
.first().should('have.text', 'Pay electric bill');
todoApp.todoPage.todoApp.main.rows.row
.last().should('have.text', 'Walk the dog');
But this works... except for the extra .get
being undesirable:
function proxifiedGet(selector: string) {
return new Proxy(
new Object(),
{
get: function (target, property) {
const propertyStr = property.toString();
if (propertyStr === 'get' ) {
return cy.get(selector);
}
return target[property as keyof typeof target];
},
});
}
todoApp.todoPage.todoApp.main.rows.row.get
.first().should('have.text', 'Pay electric bill');
todoApp.todoPage.todoApp.main.rows.row.get
.last().should('have.text', 'Walk the dog');
Here's a minimal test case:
describe('example', () => {
beforeEach(() => {
cy.visit('https://www.cypress.io/');
});
it('foo', () => {
proxifiedGet('[data-cy="header-login"]')
.first().should('have.text', 'Log In ');
});
);
I see
specWindow
andchainerId
as property... even though I never used those properties!
It's not your code that's using them, but probably the .first()
method which you are invoking on your proxy - i.e. this
inside the Cypress method code is your proxy. But your proxy is not a Cypress instance, and not even a proxy around one.
A hacky workaround would be to bind the returned method to the cy.get(selector)
on which you accessed the method, but this whole design with the cypressChainableMethodsAndProperties
heuristic doesn't look right.
Instead, create a proxy around the respective Cypress object that is chained from cy
:
function proxifiedFind(chain) {
return new Proxy(chain, {
get: function (target, key) {
if (key in target) {
return target[key];
}
return proxifiedFind(chain.find(`[data-test=${key}]`));
}
});
}
function proxifiedGet(selector: string) {
return proxifiedFind(cy.get(selector));
}