refactor(cdk-experimental/testing): refactor the ComponentHarness API (#16234)

Renames several APIs for readability
This commit is contained in:
mmalerba
2019-06-18 10:14:04 -07:00
committed by Jeremy Elbourn
parent 30127fa267
commit 22268c326a
16 changed files with 991 additions and 716 deletions
+208 -144
View File
@@ -8,183 +8,247 @@
import {TestElement} from './test-element';
/** Options that can be specified when querying for an Element. */
export interface QueryOptions {
/**
* Whether the found element can be null. If allowNull is set, the searching function will always
* try to fetch the element at once. When the element cannot be found, the searching function
* should return null if allowNull is set to true, throw an error if allowNull is set to false.
* If allowNull is not set, the framework will choose the behaviors that make more sense for each
* test type (e.g. for unit test, the framework will make sure the element is not null; otherwise
* throw an error); however, the internal behavior is not guaranteed and user should not rely on
* it. Note that in most cases, you don't need to care about whether an element is present when
* loading the element and don't need to set this parameter unless you do want to check whether
* the element is present when calling the searching function. e.g. you want to make sure some
* element is not there when loading the element in order to check whether a "ngif" works well.
*/
allowNull?: boolean;
/**
* If global is set to true, the selector will match any element on the page and is not limited to
* the root of the harness. If global is unset or set to false, the selector will only find
* elements under the current root.
*/
global?: boolean;
}
/** An async function that returns a promise when called. */
export type AsyncFn<T> = () => Promise<T>;
/** Interface that is used to find elements in the DOM and create harnesses for them. */
export interface HarnessLocator {
/**
* Interface used to load ComponentHarness objects. This interface is used by test authors to
* instantiate `ComponentHarness`es.
*/
export interface HarnessLoader {
/**
* Get the host element of locator.
* Searches for an element with the given selector under the current instances's root element,
* and returns a `HarnessLoader` rooted at the matching element. If multiple elements match the
* selector, the first is used. If no elements match, an error is thrown.
* @param selector The selector for the root element of the new `HarnessLoader`
* @return A `HarnessLoader` rooted at the element matching the given selector.
* @throws If a matching element can't be found.
*/
host(): TestElement;
getChildLoader(selector: string): Promise<HarnessLoader>;
/**
* Search the first matched test element.
* @param selector The CSS selector of the test elements.
* @param options Optional, extra searching options
* Searches for all elements with the given selector under the current instances's root element,
* and returns an array of `HarnessLoader`s, one for each matching element, rooted at that
* element.
* @param selector The selector for the root element of the new `HarnessLoader`
* @return A list of `HarnessLoader`s, one for each matching element, rooted at that element.
*/
querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null>;
getAllChildLoaders(selector: string): Promise<HarnessLoader[]>;
/**
* Search all matched test elements under current root by CSS selector.
* @param selector The CSS selector of the test elements.
* Searches for an instance of the component corresponding to the given harness type under the
* `HarnessLoader`'s root element, and returns a `ComponentHarness` for that instance. If multiple
* matching components are found, a harness for the first one is returned. If no matching
* component is found, an error is thrown.
* @param harnessType The type of harness to create
* @return An instance of the given harness type
* @throws If a matching component instance can't be found.
*/
querySelectorAll(selector: string): Promise<TestElement[]>;
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T>;
/**
* Load the first matched Component Harness.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harness.
* @param options Optional, extra searching options
* Searches for all instances of the component corresponding to the given harness type under the
* `HarnessLoader`'s root element, and returns a list `ComponentHarness` for each instance.
* @param harnessType The type of harness to create
* @return A list instances of the given harness type.
*/
load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, root: string,
options?: QueryOptions): Promise<T|null>;
/**
* Load all Component Harnesses under current root.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harnesses.
*/
loadAll<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, root: string): Promise<T[]>;
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T[]>;
}
/**
* Base Component Harness
* This base component harness provides the basic ability to locate element and
* sub-component harness. It should be inherited when defining user's own
* harness.
* Interface used to create asynchronous locator functions used find elements and component
* harnesses. This interface is used by `ComponentHarness` authors to create locator functions for
* their `ComponentHarenss` subclass.
*/
export interface LocatorFactory {
/** Gets a locator factory rooted at the document root. */
documentRootLocatorFactory(): LocatorFactory;
/** The root element of this `LocatorFactory` as a `TestElement`. */
rootElement: TestElement;
/**
* Creates an asynchronous locator function that can be used to search for elements with the given
* selector under the root element of this `LocatorFactory`. When the resulting locator function
* is invoked, if multiple matching elements are found, the first element is returned. If no
* elements are found, an error is thrown.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
locatorFor(selector: string): AsyncFn<TestElement>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
* component matching the given harness type under the root element of this `LocatorFactory`.
* When the resulting locator function is invoked, if multiple matching components are found, a
* harness for the first one is returned. If no components are found, an error is thrown.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or throws an error.
*/
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T>;
/**
* Creates an asynchronous locator function that can be used to search for elements with the given
* selector under the root element of this `LocatorFactory`. When the resulting locator function
* is invoked, if multiple matching elements are found, the first element is returned. If no
* elements are found, null is returned.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or returns null.
*/
locatorForOptional(selector: string): AsyncFn<TestElement | null>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
* component matching the given harness type under the root element of this `LocatorFactory`.
* When the resulting locator function is invoked, if multiple matching components are found, a
* harness for the first one is returned. If no components are found, null is returned.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
*/
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T | null>;
/**
* Creates an asynchronous locator function that can be used to search for a list of elements with
* the given selector under the root element of this `LocatorFactory`. When the resulting locator
* function is invoked, a list of matching elements is returned.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
locatorForAll(selector: string): AsyncFn<TestElement[]>;
/**
* Creates an asynchronous locator function that can be used to find a list of
* `ComponentHarness`es for all components matching the given harness type under the root element
* of this `LocatorFactory`. When the resulting locator function is invoked, a list of
* `ComponentHarness`es for the matching components is returned.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and returns a list of `ComponentHarness`es.
*/
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
}
/**
* Base class for component harnesses that all component harness authors should extend. This base
* component harness provides the basic ability to locate element and sub-component harness. It
* should be inherited when defining user's own harness.
*/
export abstract class ComponentHarness {
constructor(private readonly locator: HarnessLocator) {}
constructor(private readonly locatorFactory: LocatorFactory) {}
/**
* Get the host element of component harness.
*/
host(): TestElement {
return this.locator.host();
/** Gets a `Promise` for the `TestElement` representing the host element of the component. */
async host(): Promise<TestElement> {
return this.locatorFactory.rootElement;
}
/**
* Generate a function to find the first matched test element by CSS
* selector.
* @param selector The CSS selector of the test element.
* Gets a `LocatorFactory` for the document root element. This factory can be used to create
* locators for elements that a component creates outside of its own root element. (e.g. by
* appending to document.body).
*/
protected find(selector: string): () => Promise<TestElement>;
/**
* Generate a function to find the first matched test element by CSS
* selector.
* @param selector The CSS selector of the test element.
* @param options Extra searching options
*/
protected find(selector: string, options: QueryOptions & {allowNull: true}):
() => Promise<TestElement|null>;
/**
* Generate a function to find the first matched test element by CSS
* selector.
* @param selector The CSS selector of the test element.
* @param options Extra searching options
*/
protected find(selector: string, options: QueryOptions): () => Promise<TestElement>;
/**
* Generate a function to find the first matched Component Harness.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harness.
*/
protected find<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
root: string): () => Promise<T>;
/**
* Generate a function to find the first matched Component Harness.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harness.
* @param options Extra searching options
*/
protected find<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, root: string,
options: QueryOptions & {allowNull: true}): () => Promise<T|null>;
/**
* Generate a function to find the first matched Component Harness.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harness.
* @param options Extra searching options
*/
protected find<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, root: string,
options: QueryOptions): () => Promise<T>;
protected find<T extends ComponentHarness>(
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
selectorOrOptions?: string|QueryOptions,
options?: QueryOptions): () => Promise<TestElement|T|null> {
if (typeof selectorOrComponentHarness === 'string') {
const selector = selectorOrComponentHarness;
return () => this.locator.querySelector(selector, selectorOrOptions as QueryOptions);
} else {
const componentHarness = selectorOrComponentHarness;
const selector = selectorOrOptions as string;
return () => this.locator.load(componentHarness, selector, options);
}
protected documentRootLocatorFactory(): LocatorFactory {
return this.locatorFactory.documentRootLocatorFactory();
}
/**
* Generate a function to find all matched test elements by CSS selector.
* @param selector The CSS root selector of elements. It will locate
* elements under the current root.
* Creates an asynchronous locator function that can be used to search for elements with the given
* selector under the host element of this `ComponentHarness`. When the resulting locator function
* is invoked, if multiple matching elements are found, the first element is returned. If no
* elements are found, an error is thrown.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
protected findAll(selector: string): () => Promise<TestElement[]>;
protected locatorFor(selector: string): AsyncFn<TestElement>;
/**
* Generate a function to find all Component Harnesses under current
* component harness.
* @param componentHarness Type of user customized harness.
* @param root CSS root selector of the new component harnesses. It will
* locate harnesses under the current root.
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
* component matching the given harness type under the host element of this `ComponentHarness`.
* When the resulting locator function is invoked, if multiple matching components are found, a
* harness for the first one is returned. If no components are found, an error is thrown.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or throws an error.
*/
protected findAll<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
root: string): () => Promise<T[]>;
protected locatorFor<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T>;
protected findAll<T extends ComponentHarness>(
selectorOrComponentHarness: string|ComponentHarnessConstructor<T>,
root?: string): () => Promise<TestElement[]|T[]> {
if (typeof selectorOrComponentHarness === 'string') {
const selector = selectorOrComponentHarness;
return () => this.locator.querySelectorAll(selector);
} else {
const componentHarness = selectorOrComponentHarness;
return () => this.locator.loadAll(componentHarness, root as string);
}
protected locatorFor(arg: any): any {
return this.locatorFactory.locatorFor(arg);
}
/**
* Creates an asynchronous locator function that can be used to search for elements with the given
* selector under the host element of this `ComponentHarness`. When the resulting locator function
* is invoked, if multiple matching elements are found, the first element is returned. If no
* elements are found, null is returned.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or returns null.
*/
protected locatorForOptional(selector: string): AsyncFn<TestElement | null>;
/**
* Creates an asynchronous locator function that can be used to find a `ComponentHarness` for a
* component matching the given harness type under the host element of this `ComponentHarness`.
* When the resulting locator function is invoked, if multiple matching components are found, a
* harness for the first one is returned. If no components are found, null is returned.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and either returns a `ComponentHarness` for the component, or null if none is found.
*/
protected locatorForOptional<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>): AsyncFn<T | null>;
protected locatorForOptional(arg: any): any {
return this.locatorFactory.locatorForOptional(arg);
}
/**
* Creates an asynchronous locator function that can be used to search for a list of elements with
* the given selector under the host element of this `ComponentHarness`. When the resulting
* locator function is invoked, a list of matching elements is returned.
* @param selector The selector for the element that the locator function should search for.
* @return An asynchronous locator function that searches for elements with the given selector,
* and either finds one or throws an error
*/
protected locatorForAll(selector: string): AsyncFn<TestElement[]>;
/**
* Creates an asynchronous locator function that can be used to find a list of
* `ComponentHarness`es for all components matching the given harness type under the host element
* of this `ComponentHarness`. When the resulting locator function is invoked, a list of
* `ComponentHarness`es for the matching components is returned.
* @param harnessType The type of harness to search for.
* @return An asynchronous locator function that searches components matching the given harness
* type, and returns a list of `ComponentHarness`es.
*/
protected locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
protected locatorForAll(arg: any): any {
return this.locatorFactory.locatorForAll(arg);
}
}
/** Constructor for a ComponentHarness subclass. */
export interface ComponentHarnessConstructor<T extends ComponentHarness> {
new(locator: HarnessLocator): T;
new(locatorFactory: LocatorFactory): T;
/**
* `ComponentHarness` subclasses must specify a static `hostSelector` property that is used to
* find the host element for the corresponding component. This property should match the selector
* for the Angular component.
*/
hostSelector: string;
}
@@ -0,0 +1,144 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
AsyncFn,
ComponentHarness,
ComponentHarnessConstructor,
HarnessLoader,
LocatorFactory
} from './component-harness';
import {TestElement} from './test-element';
/**
* Base harness environment class that can be extended to allow `ComponentHarness`es to be used in
* different test environments (e.g. testbed, protractor, etc.). This class implements the
* functionality of both a `HarnessLoader` and `LocatorFactory`. This class is generic on the raw
* element type, `E`, used by the particular test environment.
*/
export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFactory {
// Implemented as part of the `LocatorFactory` interface.
rootElement: TestElement;
protected constructor(protected rawRootElement: E) {
this.rootElement = this.createTestElement(rawRootElement);
}
// Implemented as part of the `LocatorFactory` interface.
documentRootLocatorFactory(): LocatorFactory {
return this.createEnvironment(this.getDocumentRoot());
}
// Implemented as part of the `LocatorFactory` interface.
locatorFor(selector: string): AsyncFn<TestElement>;
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T>;
locatorFor<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
if (element) {
return this.createTestElement(element);
}
} else {
const element = await this.getRawElement(arg.hostSelector);
if (element) {
return this.createComponentHarness(arg, element);
}
}
const selector = typeof arg === 'string' ? arg : arg.hostSelector;
throw Error(`Expected to find element matching selector: "${selector}", but none was found`);
};
}
// Implemented as part of the `LocatorFactory` interface.
locatorForOptional(selector: string): AsyncFn<TestElement | null>;
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T | null>;
locatorForOptional<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement | T | null> {
return async () => {
if (typeof arg === 'string') {
const element = await this.getRawElement(arg);
return element ? this.createTestElement(element) : null;
} else {
const element = await this.getRawElement(arg.hostSelector);
return element ? this.createComponentHarness(arg, element) : null;
}
};
}
// Implemented as part of the `LocatorFactory` interface.
locatorForAll(selector: string): AsyncFn<TestElement[]>;
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
AsyncFn<T[]>;
locatorForAll<T extends ComponentHarness>(
arg: string | ComponentHarnessConstructor<T>): AsyncFn<TestElement[] | T[]> {
return async () => {
if (typeof arg === 'string') {
return (await this.getAllRawElements(arg)).map(e => this.createTestElement(e));
} else {
return (await this.getAllRawElements(arg.hostSelector))
.map(e => this.createComponentHarness(arg, e));
}
};
}
// Implemented as part of the `HarnessLoader` interface.
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T> {
return this.locatorFor(harnessType)();
}
// Implemented as part of the `HarnessLoader` interface.
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>):
Promise<T[]> {
return this.locatorForAll(harnessType)();
}
// Implemented as part of the `HarnessLoader` interface.
async getChildLoader(selector: string): Promise<HarnessLoader> {
const element = await this.getRawElement(selector);
if (element) {
return this.createEnvironment(element);
}
throw Error(`Expected to find element matching selector: "${selector}", but none was found`);
}
// Implemented as part of the `HarnessLoader` interface.
async getAllChildLoaders(selector: string): Promise<HarnessLoader[]> {
return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e));
}
/** Creates a `ComponentHarness` for the given harness type with the given raw host element. */
protected createComponentHarness<T extends ComponentHarness>(
harnessType: ComponentHarnessConstructor<T>, element: E): T {
return new harnessType(this.createEnvironment(element));
}
/** Gets the root element for the document. */
protected abstract getDocumentRoot(): E;
/** Creates a `TestElement` from a raw element. */
protected abstract createTestElement(element: E): TestElement;
/** Creates a `HarnessLoader` rooted at the given raw element. */
protected abstract createEnvironment(element: E): HarnessEnvironment<E>;
/**
* Gets the first element matching the given selector under this environment's root element, or
* null if no elements match.
*/
protected abstract getRawElement(selector: string): Promise<E | null>;
/**
* Gets a list of all elements matching the given selector under this environment's root element.
*/
protected abstract getAllRawElements(selector: string): Promise<E[]>;
}
-170
View File
@@ -1,170 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {browser, by, element as protractorElement, ElementFinder} from 'protractor';
import {
ComponentHarness,
ComponentHarnessConstructor,
HarnessLocator,
QueryOptions
} from './component-harness';
import {TestElement} from './test-element';
/**
* Component harness factory for protractor.
* The function will not try to fetch the host element of harness at once, which
* is for performance purpose; however, this is the most common way to load
* protractor harness. If you do care whether the host element is present when
* loading harness, using the load function that accepts extra searching
* options.
* @param componentHarness: Type of user defined harness.
* @param rootSelector: Optional. CSS selector to specify the root of component.
* Set to 'body' by default
*/
export async function load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
rootSelector: string): Promise<T>;
/**
* Component harness factory for protractor.
* @param componentHarness: Type of user defined harness.
* @param rootSelector: Optional. CSS selector to specify the root of component.
* Set to 'body' by default.
* @param options Optional. Extra searching options
*/
export async function load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, rootSelector?: string,
options?: QueryOptions): Promise<T|null>;
export async function load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, rootSelector = 'body',
options?: QueryOptions): Promise<T|null> {
const root = await getElement(rootSelector, undefined, options);
return root && new componentHarness(new ProtractorLocator(root));
}
/**
* Gets the corresponding ElementFinder for the root of a TestElement.
*/
export function getElementFinder(testElement: TestElement): ElementFinder {
if (testElement instanceof ProtractorElement) {
return testElement.element;
}
throw Error(`Expected an instance of ProtractorElement, got ${testElement}`);
}
class ProtractorLocator implements HarnessLocator {
private readonly _root: ProtractorElement;
constructor(private _rootFinder: ElementFinder) {
this._root = new ProtractorElement(this._rootFinder);
}
host(): TestElement {
return this._root;
}
async querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null> {
const finder = await getElement(selector, this._rootFinder, options);
return finder && new ProtractorElement(finder);
}
async querySelectorAll(selector: string): Promise<TestElement[]> {
const elementFinders = this._rootFinder.all(by.css(selector));
return elementFinders.reduce(
(result: TestElement[], el: ElementFinder) =>
el ? result.concat([new ProtractorElement(el)]) : result,
[]);
}
async load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, selector: string,
options?: QueryOptions): Promise<T|null> {
const root = await getElement(selector, this._rootFinder, options);
return root && new componentHarness(new ProtractorLocator(root));
}
async loadAll<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
rootSelector: string): Promise<T[]> {
const roots = this._rootFinder.all(by.css(rootSelector));
return roots.reduce(
(result: T[], el: ElementFinder) =>
el ? result.concat(new componentHarness(new ProtractorLocator(el))) : result,
[]);
}
}
class ProtractorElement implements TestElement {
constructor(readonly element: ElementFinder) {}
async blur(): Promise<void> {
return this.element['blur']();
}
async clear(): Promise<void> {
return this.element.clear();
}
async click(): Promise<void> {
return this.element.click();
}
async focus(): Promise<void> {
return this.element['focus']();
}
async getCssValue(property: string): Promise<string> {
return this.element.getCssValue(property);
}
async hover(): Promise<void> {
return browser.actions()
.mouseMove(await this.element.getWebElement())
.perform();
}
async sendKeys(keys: string): Promise<void> {
return this.element.sendKeys(keys);
}
async text(): Promise<string> {
return this.element.getText();
}
async getAttribute(name: string): Promise<string|null> {
return this.element.getAttribute(name);
}
}
/**
* Get an element finder based on the CSS selector and root element.
* Note that it will check whether the element is present only when
* Options.allowNull is set. This is for performance purpose.
* @param selector The CSS selector
* @param root Optional Search element under the root element. If not set,
* search element globally. If options.global is set, root is ignored.
* @param options Optional, extra searching options
*/
async function getElement(selector: string, root?: ElementFinder, options?: QueryOptions):
Promise<ElementFinder|null> {
const useGlobalRoot = options && !!options.global;
const elem = root === undefined || useGlobalRoot ?
protractorElement(by.css(selector)) : root.element(by.css(selector));
const allowNull = options !== undefined && options.allowNull !== undefined ?
options.allowNull : undefined;
if (allowNull !== undefined && !(await elem.isPresent())) {
if (allowNull) {
return null;
}
throw Error('Cannot find element based on the CSS selector: ' + selector);
}
return elem;
}
@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './protractor-element';
export * from './protractor-harness-environment';
@@ -0,0 +1,53 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {browser, ElementFinder} from 'protractor';
import {TestElement} from '../test-element';
/** A `TestElement` implementation for Protractor. */
export class ProtractorElement implements TestElement {
constructor(readonly element: ElementFinder) {}
async blur(): Promise<void> {
return browser.executeScript('arguments[0].blur()', this.element);
}
async clear(): Promise<void> {
return this.element.clear();
}
async click(): Promise<void> {
return this.element.click();
}
async focus(): Promise<void> {
return browser.executeScript('arguments[0].focus()', this.element);
}
async getCssValue(property: string): Promise<string> {
return this.element.getCssValue(property);
}
async hover(): Promise<void> {
return browser.actions()
.mouseMove(await this.element.getWebElement())
.perform();
}
async sendKeys(keys: string): Promise<void> {
return this.element.sendKeys(keys);
}
async text(): Promise<string> {
return this.element.getText();
}
async getAttribute(name: string): Promise<string|null> {
return this.element.getAttribute(name);
}
}
@@ -0,0 +1,48 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {by, element as protractorElement, ElementFinder} from 'protractor';
import {HarnessLoader} from '../component-harness';
import {HarnessEnvironment} from '../harness-environment';
import {TestElement} from '../test-element';
import {ProtractorElement} from './protractor-element';
/** A `HarnessEnvironment` implementation for Protractor. */
export class ProtractorHarnessEnvironment extends HarnessEnvironment<ElementFinder> {
protected constructor(rawRootElement: ElementFinder) {
super(rawRootElement);
}
/** Creates a `HarnessLoader` rooted at the document root. */
static loader(): HarnessLoader {
return new ProtractorHarnessEnvironment(protractorElement(by.css('body')));
}
protected getDocumentRoot(): ElementFinder {
return protractorElement(by.css('body'));
}
protected createTestElement(element: ElementFinder): TestElement {
return new ProtractorElement(element);
}
protected createEnvironment(element: ElementFinder): HarnessEnvironment<ElementFinder> {
return new ProtractorHarnessEnvironment(element);
}
protected async getRawElement(selector: string): Promise<ElementFinder | null> {
const element = this.rawRootElement.element(by.css(selector));
return await element.isPresent() ? element : null;
}
protected async getAllRawElements(selector: string): Promise<ElementFinder[]> {
const elements = this.rawRootElement.all(by.css(selector));
return elements.reduce(
(result: ElementFinder[], el: ElementFinder) => el ? result.concat([el]) : result, []);
}
}
+3 -4
View File
@@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as protractor from './protractor';
import * as testbed from './testbed';
export * from './component-harness';
export * from './harness-environment';
export * from './protractor';
export * from './test-element';
export {protractor, testbed};
export * from './testbed';
-207
View File
@@ -1,207 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
dispatchFakeEvent,
dispatchKeyboardEvent,
dispatchMouseEvent,
triggerBlur,
triggerFocus
} from '@angular/cdk/testing';
import {ComponentFixture} from '@angular/core/testing';
import {
ComponentHarness,
ComponentHarnessConstructor,
HarnessLocator,
QueryOptions
} from './component-harness';
import {TestElement} from './test-element';
/**
* Component harness factory for testbed.
* @param componentHarness: Type of user defined harness.
* @param fixture: Component Fixture of the component to be tested.
*/
export function load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
fixture: ComponentFixture<{}>): T {
const stabilize = async () => {
fixture.detectChanges();
await fixture.whenStable();
};
return new componentHarness(new UnitTestLocator(fixture.nativeElement, stabilize));
}
/**
* Gets the corresponding Element for the root of a TestElement.
*/
export function getNativeElement(testElement: TestElement): Element {
if (testElement instanceof UnitTestElement) {
return testElement.element;
}
throw Error(`Expected an instance of UnitTestElement, got ${testElement}`);
}
/**
* Locator implementation for testbed.
* Note that, this locator is exposed for internal usage, please do not use it.
*/
export class UnitTestLocator implements HarnessLocator {
private readonly _rootElement: TestElement;
constructor(private _root: Element, private _stabilize: () => Promise<void>) {
this._rootElement = new UnitTestElement(_root, this._stabilize);
}
host(): TestElement {
return this._rootElement;
}
async querySelector(selector: string, options?: QueryOptions): Promise<TestElement|null> {
await this._stabilize();
const e = getElement(selector, this._root, options);
return e && new UnitTestElement(e, this._stabilize);
}
async querySelectorAll(selector: string): Promise<TestElement[]> {
await this._stabilize();
return Array.prototype.map.call(
this._root.querySelectorAll(selector),
(e: Element) => new UnitTestElement(e, this._stabilize));
}
async load<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>, selector: string,
options?: QueryOptions): Promise<T|null> {
await this._stabilize();
const root = getElement(selector, this._root, options);
return root && new componentHarness(new UnitTestLocator(root, this._stabilize));
}
async loadAll<T extends ComponentHarness>(
componentHarness: ComponentHarnessConstructor<T>,
rootSelector: string): Promise<T[]> {
await this._stabilize();
return Array.prototype.map.call(
this._root.querySelectorAll(rootSelector),
(e: Element) => new componentHarness(new UnitTestLocator(e, this._stabilize)));
}
}
class UnitTestElement implements TestElement {
constructor(readonly element: Element, private _stabilize: () => Promise<void>) {}
async blur(): Promise<void> {
await this._stabilize();
triggerBlur(this.element as HTMLElement);
await this._stabilize();
}
async clear(): Promise<void> {
await this._stabilize();
if (!this._isTextInput(this.element)) {
throw Error('Attempting to clear an invalid element');
}
triggerFocus(this.element as HTMLElement);
this.element.value = '';
dispatchFakeEvent(this.element, 'input');
await this._stabilize();
}
async click(): Promise<void> {
await this._stabilize();
dispatchMouseEvent(this.element, 'click');
await this._stabilize();
}
async focus(): Promise<void> {
await this._stabilize();
triggerFocus(this.element as HTMLElement);
await this._stabilize();
}
async getCssValue(property: string): Promise<string> {
await this._stabilize();
// TODO(mmalerba): Consider adding value normalization if we run into common cases where its
// needed.
return getComputedStyle(this.element).getPropertyValue(property);
}
async hover(): Promise<void> {
await this._stabilize();
dispatchMouseEvent(this.element, 'mouseenter');
await this._stabilize();
}
async sendKeys(keys: string): Promise<void> {
await this._stabilize();
triggerFocus(this.element as HTMLElement);
for (const key of keys) {
const keyCode = key.charCodeAt(0);
dispatchKeyboardEvent(this.element, 'keydown', keyCode);
dispatchKeyboardEvent(this.element, 'keypress', keyCode);
if (this._isTextInput(this.element)) {
this.element.value += key;
}
dispatchKeyboardEvent(this.element, 'keyup', keyCode);
if (this._isTextInput(this.element)) {
dispatchFakeEvent(this.element, 'input');
}
}
await this._stabilize();
}
async text(): Promise<string> {
await this._stabilize();
return this.element.textContent || '';
}
async getAttribute(name: string): Promise<string|null> {
await this._stabilize();
let value = this.element.getAttribute(name);
// If cannot find attribute in the element, also try to find it in property,
// this is useful for input/textarea tags.
if (value === null && name in this.element) {
// We need to cast the element so we can access its properties via string indexing.
return (this.element as unknown as {[key: string]: string|null})[name];
}
return value;
}
private _isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement {
return element.nodeName.toLowerCase() === 'input' ||
element.nodeName.toLowerCase() === 'textarea' ;
}
}
/**
* Get an element based on the CSS selector and root element.
* @param selector The CSS selector
* @param root Search element under the root element. If options.global is set,
* root is ignored.
* @param options Optional, extra searching options
* @return When element is not present, return null if Options.allowNull is set
* to true, throw an error if Options.allowNull is set to false; otherwise,
* return the element
*/
function getElement(selector: string, root: Element, options?: QueryOptions): Element|null {
const useGlobalRoot = options && options.global;
const elem = (useGlobalRoot ? document : root).querySelector(selector);
const allowNull = options !== undefined && options.allowNull !== undefined ?
options.allowNull : undefined;
if (elem === null) {
if (allowNull) {
return null;
}
throw Error('Cannot find element based on the CSS selector: ' + selector);
}
return elem;
}
@@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './testbed-harness-environment';
export * from './unit-test-element';
@@ -0,0 +1,65 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentFixture} from '@angular/core/testing';
import {ComponentHarness, ComponentHarnessConstructor, HarnessLoader} from '../component-harness';
import {HarnessEnvironment} from '../harness-environment';
import {TestElement} from '../test-element';
import {UnitTestElement} from './unit-test-element';
/** A `HarnessEnvironment` implementation for Angular's Testbed. */
export class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
protected constructor(rawRootElement: Element, private _fixture: ComponentFixture<unknown>) {
super(rawRootElement);
}
/** Creates a `HarnessLoader` rooted at the given fixture's root element. */
static loader(fixture: ComponentFixture<unknown>): HarnessLoader {
return new TestbedHarnessEnvironment(fixture.nativeElement, fixture);
}
/**
* Creates an instance of the given harness type, using the fixture's root element as the
* harness's host element. This method should be used when creating a harness for the root element
* of a fixture, as components do not have the correct selector when they are created as the root
* of the fixture.
*/
static async harnessForFixture<T extends ComponentHarness>(
fixture: ComponentFixture<unknown>, harnessType: ComponentHarnessConstructor<T>): Promise<T> {
const environment = new TestbedHarnessEnvironment(fixture.nativeElement, fixture);
await environment._stabilize();
return environment.createComponentHarness(harnessType, fixture.nativeElement);
}
protected getDocumentRoot(): Element {
return this._fixture.nativeElement;
}
protected createTestElement(element: Element): TestElement {
return new UnitTestElement(element, this._stabilize.bind(this));
}
protected createEnvironment(element: Element): HarnessEnvironment<Element> {
return new TestbedHarnessEnvironment(element, this._fixture);
}
protected async getRawElement(selector: string): Promise<Element | null> {
await this._stabilize();
return this.rawRootElement.querySelector(selector) || null;
}
protected async getAllRawElements(selector: string): Promise<Element[]> {
await this._stabilize();
return Array.from(this.rawRootElement.querySelectorAll(selector));
}
private async _stabilize(): Promise<void> {
this._fixture.detectChanges();
await this._fixture.whenStable();
}
}
@@ -0,0 +1,103 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
dispatchFakeEvent,
dispatchKeyboardEvent,
dispatchMouseEvent,
triggerBlur,
triggerFocus
} from '@angular/cdk/testing';
import {TestElement} from '../test-element';
function isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement {
return element.nodeName.toLowerCase() === 'input' ||
element.nodeName.toLowerCase() === 'textarea' ;
}
/** A `TestElement` implementation for unit tests. */
export class UnitTestElement implements TestElement {
constructor(readonly element: Element, private _stabilize: () => Promise<void>) {}
async blur(): Promise<void> {
await this._stabilize();
triggerBlur(this.element as HTMLElement);
await this._stabilize();
}
async clear(): Promise<void> {
await this._stabilize();
if (!isTextInput(this.element)) {
throw Error('Attempting to clear an invalid element');
}
triggerFocus(this.element as HTMLElement);
this.element.value = '';
dispatchFakeEvent(this.element, 'input');
await this._stabilize();
}
async click(): Promise<void> {
await this._stabilize();
dispatchMouseEvent(this.element, 'click');
await this._stabilize();
}
async focus(): Promise<void> {
await this._stabilize();
triggerFocus(this.element as HTMLElement);
await this._stabilize();
}
async getCssValue(property: string): Promise<string> {
await this._stabilize();
// TODO(mmalerba): Consider adding value normalization if we run into common cases where its
// needed.
return getComputedStyle(this.element).getPropertyValue(property);
}
async hover(): Promise<void> {
await this._stabilize();
dispatchMouseEvent(this.element, 'mouseenter');
await this._stabilize();
}
async sendKeys(keys: string): Promise<void> {
await this._stabilize();
triggerFocus(this.element as HTMLElement);
for (const key of keys) {
const keyCode = key.charCodeAt(0);
dispatchKeyboardEvent(this.element, 'keydown', keyCode);
dispatchKeyboardEvent(this.element, 'keypress', keyCode);
if (isTextInput(this.element)) {
this.element.value += key;
}
dispatchKeyboardEvent(this.element, 'keyup', keyCode);
if (isTextInput(this.element)) {
dispatchFakeEvent(this.element, 'input');
}
}
await this._stabilize();
}
async text(): Promise<string> {
await this._stabilize();
return this.element.textContent || '';
}
async getAttribute(name: string): Promise<string|null> {
await this._stabilize();
let value = this.element.getAttribute(name);
// If cannot find attribute in the element, also try to find it in property,
// this is useful for input/textarea tags.
if (value === null && name in this.element) {
// We need to cast the element so we can access its properties via string indexing.
return (this.element as unknown as {[key: string]: string|null})[name];
}
return value;
}
}
@@ -10,33 +10,40 @@ import {ComponentHarness} from '../../component-harness';
import {TestElement} from '../../test-element';
import {SubComponentHarness} from './sub-component-harness';
export class WrongComponentHarness extends ComponentHarness {
static readonly hostSelector = 'wrong-selector';
}
export class MainComponentHarness extends ComponentHarness {
readonly title = this.find('h1');
readonly asyncCounter = this.find('#asyncCounter');
readonly counter = this.find('#counter');
readonly input = this.find('#input');
readonly value = this.find('#value');
readonly allLabels = this.findAll('label');
readonly allLists = this.findAll(SubComponentHarness, 'test-sub');
readonly memo = this.find('textarea');
static readonly hostSelector = 'test-main';
readonly title = this.locatorFor('h1');
readonly button = this.locatorFor('button');
readonly asyncCounter = this.locatorFor('#asyncCounter');
readonly counter = this.locatorFor('#counter');
readonly input = this.locatorFor('#input');
readonly value = this.locatorFor('#value');
readonly allLabels = this.locatorForAll('label');
readonly allLists = this.locatorForAll(SubComponentHarness);
readonly memo = this.locatorFor('textarea');
// Allow null for element
readonly nullItem = this.find('wrong locator', {allowNull: true});
readonly nullItem = this.locatorForOptional('wrong locator');
// Allow null for component harness
readonly nullComponentHarness =
this.find(SubComponentHarness, 'wrong locator', {allowNull: true});
readonly errorItem = this.find('wrong locator', {allowNull: false});
readonly nullComponentHarness = this.locatorForOptional(WrongComponentHarness);
readonly errorItem = this.locatorFor('wrong locator');
readonly globalEl = this.find('.sibling', {global: true});
readonly errorGlobalEl =
this.find('wrong locator', {global: true, allowNull: false});
readonly nullGlobalEl =
this.find('wrong locator', {global: true, allowNull: true});
readonly globalEl = this.documentRootLocatorFactory().locatorFor('.sibling');
readonly errorGlobalEl = this.documentRootLocatorFactory().locatorFor('wrong locator');
readonly nullGlobalEl = this.documentRootLocatorFactory().locatorForOptional('wrong locator');
private _button = this.find('button');
private _testTools = this.find(SubComponentHarness, 'test-sub');
readonly optionalDiv = this.locatorForOptional('div');
readonly optionalSubComponent = this.locatorForOptional(SubComponentHarness);
readonly errorSubComponent = this.locatorFor(WrongComponentHarness);
private _testTools = this.locatorFor(SubComponentHarness);
async increaseCounter(times: number) {
const button = await this._button();
const button = await this.button();
for (let i = 0; i < times; i++) {
await button.click();
}
@@ -10,8 +10,11 @@ import {ComponentHarness} from '../../component-harness';
import {TestElement} from '../../test-element';
export class SubComponentHarness extends ComponentHarness {
readonly title = this.find('h2');
readonly getItems = this.findAll('li');
static readonly hostSelector = 'test-sub';
readonly title = this.locatorFor('h2');
readonly getItems = this.locatorForAll('li');
readonly globalElement = this.documentRootLocatorFactory().locatorFor('#username');
async getItem(index: number): Promise<TestElement> {
const items = await this.getItems();
@@ -1,31 +1,106 @@
import {browser, by, element} from 'protractor';
import {getElementFinder, load} from '../protractor';
import {browser} from 'protractor';
import {HarnessLoader} from '../component-harness';
import {ProtractorHarnessEnvironment} from '../protractor';
import {MainComponentHarness} from './harnesses/main-component-harness';
import {SubComponentHarness} from './harnesses/sub-component-harness';
describe('Protractor Helper Test', () => {
let harness: MainComponentHarness;
describe('ProtractorHarnessEnvironment', () => {
beforeEach(async () => {
await browser.get('/component-harness');
harness = await load(MainComponentHarness, 'test-main');
});
describe('Locator', () => {
it('should be able to locate a element based on CSS selector', async () => {
describe('HarnessLoader', () => {
let loader: HarnessLoader;
beforeEach(async () => {
loader = ProtractorHarnessEnvironment.loader();
});
it('should create HarnessLoader', async () => {
expect(loader).not.toBeNull();
});
it('should find required HarnessLoader for child element', async () => {
const subcomponentsLoader = await loader.getChildLoader('.subcomponents');
expect(subcomponentsLoader).not.toBeNull();
});
it('should error after failing to find required HarnessLoader for child element', async () => {
try {
await loader.getChildLoader('error');
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "error", but none was found');
}
});
it('should find all HarnessLoaders for child elements', async () => {
const loaders = await loader.getAllChildLoaders('.subcomponents,.counters');
expect(loaders.length).toBe(2);
});
it('should get first matching component for required harness', async () => {
const harness = await loader.getHarness(SubComponentHarness);
expect(harness).not.toBeNull();
expect(await (await harness.title()).text()).toBe('List of test tools');
});
it('should throw if no matching component found for required harness', async () => {
const countersLoader = await loader.getChildLoader('.counters');
try {
await countersLoader.getHarness(SubComponentHarness);
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "test-sub", but none was found');
}
});
it('should get all matching components for all harnesses', async () => {
const harnesses = await loader.getAllHarnesses(SubComponentHarness);
expect(harnesses.length).toBe(2);
});
});
describe('ComponentHarness', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness);
});
it('should locate a required element based on CSS selector', async () => {
const title = await harness.title();
expect(await title.text()).toBe('Main Component');
});
it('should be able to locate all elements based on CSS selector',
async () => {
const labels = await harness.allLabels();
expect(labels.length).toBe(2);
expect(await labels[0].text()).toBe('Count:');
expect(await labels[1].text()).toBe('AsyncCounter:');
});
it('should throw when failing to locate a required element based on CSS selector', async () => {
try {
await harness.errorItem();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong locator", but none was found');
}
});
it('should be able to locate the sub harnesses', async () => {
it('should locate an optional element based on CSS selector', async () => {
const present = await harness.optionalDiv();
const missing = await harness.nullItem();
expect(present).not.toBeNull();
expect(await present!.text()).toBe('Hello Yi from Angular 2!');
expect(missing).toBeNull();
});
it('should locate all elements based on CSS selector', async () => {
const labels = await harness.allLabels();
expect(labels.length).toBe(2);
expect(await labels[0].text()).toBe('Count:');
expect(await labels[1].text()).toBe('AsyncCounter:');
});
it('should locate required sub harnesses', async () => {
const items = await harness.getTestTools();
expect(items.length).toBe(3);
expect(await items[0].text()).toBe('Protractor');
@@ -33,7 +108,25 @@ describe('Protractor Helper Test', () => {
expect(await items[2].text()).toBe('Other');
});
it('should be able to locate all sub harnesses', async () => {
it('should throw when failing to locate required sub harnesses', async () => {
try {
await harness.errorSubComponent();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong-selector", but none was found');
}
});
it('should locate optional sub harnesses', async () => {
const present = await harness.optionalSubComponent();
const missing = await harness.nullComponentHarness();
expect(present).not.toBeNull();
expect(await (await present!.title()).text()).toBe('List of test tools');
expect(missing).toBeNull();
});
it('should locate all sub harnesses', async () => {
const alllists = await harness.allLists();
const items1 = await alllists[0].getItems();
const items2 = await alllists[1].getItems();
@@ -47,9 +140,27 @@ describe('Protractor Helper Test', () => {
expect(await items2[1].text()).toBe('Integration Test');
expect(await items2[2].text()).toBe('Performance Test');
});
it('should wait for async operation to complete', async () => {
const asyncCounter = await harness.asyncCounter();
expect(await asyncCounter.text()).toBe('5');
await harness.increaseCounter(3);
expect(await asyncCounter.text()).toBe('8');
});
it('can get elements outside of host', async () => {
const globalEl = await harness.globalEl();
expect(await globalEl.text()).toBe('I am a sibling!');
});
});
describe('Test element', () => {
describe('TestElement', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness = await ProtractorHarnessEnvironment.loader().getHarness(MainComponentHarness);
});
it('should be able to clear', async () => {
const input = await harness.input();
await input.sendKeys('Yi');
@@ -79,8 +190,7 @@ describe('Protractor Helper Test', () => {
const input = await harness.input();
await input.sendKeys('Yi');
expect(await input.getAttribute('id'))
.toBe(await browser.driver.switchTo().activeElement().getAttribute(
'id'));
.toBe(await browser.driver.switchTo().activeElement().getAttribute('id'));
});
it('should be able to hover', async () => {
@@ -106,78 +216,16 @@ describe('Protractor Helper Test', () => {
const title = await harness.title();
expect(await title.getCssValue('height')).toBe('50px');
});
});
describe('Async operation', () => {
it('should wait for async opeartion to complete', async () => {
const asyncCounter = await harness.asyncCounter();
expect(await asyncCounter.text()).toBe('5');
await harness.increaseCounter(3);
expect(await asyncCounter.text()).toBe('8');
});
});
describe('Allow null', () => {
it('should allow element to be null when setting allowNull', async () => {
expect(await harness.nullItem()).toBe(null);
});
it('should allow main harness to be null when setting allowNull',
async () => {
const nullMainHarness = await load(
MainComponentHarness, 'harness not present', {allowNull: true});
expect(nullMainHarness).toBe(null);
});
it('should allow sub-harness to be null when setting allowNull',
async () => {
expect(await harness.nullComponentHarness()).toBe(null);
});
});
describe('with the global option', () => {
it('should find an element outside the root of the harness', async () => {
const globalEl = await harness.globalEl();
expect(await globalEl.text()).toBe('I am a sibling!');
});
it('should return null for a selector that does not exist', async () => {
expect(await harness.nullGlobalEl()).toBeNull();
});
it('should throw an error for a selctor that does not exist ' +
'with allowNull = false',
async () => {
try {
await harness.errorGlobalEl();
fail('Should throw error');
} catch (err) {
expect(err.message)
.toBe(
'Cannot find element based on the CSS selector: wrong locator');
}
});
});
describe('Throw error', () => {
it('should show the correct error', async () => {
try {
await harness.errorItem();
fail('Should throw error');
} catch (err) {
expect(err.message)
.toBe(
'Cannot find element based on the CSS selector: wrong locator');
}
});
});
describe('getElementFinder', () => {
it('should return the element finder', async () => {
const mainElement = await element(by.css('test-main'));
const elementFromHarness = getElementFinder(harness.host());
expect(await elementFromHarness.getId())
.toBe(await mainElement.getId());
it('should focus and blur element', async () => {
let button = await harness.button();
expect(await (await browser.switchTo().activeElement()).getText())
.not.toBe(await button.text());
await button.focus();
expect(await (await browser.switchTo().activeElement()).getText()).toBe(await button.text());
await button.blur();
expect(await (await browser.switchTo().activeElement()).getText())
.not.toBe(await button.text());
});
});
});
@@ -1,12 +1,18 @@
<h1 style="height: 50px">Main Component</h1>
<div id="username">Hello {{username}} from Angular 2!</div>
<button (click)="click()">Up</button><br>
<label>Count:</label>
<div id="counter">{{counter}}</div>
<label>AsyncCounter:</label>
<div id="asyncCounter">{{asyncCounter}}</div>
<input [(ngModel)]="input" id="input" aria-label="input">
<div id="value">Input: {{input}}</div>
<textarea id="memo" aria-label="memo">{{memo}}</textarea>
<test-sub title="test tools" [items]="testTools"></test-sub>
<test-sub title="test methods" [items]="testMethods"></test-sub>
<div class="counters">
<button (click)="click()">Up</button><br>
<label>Count:</label>
<div id="counter">{{counter}}</div>
<label>AsyncCounter:</label>
<div id="asyncCounter">{{asyncCounter}}</div>
</div>
<div class="inputs">
<input [(ngModel)]="input" id="input" aria-label="input">
<div id="value">Input: {{input}}</div>
<textarea id="memo" aria-label="memo">{{memo}}</textarea>
</div>
<div class="subcomponents">
<test-sub title="test tools" [items]="testTools"></test-sub>
<test-sub title="test methods" [items]="testMethods"></test-sub>
</div>
@@ -1,40 +1,122 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {getNativeElement, load} from '../testbed';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '../component-harness';
import {TestbedHarnessEnvironment} from '../testbed/index';
import {MainComponentHarness} from './harnesses/main-component-harness';
import {SubComponentHarness} from './harnesses/sub-component-harness';
import {TestComponentsModule} from './test-components-module';
import {TestMainComponent} from './test-main-component';
describe('Testbed Helper Test', () => {
let harness: MainComponentHarness;
let fixture: ComponentFixture<{}>;
beforeEach(async(() => {
TestBed
.configureTestingModule({
imports: [TestComponentsModule],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestMainComponent);
harness = load(MainComponentHarness, fixture);
});
}));
function activeElementText() {
return document.activeElement && (document.activeElement as HTMLElement).innerText || '';
}
describe('Locator', () => {
it('should be able to locate a element based on CSS selector', async () => {
describe('TestbedHarnessEnvironment', () => {
let fixture: ComponentFixture<{}>;
beforeEach(async () => {
await TestBed.configureTestingModule({imports: [TestComponentsModule]}).compileComponents();
fixture = TestBed.createComponent(TestMainComponent);
});
describe('HarnessLoader', () => {
let loader: HarnessLoader;
beforeEach(async () => {
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should create HarnessLoader from fixture', async () => {
expect(loader).not.toBeNull();
});
it('should create ComponentHarness for fixture', async () => {
const harness =
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
expect(harness).not.toBeNull();
});
it('should find required HarnessLoader for child element', async () => {
const subcomponentsLoader = await loader.getChildLoader('.subcomponents');
expect(subcomponentsLoader).not.toBeNull();
});
it('should error after failing to find required HarnessLoader for child element', async () => {
try {
await loader.getChildLoader('error');
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "error", but none was found');
}
});
it('should find all HarnessLoaders for child elements', async () => {
const loaders = await loader.getAllChildLoaders('.subcomponents,.counters');
expect(loaders.length).toBe(2);
});
it('should get first matching component for required harness', async () => {
const harness = await loader.getHarness(SubComponentHarness);
expect(harness).not.toBeNull();
expect(await (await harness.title()).text()).toBe('List of test tools');
});
it('should throw if no matching component found for required harness', async () => {
const countersLoader = await loader.getChildLoader('.counters');
try {
await countersLoader.getHarness(SubComponentHarness);
fail('Expected to throw');
} catch (e) {
expect(e.message)
.toBe('Expected to find element matching selector: "test-sub", but none was found');
}
});
it('should get all matching components for all harnesses', async () => {
const harnesses = await loader.getAllHarnesses(SubComponentHarness);
expect(harnesses.length).toBe(2);
});
});
describe('ComponentHarness', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness =
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
});
it('should locate a required element based on CSS selector', async () => {
const title = await harness.title();
expect(await title.text()).toBe('Main Component');
});
it('should be able to locate all elements based on CSS selector',
async () => {
const labels = await harness.allLabels();
expect(labels.length).toBe(2);
expect(await labels[0].text()).toBe('Count:');
expect(await labels[1].text()).toBe('AsyncCounter:');
});
it('should throw when failing to locate a required element based on CSS selector', async () => {
try {
await harness.errorItem();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong locator", but none was found');
}
});
it('should be able to locate the sub harnesses', async () => {
it('should locate an optional element based on CSS selector', async () => {
const present = await harness.optionalDiv();
const missing = await harness.nullItem();
expect(present).not.toBeNull();
expect(await present!.text()).toBe('Hello Yi from Angular 2!');
expect(missing).toBeNull();
});
it('should locate all elements based on CSS selector', async () => {
const labels = await harness.allLabels();
expect(labels.length).toBe(2);
expect(await labels[0].text()).toBe('Count:');
expect(await labels[1].text()).toBe('AsyncCounter:');
});
it('should locate required sub harnesses', async () => {
const items = await harness.getTestTools();
expect(items.length).toBe(3);
expect(await items[0].text()).toBe('Protractor');
@@ -42,7 +124,25 @@ describe('Testbed Helper Test', () => {
expect(await items[2].text()).toBe('Other');
});
it('should be able to locate all sub harnesses', async () => {
it('should throw when failing to locate required sub harnesses', async () => {
try {
await harness.errorSubComponent();
fail('Expected to throw');
} catch (e) {
expect(e.message).toBe(
'Expected to find element matching selector: "wrong-selector", but none was found');
}
});
it('should locate optional sub harnesses', async () => {
const present = await harness.optionalSubComponent();
const missing = await harness.nullComponentHarness();
expect(present).not.toBeNull();
expect(await (await present!.title()).text()).toBe('List of test tools');
expect(missing).toBeNull();
});
it('should locate all sub harnesses', async () => {
const alllists = await harness.allLists();
const items1 = await alllists[0].getItems();
const items2 = await alllists[1].getItems();
@@ -56,9 +156,31 @@ describe('Testbed Helper Test', () => {
expect(await items2[1].text()).toBe('Integration Test');
expect(await items2[2].text()).toBe('Performance Test');
});
it('should wait for async operation to complete', async () => {
const asyncCounter = await harness.asyncCounter();
expect(await asyncCounter.text()).toBe('5');
await harness.increaseCounter(3);
expect(await asyncCounter.text()).toBe('8');
});
it('can get elements outside of host', async () => {
const subcomponents = await harness.allLists();
expect(subcomponents[0]).not.toBeNull();
const globalEl = await subcomponents[0]!.globalElement();
expect(globalEl).not.toBeNull();
expect(await globalEl.text()).toBe('Hello Yi from Angular 2!');
});
});
describe('Test element', () => {
describe('TestElement', () => {
let harness: MainComponentHarness;
beforeEach(async () => {
harness =
await TestbedHarnessEnvironment.harnessForFixture(fixture, MainComponentHarness);
});
it('should be able to clear', async () => {
const input = await harness.input();
await input.sendKeys('Yi');
@@ -87,8 +209,7 @@ describe('Testbed Helper Test', () => {
it('focuses the element before sending key', async () => {
const input = await harness.input();
await input.sendKeys('Yi');
expect(await input.getAttribute('id'))
.toBe(document.activeElement!.id);
expect(await input.getAttribute('id')).toBe(document.activeElement!.id);
});
it('should be able to hover', async () => {
@@ -114,43 +235,14 @@ describe('Testbed Helper Test', () => {
const title = await harness.title();
expect(await title.getCssValue('height')).toBe('50px');
});
});
describe('Async operation', () => {
it('should wait for async opeartion to complete', async () => {
const asyncCounter = await harness.asyncCounter();
expect(await asyncCounter.text()).toBe('5');
await harness.increaseCounter(3);
expect(await asyncCounter.text()).toBe('8');
});
});
describe('Allow null', () => {
it('should allow element to be null when setting allowNull', async () => {
expect(await harness.nullItem()).toBe(null);
});
it('should allow harness to be null when setting allowNull', async () => {
expect(await harness.nullComponentHarness()).toBe(null);
});
});
describe('Throw error', () => {
it('should show the correct error', async () => {
try {
await harness.errorItem();
fail('Should throw error');
} catch (err) {
expect(err.message)
.toBe(
'Cannot find element based on the CSS selector: wrong locator');
}
});
});
describe('getNativeElement', () => {
it('should return the native element', async () => {
expect(getNativeElement(harness.host())).toBe(fixture.nativeElement);
it('should focus and blur element', async () => {
let button = await harness.button();
expect(activeElementText()).not.toBe(await button.text());
await button.focus();
expect(activeElementText()).toBe(await button.text());
await button.blur();
expect(activeElementText()).not.toBe(await button.text());
});
});
});