Skip to content

Latest commit

 

History

History
348 lines (253 loc) · 10.8 KB

File metadata and controls

348 lines (253 loc) · 10.8 KB

Table of Contents

PageObject

Extends ArrayStub

This class implements all the basic page object functionality, and all page objects must inherit from it. It can host selector and globalSelector fields, and will properly instantiate them as nested PageObjects when accessed. Each page object represents a DOM query that matches zero or more Elements (or subclasses of Element -- see ElementType).

PageObjects exist in a tree where each PageObject's elements are descendants of its parent's elements. The root of the tree is a top-level PageObject created using new PageObject(), that by default will match the root element (the body element or whatever was set as the root using setRoot). It can also be constructed with a selector argument, new PageObject(selector), in which case its query will be a selector query starting from the root element. Then each non-root PageObject has a selector and optional index that define its elements relative to its parent's elements.

As long as no page objects have an index, each PageObject conceptually appends its selector to its parent's selector, in effect making its matching elements

rootElement.querySelectorAll(`${this.parent.selector} ${this.selector}`);

Page objects that do have an index ("indexed page objects") restrict their matching elements to only the one element at their index within their full query results (or no elements if there is no element at that index). That in effect "resets" the root element for its descendants, so its children execute queries relative to its matching element (if it has one) and so on.

PageObjects are lazy, meaning that their query is not evaluated when they are constructed, but is evaluated and re-evaluated each time a property that depends on it is accessed.

PageObjects expose an API for interacting with their matching elements that comprises PageObject#element, PageObject#elements, and an Array API that exposes the page object's matching ElementTypes wrapped in indexed PageObjects. The index operator will return an indexed PageObject that may or may not match an element (similar to how you can index off the end of a native array and get undefined), while various array iteration methods like PageObject#map generate a range of PageObjects that reflect only the indices that actually match an element.

Descendant PageObjects are defined by subclassing PageObject and using the selector factory function to initialize class fields.

When creating a top-level PageObject directly using new, its query will match the root element (the body element or whatever was )

Parameters

  • selector the selector to use for this page object's query (optional, default '')
  • parent the element or page object to use as the root of the page object's query, defaulting to the global root element (optional, default null)
  • index an index to narrow the query to an element at a specific index within the query results (optional, default null)

Examples

import { PageObject, selector, setRoot } from 'fractal-page-object';

class Page extends PageObject {
  list = selector('.list');
}

setRoot(rootElement);

// rootElement.querySelectorAll('.list')
new Page().list.elements;
// rootElement.querySelectorAll('.container .list')
new Page('.container').list.elements;
// rootElement.querySelectorAll('.container')[0].querySelectorAll('.list')
new Page('.container', null, 0).list.elements;
// document.body.querySelectorAll('.list');
new Page('', document.body).list.elements;
// document.body.querySelectorAll('.container .list');
new Page('.container', document.body).list.elements;
// document.body.querySelectorAll('.container')[1].querySelectorAll('.list');
new Page('.container', document.body, 1).list.elements;

element

This page object's single matching DOM element -- the first DOM element matching this page object's query if this page object does not have an index, or the indexth matching DOM element if it does have an index specified.

Type: (ElementType | null)

elements

This page object's list of matching DOM elements. If this page object has an index, this property will always have a length of 0 or 1.

Type: Array<ElementType>

selector

Define a child PageObject. It can optionally be supplied with a PageObject subclass definition to allow customizing functionality on the page object, such as defining (grand)child page objects, or other helpful properties and functions. Alternatively, it can be passed an Element subclass as a type argument so elements its page objects produces will be typed more specifically.

Parameters

  • selector string the selector relative to the parent node
  • Class Function<PageObject>? optional PageObject subclass that can be used to extend the functionality of this page object

Examples

import { PageObject, selector } from 'fractal-page-object';

class Page extends PageObject {
  list = selector('.list', class extends PageObject {
    items = selector('li');
  });
}
let page = new Page();
page.list.elements; // document.body.querySelectorAll('.list')
page.list.items.elements; // document.body.querySelectorAll('.list li')
import { PageObject, selector } from 'fractal-page-object';

class Page extends PageObject {
  input = selector<HTMLInputElement>('input');
}
let page = new Page();
page.input.element; // type is HTMLInputElement
page.input.element.value; // no type cast needed

Returns PageObject a PageObject or PageObject subclass instance

globalSelector

Define a PageObject with a global scope, i.e. not scoped by its parent page object. Useful for cases like popovers and dropdowns, where the UI control is logically inside a given component, but all or part of it renders elsewhere in the DOM, such as directly under the body. globalSelector accepts a selector and optional custom class like selector(), but the queries of the page objects it generates will be executed from the root (document.body or whatever was passed to setRoot) rather than the parent page object's elements.

In some cases, it makes sense to set up a global root using setRoot, but some elements might be rendered outside even that, such as directly under the body. For cases like this, globalSelector accepts an optional root element to use as the root of the queries of the page object it generates.

Parameters

  • args ...any
  • selector string the selector
  • rootElement Element optional the root element under which to query the selector.
  • Class Function<PageObject> optional PageObject subclass that can be used to extend the functionality of this page object

Examples

import { PageObject, selector, globalSelector } from 'fractal-page-object';

const testContainer = document.querySelector('#test-container');
setRoot(testContainer);

class Page extends PageObject {
  listItems = selector('.list-item', class extends PageObject {
    popover = globalSelector('.popover', class extends PageObject {
      icon = selector('.icon');
    });
  });
}
let page = new Page();
page.listItems[0]; // testContainer.querySelectorAll('.listItems')[0]
page.listItems[0].popover; // testContainer.querySelectorAll('.popover')
page.listItems[0].popover.icon; // testContainer.querySelectorAll('.popover .icon')
import { setRoot, PageObject, selector, globalSelector } from 'fractal-page-object';

const testContainer = document.querySelector('#test-container');
setRoot(testContainer);

class Page extends PageObject {
  listItems = selector('.list-item', class extends PageObject {
    popover = globalSelector('.popover', document.body, class extends PageObject {
      icon = selector('.icon');
    });
  });
}
let page = new Page();
page.listItems[0]; // testContainer.querySelectorAll('.listItems')[0]
page.listItems[0].popover; // document.body.querySelectorAll('.popover')
page.listItems[0].popover.icon; // document.body.querySelectorAll('.popover .icon')
import { PageObject, globalSelector } from 'fractal-page-object';

class Page extends PageObject {
  input = globalSelector<HTMLInputElement>('input');
}
let page = new Page();
page.input.element; // type is HTMLInputElement
page.input.element.value; // no type cast needed

Returns PageObject a PageObject or PageObject subclass instance

setRoot

Set the global default root element -- by default PageObjects will only match elements that are descendants of this element. The default root element is document.body.

Parameters

  • element (Element | Function) the root element or a function that will return it

assertExists

Useful for providing clarity to consumers of page-objects to provide additional context so "can't access property on undefined" errors do not public up to the consumer.

In typescript, this is also useful for type-narrowing so that you can pass on the element to other utilities.

Parameters

  • msg string a descriptor for what it could mean when the element doesn't exist
  • pageObject PageObject the page object

Examples

let page = new Page();

assertExists('is the element on the page?', page);

await click(page.element);

getDescription

Utility to get the fully resolved selector path of a PageObject

Parameters

  • pageObject