0.0.3 • Published 2 years ago

@autometa/page-components v0.0.3

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Page Components Model

Full Documentation/Tutorial Here

Autometa Page Components Model is an (early and experimental) interpretation of the Page Object Model, built for use with selenium

Quick Intro

Build Page Objects in a declarative manner, without instantiation or wiring logic, and no explicit waiting in tests:

export class MyHomePage extends WebPage {
  @component(By.className('login-btn'))
  login: Button;
}

And that's enough to start testing:

const url = process.env.MY_URL;
const builder = new Builder().forBrowser(Browser.CHROME);
const site = Site(url, builder);

describe('Clicking the login button', () => {
  let page: MyHomePage;
  beforeEach(async () => {
    page = await site.browse(MyHomePage);
  });

  it('should wait for the page to load and click the login button', () => {
    await page.waitForTitleIs('My Home Page!');
    await page.login.click();
  });
});

Easily create new components to better describe the intent of your Page Objects

export class MyLoginModal extends Component {
  @component(By.css('input[type="username"'))
  username: TextInput;
  @component(By.css('input[type="password"'))
  password: TextInput;
  // Automatically scoped to this modal. No conflict with
  // the '.login-btn' button in the parent DOM
  @component(By.className('login-btn'))
  login: Button;
}

Now start composing behavior

export class MyLoginModal extends Component {
  @component(By.css('input[type="username"'))
  username: TextInput;

  @component(By.css('input[type="password"'))
  password: TextInput;

  // Automatically scoped to this modal. No conflict with
  // the '.login-btn' button in the parent DOM.
  // Set wait-until condition and max timeout for slower items
  @component(By.className('login-btn'), Until.isDisplayed, 3500)
  login: Button;

  logUserIn = async (username: string, password: string) => {
    await this.username.write(username);
    await this.password.write(password);
    await login.click();
  };
}

And make a shortcut on our home-page if we like

export class MyLoginModal extends Component {
  @component(By.id('login-modal'))
  loginModal: MyLoginModal;

  @component(By.className('login-btn'))
  login: Button;

  logUserIn = async (username: string, password: string) => {
    await login.click();
    await this.loginModal.logUserIn(username, password);
  };
}

And begin using it in tests

const url = process.env.MY_URL;
const builder = new Builder().forBrowser(Browser.CHROME);
const site = Site(url, builder);

describe('Logging the user in', () => {
  let page: MyHomePage;
  beforeEach(async () => {
    page = await site.browse(MyHomePage);
  });

  it('should log the user in', () => {
    await page.waitForTitleIs('My Home Page!');
    await page.logUserIn('myname', 'mYpa55');
    await page.waitForTitleIs('mynames Profile')
  });
});

Other pages can also be bound for easy navigation

export class MyHomePage extends WebPage{
    @page()
    myProfilePage: MyProfilePage
}

Benefits

Declarative Model

Spend less time assembling Page Models. Declare models quickly and focus on writing tests.

Scoped Element Searches

Components automatically use their parents WebElement to initialize, and only use the WebDriver if the parent Page Object is a WebPage.

Lazy Initialization

Components initialize early so WebElements don't have to. WebElements are handled lazily, loaded only when acted upon, improving start up performance for element-based Page Object Models

Automatic Waiting

Lazily loaded elements when activated will automatically be waited for fluently. Until strategy can be configured multiple ways.

Automatic Staleness Handling

Elements will detect when they're stale and propagate a refresh request up the Page Model, forcing affected Components to lazily reload when next accessed instead of crashing.

Automatic Logging

(WIP) - only supports node console at this time.

Every interaction with the WebDriver is logged in detail with 'breadcrumbs' describing exactly what's happening in the tests

Example log:

  console.info
    Clicking On IndexPage[$root] >
      GridContainer[grid, By(css selector, .grid)] > 
        SimpleTypeDiv[simpleTypes, By(css selector, div:nth-of-type(1))] >
          Button[button, By(css selector, button)]( { text: "Click Me" } )

      at Button.#logActionOccurring (src/lib/meta-types/component.ts:558:17)

  console.info
    Searching for [ListItem, By(css selector, li)] in IndexPage[$root] > 
      GridContainer[grid, By(css selector, .grid)] >
        SimpleTypeDiv[simpleTypes, By(css selector, div:nth-of-type(1))] >
          UnorderedList[unorderedList, By(css selector, ul)]

      at ElementArray.<anonymous> (src/lib/meta-types/component.ts:540:17)

Easily Handle Collections of Similar Models

Patterns provided for handling dynamic or indeterminate content which share a model or structure.

Example html:

<ol>
  <li>first</li>
  <li>second</li>
  <li>third</li>
  <li>fourth</li>
</ol>

Example Page Model:

export class MyPageWithList extends WebPage {
  @collection(By.css('ol'), ListItem, By.css('li'))
  numericList: Collection<ListItem>;
}

Example test:

it('should check the list', () => {
  // array-like functions
  const expected = ['first', 'second', 'third', 'fourth'];

  await page.numericList.forEach(async (li, idx) => {
    expect(await li.text).toBe(expected[idx]);
  });

  const mapped = await page.numericList.map(async (li) => li.text);
  expect(mapped).toStrictEqual(expected);

  expect(await page.numericList.at(0)).toBe(expected[0]);

  // works with for...of
  for (const li of await page.numericList.values) {
    expect(await li.text).toBe(expected.shift());
  }
});