CodeceptJS
CodeceptJS copied to clipboard
Unable to use Proxy in Page Object
What are you trying to achieve?
- Having the ability to use the same format for getting Page Object attributes is helpful
- For example, if only most of the attributes on the page use the correct
data-test-idattribute, but there are still some attributes which require an xpath definition, you can still use the same format:
LoginPage.$undefinedFunctionUsesCustomLocator(); // returns '//*[@data-test-id="foo"]'
LoginPage.$definedFunctionReturningXpath(); // returns '//div[contains(@class, "row")]'
- We should also be able to utilise the
customLocatorplugin in a Page Object to keep the way we access attributes consistent - e.g:
I.fill(LoginPage.$userName, 'foo'); // uses the customLocator plugin to build an xpath search of //*[@id="userName"]
I.fill(LoginPage.$bar, 'P@$$w0rd'); // calls a defined LoginPage.bar() function which returns //*[@id="password"]
What do you get instead?
- The
containerseems to override any Page Object proxy with its own Proxy, so we just get an error like this:
LoginPage.$bar is not a function
Provide test source code if related
// paste test
Details
- CodeceptJS version: 3.3.3
- NodeJS Version: v16.14.2
- Operating System: OSX 11.5.2 (Big Sur)
- puppeteer
- Configuration file:
exports.config = {
tests: './test/*.test.js',
output: './output',
helpers: {
Puppeteer: {
url: 'http://demoqa.com',
show: true,
windowSize: '1200x900'
}
},
plugins: {
customLocator: {
enabled: true,
attribute: 'id',
prefix: '$',
}
},
include: {
I: './steps_file.js',
LoginPage: './pages/LoginPage.js',
},
bootstrap: null,
mocha: {},
name: 'codeceptjs-issues'
}
/pages/LoginPage.js:
const prefix = global.codeceptjs.config.get('plugins').customLocator.prefix || '$';
module.exports = new Proxy({
_init() {
I = actor();
},
bar() {
return '//*[@id="password"]';
},
loginToBookstore(userName, password) {
I.amOnPage(`/login`);
// use the default customLocator prefix
I.fillField(`${prefix}userName`, userName);
// use the Proxy for this PageObject for the same customLocator prefix
I.fillField(this[`${prefix}bar`](), password);
I.click(`${prefix}login`);
}
}, {
get(target, prop) {
// not using optional chaining for compatability with older node versions
if (global.codeceptjs.config.get('plugins').customLocator && global.codeceptjs.config.get('plugins').customLocator.enabled) {
const prefix = global.codeceptjs.config.get('plugins').customLocator.prefix || '$';
if (prop.constructor.name === 'String' && prop.startsWith(prefix)) {
const propWithoutPrefix = prop.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`), '');
if(target[propWithoutPrefix]) {
return target[propWithoutPrefix]
};
return (() => global.codeceptjs.locator.build(prop).simplify());
}
}
return target[prop];
}
})
test/login.test.js:
Feature(`
As a tester
I want to be able to use a Proxy for page objects with the customLocator plugin`)
const prefix = global.codeceptjs.config.get('plugins').customLocator.prefix || '$';
Scenario('Login to demoqa.com', async ({ I, LoginPage }) => {
// use the standard customLocator prefix
// Can just do I.fillField('$userName', 'foo'); if the prefix is known
I.amOnPage('/login');
I.fillField(`${prefix}userName`, 'test123');
I.fillField(`${prefix}password`, 'test123');
I.click(`${prefix}login`);
I.waitForElement(`${prefix}output`, 5);
I.wait(1)
// using the customLocator prefix from within a Page Object that has a proxy to capture missing methods
LoginPage.loginToBookstore('test456', 'test456');
I.wait(1)
// Can just do I.fillField(LoginPage.$userName(), 'foo'); if the prefix is known
I.fillField(LoginPage[`${prefix}userName`](), 'test789');
// use the customLocator Proxy on a Page Object in a Scenario
I.fillField(LoginPage[`${prefix}bar`](), 'test789');
I.wait(1)
});