CodeceptJS icon indicating copy to clipboard operation
CodeceptJS copied to clipboard

Unable to use Proxy in Page Object

Open aruiz-caritsqa opened this issue 3 years ago • 0 comments

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-id attribute, 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 customLocator plugin 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 container seems 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)
});

aruiz-caritsqa avatar Jun 22 '22 15:06 aruiz-caritsqa