@badisi/wdio-harness 3.1.5 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Badisi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ <h1 align="center">
2
+ @badisi/wdio-harness
3
+ </h1>
4
+
5
+ <p align="center">
6
+ <i>🔬 <a href="https://webdriver.io" alt="wdio">WebdriverIO</a> support for Angular component test harnesses.</i><br/>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@badisi/wdio-harness">
11
+ <img src="https://img.shields.io/npm/v/@badisi/wdio-harness.svg?color=blue&logo=npm" alt="npm version" /></a>
12
+ <a href="https://npmcharts.com/compare/@badisi/wdio-harness?minimal=true">
13
+ <img src="https://img.shields.io/npm/dw/@badisi/wdio-harness.svg?color=7986CB&logo=npm" alt="npm donwloads" /></a>
14
+ <a href="https://github.com/badisi/wdio-harness/blob/main/LICENSE">
15
+ <img src="https://img.shields.io/npm/l/@badisi/wdio-harness.svg?color=ff69b4" alt="license" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://github.com/Badisi/wdio-harness/actions/workflows/ci_tests.yml">
20
+ <img src="https://github.com/Badisi/wdio-harness/actions/workflows/ci_tests.yml/badge.svg" alt="build status" /></a>
21
+ <a href="https://github.com/badisi/wdio-harness/blob/main/CONTRIBUTING.md#-submitting-a-pull-request-pr">
22
+ <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" /></a>
23
+ </p>
24
+
25
+ <hr/>
26
+
27
+ #### Component test harnesses
28
+
29
+ > A component harness is a class that lets a test interact with a component via a supported API. Each harness's API interacts with a component the same way a user would. By using the harness API, a test insulates itself against updates to the internals of a component, such as changing its DOM structure. The idea for component harnesses comes from the [PageObject](https://martinfowler.com/bliki/PageObject.html) pattern commonly used for integration testing.
30
+
31
+ [More info](https://material.angular.io/cdk/test-harnesses/overview)
32
+
33
+ <hr/>
34
+
35
+ ## Installation
36
+
37
+ ```sh
38
+ npm install @badisi/wdio-harness --save-dev
39
+ ```
40
+
41
+ ```sh
42
+ yarn add @badisi/wdio-harness --dev
43
+ ```
44
+
45
+
46
+ ## Usage
47
+
48
+ __Methods__
49
+
50
+ - `createHarnessEnvironment(rootElement)` - gets a HarnessLoader instance from a given element (defaults to body)
51
+ - `getHarness(harnessType, element)` - searches for an harness instance from a given ComponentHarness class and element
52
+ - `getHarness(harnessType)` - searches for an harness instance from a given ComponentHarness class
53
+ - `getHarness(query)` - searches for an harness instance from a given HarnessPredicate
54
+ - `getAllHarnesses(query)` - acts like getHarness, but returns an array of harness instances
55
+ - `waitForAngular()` - waits for Angular to finish bootstrapping
56
+
57
+ __Example__
58
+
59
+ ```ts
60
+ import { MatDatepickerInputHarness } from '@angular/material/datepicker/testing';
61
+ import { getHarness } from '@badisi/wdio-harness';
62
+
63
+ describe('Angular Material Harness', () => {
64
+ beforeEach(async () => {
65
+ await browser.url('http://localhost:4200');
66
+ });
67
+
68
+ it('MatDatePicker', async () => {
69
+ const datepicker = await getHarness(MatDatepickerInputHarness.with({ selector: '#demo-datepicker-input' }));
70
+
71
+ await datepicker.setValue('9/27/1954');
72
+ expect(await datepicker.getValue()).withContext('Date should be 9/27/1954').toBe('9/27/1954');
73
+
74
+ await datepicker.openCalendar();
75
+ const calendar = await datepicker.getCalendar();
76
+ await calendar.next();
77
+ await calendar.selectCell({ text: '20' });
78
+ expect(await datepicker.getValue()).withContext('Date should be 10/20/1954').toBe('10/20/1954');
79
+ });
80
+ });
81
+ ```
82
+
83
+ More examples [here][examples].
84
+
85
+
86
+ ## Development
87
+
88
+ See the [developer docs][developer].
89
+
90
+
91
+ ## Contributing
92
+
93
+ #### > Want to Help ?
94
+
95
+ Want to file a bug, contribute some code or improve documentation ? Excellent!
96
+
97
+ But please read up first on the guidelines for [contributing][contributing], and learn about submission process, coding rules and more.
98
+
99
+ #### > Code of Conduct
100
+
101
+ Please read and follow the [Code of Conduct][codeofconduct] and help me keep this project open and inclusive.
102
+
103
+
104
+
105
+
106
+ [developer]: https://github.com/badisi/wdio-harness/blob/main/DEVELOPER.md
107
+ [contributing]: https://github.com/badisi/wdio-harness/blob/main/CONTRIBUTING.md
108
+ [codeofconduct]: https://github.com/badisi/wdio-harness/blob/main/CODE_OF_CONDUCT.md
109
+ [examples]: https://github.com/badisi/wdio-harness/blob/main/projects/tests-e2e/harness.e2e.ts
@@ -0,0 +1,34 @@
1
+ import { ComponentHarness, ComponentHarnessConstructor, HarnessEnvironment } from '@angular/cdk/testing';
2
+ import { WebdriverIOTestElement } from './WebdriverIOTestElement.js';
3
+ /**
4
+ * A `HarnessEnvironment` implementation for WebdriverIO.
5
+ */
6
+ export declare class WebdriverIOHarnessEnvironment extends HarnessEnvironment<WebdriverIO.Element> {
7
+ /**
8
+ * Keep a reference to the `document` element because `rawRootElement`
9
+ * will be the root element of the harness's environment.
10
+ */
11
+ private documentRoot;
12
+ protected constructor(rawRootElement: WebdriverIO.Element, options: {
13
+ documentRoot: WebdriverIO.Element;
14
+ });
15
+ /** Creates a `HarnessLoader` rooted at the document root. */
16
+ static loader(documentRoot: WebdriverIO.Element): Promise<WebdriverIOHarnessEnvironment>;
17
+ /**
18
+ * Flushes change detection and async tasks captured in the Angular zone.
19
+ * In most cases it should not be necessary to call this manually. However, there may be some edge
20
+ * cases where it is needed to fully flush animation events.
21
+ */
22
+ forceStabilize(): Promise<void>;
23
+ waitForTasksOutsideAngular(): Promise<void>;
24
+ /** Creates a `ComponentHarness` for the given harness type with the given raw host element. */
25
+ createComponentHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>, element: WebdriverIO.Element): T;
26
+ /** Gets a list of all elements matching the given selector under this environment's root element. */
27
+ protected getAllRawElements(selector: string): Promise<WebdriverIO.Element[]>;
28
+ /** Gets the root element for the document. */
29
+ protected getDocumentRoot(): WebdriverIO.Element;
30
+ /** Creates a `TestElement` from a raw element. */
31
+ protected createTestElement(element: WebdriverIO.Element): WebdriverIOTestElement;
32
+ /** Creates a `HarnessLoader` rooted at the given raw element. */
33
+ protected createEnvironment(element: WebdriverIO.Element): HarnessEnvironment<WebdriverIO.Element>;
34
+ }
@@ -1,79 +1,60 @@
1
- import { ComponentHarness, ComponentHarnessConstructor, HarnessEnvironment } from '@angular/cdk/testing';
1
+ import { HarnessEnvironment } from '@angular/cdk/testing';
2
2
  import logger from '@wdio/logger';
3
-
4
3
  import { WebdriverIOTestElement } from './WebdriverIOTestElement.js';
5
-
6
4
  import colors from '@colors/colors/safe.js';
7
5
  const { magenta, green } = colors;
8
-
9
6
  /** Registers the environment logger. */
10
7
  const log = logger('wdio-harness');
11
-
12
8
  /**
13
9
  * A `HarnessEnvironment` implementation for WebdriverIO.
14
10
  */
15
- export class WebdriverIOHarnessEnvironment extends HarnessEnvironment<WebdriverIO.Element> {
11
+ export class WebdriverIOHarnessEnvironment extends HarnessEnvironment {
16
12
  /**
17
13
  * Keep a reference to the `document` element because `rawRootElement`
18
14
  * will be the root element of the harness's environment.
19
15
  */
20
- private documentRoot: WebdriverIO.Element;
21
-
22
- protected constructor(
23
- rawRootElement: WebdriverIO.Element,
24
- options: { documentRoot: WebdriverIO.Element; }
25
- ) {
16
+ documentRoot;
17
+ constructor(rawRootElement, options) {
26
18
  super(rawRootElement);
27
19
  this.documentRoot = options.documentRoot;
28
20
  }
29
-
30
21
  /** Creates a `HarnessLoader` rooted at the document root. */
31
- static async loader(documentRoot: WebdriverIO.Element): Promise<WebdriverIOHarnessEnvironment> {
22
+ static async loader(documentRoot) {
32
23
  return new WebdriverIOHarnessEnvironment(documentRoot, { documentRoot });
33
24
  }
34
-
35
25
  /**
36
26
  * Flushes change detection and async tasks captured in the Angular zone.
37
27
  * In most cases it should not be necessary to call this manually. However, there may be some edge
38
28
  * cases where it is needed to fully flush animation events.
39
29
  */
40
- async forceStabilize(): Promise<void> {
30
+ async forceStabilize() {
41
31
  /* await browser.executeAsyncScript(`
42
32
  const done = arguments[0];
43
33
  window.requestAnimationFrame(done);
44
34
  `, []);*/
45
35
  }
46
-
47
- async waitForTasksOutsideAngular(): Promise<void> {
36
+ async waitForTasksOutsideAngular() {
48
37
  throw new Error('Method not implemented.');
49
38
  }
50
-
51
39
  /** Creates a `ComponentHarness` for the given harness type with the given raw host element. */
52
- createComponentHarness<T extends ComponentHarness>(
53
- harnessType: ComponentHarnessConstructor<T>,
54
- element: WebdriverIO.Element
55
- ): T {
40
+ createComponentHarness(harnessType, element) {
56
41
  return super.createComponentHarness(harnessType, element);
57
42
  }
58
-
59
43
  /** Gets a list of all elements matching the given selector under this environment's root element. */
60
- protected async getAllRawElements(selector: string): Promise<WebdriverIO.Element[]> {
44
+ async getAllRawElements(selector) {
61
45
  log.info(`${magenta('GET_ALL_RAW_ELEMENTS')} ${green(selector.toString())}`);
62
46
  return [...(await this.rawRootElement.$$(selector))];
63
47
  }
64
-
65
48
  /** Gets the root element for the document. */
66
- protected getDocumentRoot(): WebdriverIO.Element {
49
+ getDocumentRoot() {
67
50
  return this.documentRoot;
68
51
  }
69
-
70
52
  /** Creates a `TestElement` from a raw element. */
71
- protected createTestElement(element: WebdriverIO.Element): WebdriverIOTestElement {
53
+ createTestElement(element) {
72
54
  return new WebdriverIOTestElement(element);
73
55
  }
74
-
75
56
  /** Creates a `HarnessLoader` rooted at the given raw element. */
76
- protected createEnvironment(element: WebdriverIO.Element): HarnessEnvironment<WebdriverIO.Element> {
57
+ createEnvironment(element) {
77
58
  return new WebdriverIOHarnessEnvironment(element, {
78
59
  documentRoot: this.documentRoot
79
60
  });
@@ -0,0 +1,91 @@
1
+ import { ElementDimensions, EventData, ModifierKeys, TestElement, TestKey, TextOptions } from '@angular/cdk/testing';
2
+ /** Module augmentation to expose the host element as a public api */
3
+ declare module '@angular/cdk/testing' {
4
+ interface ComponentHarness {
5
+ element(): WebdriverIO.Element;
6
+ }
7
+ interface TestElement {
8
+ element(): WebdriverIO.Element;
9
+ }
10
+ }
11
+ /**
12
+ * A `TestElement` implementation for WebdriverIO.
13
+ */
14
+ export declare class WebdriverIOTestElement implements TestElement {
15
+ readonly hostElement: WebdriverIO.Element;
16
+ constructor(hostElement: WebdriverIO.Element);
17
+ /** Return the host element. */
18
+ element(): WebdriverIO.Element;
19
+ /** Blur the element. */
20
+ blur(): Promise<void>;
21
+ /** Clear the element's input (for input and textarea elements only). */
22
+ clear(): Promise<void>;
23
+ /**
24
+ * Click the element at the default location for the current environment. If you need to guarantee
25
+ * the element is clicked at a specific location, consider using `click('center')` or
26
+ * `click(x, y)` instead.
27
+ */
28
+ click(modifiers?: ModifierKeys): Promise<void>;
29
+ /** Click the element at the element's center. */
30
+ click(location: 'center', modifiers?: ModifierKeys): Promise<void>;
31
+ /**
32
+ * Click the element at the specified coordinates relative to the top-left of the element.
33
+ * @param relativeX Coordinate within the element, along the X-axis at which to click.
34
+ * @param relativeY Coordinate within the element, along the Y-axis at which to click.
35
+ * @param modifiers Modifier keys held while clicking
36
+ */
37
+ click(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
38
+ /**
39
+ * Right clicks on the element at the specified coordinates relative to the top-left of it.
40
+ * @param relativeX Coordinate within the element, along the X-axis at which to click.
41
+ * @param relativeY Coordinate within the element, along the Y-axis at which to click.
42
+ * @param modifiers Modifier keys held while clicking
43
+ */
44
+ rightClick(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
45
+ /** Focus the element. */
46
+ focus(): Promise<void>;
47
+ /** Get the computed value of the given CSS property for the element. */
48
+ getCssValue(property: string): Promise<string>;
49
+ /** Hovers the mouse over the element. */
50
+ hover(): Promise<void>;
51
+ /** Moves the mouse away from the element. */
52
+ mouseAway(): Promise<void>;
53
+ /**
54
+ * Sends the given string to the input as a series of key presses. Also fires input events
55
+ * and attempts to add the string to the Element's value.
56
+ */
57
+ sendKeys(...keys: (string | TestKey)[]): Promise<void>;
58
+ /**
59
+ * Sends the given string to the input as a series of key presses. Also fires input events
60
+ * and attempts to add the string to the Element's value.
61
+ */
62
+ sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
63
+ /** Gets the text from the element. */
64
+ text(options?: TextOptions): Promise<string>;
65
+ /** Gets the value for the given attribute from the element. */
66
+ getAttribute(name: string): Promise<string | null>;
67
+ /** Checks whether the element has the given class. */
68
+ hasClass(name: string): Promise<boolean>;
69
+ /** Gets the dimensions of the element. */
70
+ getDimensions(): Promise<ElementDimensions>;
71
+ /** Gets the value of a property of an element. */
72
+ getProperty(name: string): Promise<any>;
73
+ /** Checks whether this element matches the given selector. */
74
+ matchesSelector(selector: string): Promise<boolean>;
75
+ /** Checks whether the element is focused. */
76
+ isFocused(): Promise<boolean>;
77
+ /** Sets the value of a property of an input. */
78
+ setInputValue(value: string): Promise<void>;
79
+ /** Selects the options at the specified indexes inside of a native `select` element. */
80
+ selectOptions(...optionIndexes: number[]): Promise<void>;
81
+ /** Dispatches an event with a particular name. */
82
+ dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
83
+ /** Performs a key-down action. */
84
+ keyDown(value: string): Promise<void>;
85
+ /** Performs a key-up action. */
86
+ keyUp(value: string): Promise<void>;
87
+ /** Dispatches all the events that are part of a click event sequence. */
88
+ private dispatchClickEventSequence;
89
+ /** Writes info to the console outputs. */
90
+ private logAction;
91
+ }
@@ -1,30 +1,18 @@
1
- import {
2
- _getTextWithExcludedElements,
3
- ElementDimensions,
4
- EventData,
5
- ModifierKeys,
6
- TestElement,
7
- TestKey,
8
- TextOptions,
9
- ComponentHarness
10
- } from '@angular/cdk/testing';
1
+ import { TestKey, ComponentHarness } from '@angular/cdk/testing';
11
2
  import { browser } from '@wdio/globals';
12
3
  import logger from '@wdio/logger';
13
-
14
4
  import colors from '@colors/colors/safe.js';
15
5
  const { magenta, green } = colors;
16
-
17
- enum Button {
18
- LEFT = 'left',
19
- MIDDLE = 'middle',
20
- RIGHT = 'right'
21
- }
22
-
6
+ var Button;
7
+ (function (Button) {
8
+ Button["LEFT"] = "left";
9
+ Button["MIDDLE"] = "middle";
10
+ Button["RIGHT"] = "right";
11
+ })(Button || (Button = {}));
23
12
  /** Registers the element logger. */
24
13
  const log = logger('wdio-harness');
25
-
26
14
  /** Maps the `TestKey` constants to WebdriverIO's `Key` constants. */
27
- const keyMap: Record<number, string> = {
15
+ const keyMap = {
28
16
  [TestKey.BACKSPACE]: 'Backspace',
29
17
  [TestKey.TAB]: 'Tab',
30
18
  [TestKey.ENTER]: 'Enter',
@@ -57,23 +45,12 @@ const keyMap: Record<number, string> = {
57
45
  [TestKey.META]: 'Meta',
58
46
  [TestKey.COMMA]: ','
59
47
  };
60
-
61
- /** Module augmentation to expose the host element as a public api */
62
- declare module '@angular/cdk/testing' {
63
- interface ComponentHarness {
64
- element(): WebdriverIO.Element;
65
- }
66
- interface TestElement {
67
- element(): WebdriverIO.Element;
68
- }
69
- }
70
- ComponentHarness.prototype.element = function (this: ComponentHarness): WebdriverIO.Element {
48
+ ComponentHarness.prototype.element = function () {
71
49
  return this.locatorFactory.rootElement.element();
72
50
  };
73
-
74
51
  /** Converts a `ModifierKeys` object to a list of WebdriverIO `Key`s. */
75
- const toWebdriverIOModifierKeys = (modifiers: ModifierKeys): string[] => {
76
- const result: string[] = [];
52
+ const toWebdriverIOModifierKeys = (modifiers) => {
53
+ const result = [];
77
54
  if (modifiers.control) {
78
55
  result.push(keyMap[TestKey.CONTROL]);
79
56
  }
@@ -88,130 +65,87 @@ const toWebdriverIOModifierKeys = (modifiers: ModifierKeys): string[] => {
88
65
  }
89
66
  return result;
90
67
  };
91
-
92
68
  /**
93
69
  * A `TestElement` implementation for WebdriverIO.
94
70
  */
95
- export class WebdriverIOTestElement implements TestElement {
96
- constructor(readonly hostElement: WebdriverIO.Element) { }
97
-
71
+ export class WebdriverIOTestElement {
72
+ hostElement;
73
+ constructor(hostElement) {
74
+ this.hostElement = hostElement;
75
+ }
98
76
  /** Return the host element. */
99
- element(): WebdriverIO.Element {
77
+ element() {
100
78
  return this.hostElement;
101
79
  }
102
-
103
80
  /** Blur the element. */
104
- async blur(): Promise<void> {
81
+ async blur() {
105
82
  this.logAction('BLUR');
106
83
  return browser.executeScript('arguments[0].blur()', [this.hostElement]);
107
84
  }
108
-
109
85
  /** Clear the element's input (for input and textarea elements only). */
110
- async clear(): Promise<void> {
86
+ async clear() {
111
87
  this.logAction('CLEAR');
112
88
  return this.hostElement.clearValue();
113
89
  }
114
-
115
- /**
116
- * Click the element at the default location for the current environment. If you need to guarantee
117
- * the element is clicked at a specific location, consider using `click('center')` or
118
- * `click(x, y)` instead.
119
- */
120
- async click(modifiers?: ModifierKeys): Promise<void>;
121
- /** Click the element at the element's center. */
122
- async click(location: 'center', modifiers?: ModifierKeys): Promise<void>;
123
- /**
124
- * Click the element at the specified coordinates relative to the top-left of the element.
125
- * @param relativeX Coordinate within the element, along the X-axis at which to click.
126
- * @param relativeY Coordinate within the element, along the Y-axis at which to click.
127
- * @param modifiers Modifier keys held while clicking
128
- */
129
- async click(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
130
- async click(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?]): Promise<void> {
90
+ async click(...args) {
131
91
  this.logAction('CLICK');
132
92
  return this.dispatchClickEventSequence(args, Button.LEFT);
133
93
  }
134
-
135
- /**
136
- * Right clicks on the element at the specified coordinates relative to the top-left of it.
137
- * @param relativeX Coordinate within the element, along the X-axis at which to click.
138
- * @param relativeY Coordinate within the element, along the Y-axis at which to click.
139
- * @param modifiers Modifier keys held while clicking
140
- */
141
- async rightClick(relativeX: number, relativeY: number, modifiers?: ModifierKeys): Promise<void>;
142
- async rightClick(...args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?]): Promise<void> {
94
+ async rightClick(...args) {
143
95
  this.logAction('RIGHT_CLICK');
144
96
  return this.dispatchClickEventSequence(args, Button.RIGHT);
145
97
  }
146
-
147
98
  /** Focus the element. */
148
- async focus(): Promise<void> {
99
+ async focus() {
149
100
  this.logAction('FOCUS');
150
101
  return browser.executeScript('arguments[0].focus()', [this.hostElement]);
151
102
  }
152
-
153
103
  /** Get the computed value of the given CSS property for the element. */
154
- async getCssValue(property: string): Promise<string> {
104
+ async getCssValue(property) {
155
105
  this.logAction('GET_CSS_VALUE');
156
106
  return (await this.hostElement.getCSSProperty(property)).value ?? '';
157
107
  }
158
-
159
108
  /** Hovers the mouse over the element. */
160
- async hover(): Promise<void> {
109
+ async hover() {
161
110
  this.logAction('HOVER');
162
111
  return this.hostElement.moveTo();
163
112
  }
164
-
165
113
  /** Moves the mouse away from the element. */
166
- async mouseAway(): Promise<void> {
114
+ async mouseAway() {
167
115
  this.logAction('MOUSE_AWAY');
168
116
  return this.hostElement.moveTo({ xOffset: -1, yOffset: -1 });
169
117
  }
170
-
171
- /**
172
- * Sends the given string to the input as a series of key presses. Also fires input events
173
- * and attempts to add the string to the Element's value.
174
- */
175
- async sendKeys(...keys: (string | TestKey)[]): Promise<void>;
176
- /**
177
- * Sends the given string to the input as a series of key presses. Also fires input events
178
- * and attempts to add the string to the Element's value.
179
- */
180
- async sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void>;
181
- async sendKeys(...modifiersAndKeys: any[]): Promise<void> {
182
- let modifiers: ModifierKeys;
183
- let rest: (string | TestKey)[];
184
-
118
+ async sendKeys(...modifiersAndKeys) {
119
+ let modifiers;
120
+ let rest;
185
121
  const first = modifiersAndKeys[0];
186
122
  if (first !== undefined && typeof first !== 'string' && typeof first !== 'number') {
187
123
  modifiers = first;
188
124
  rest = modifiersAndKeys.slice(1);
189
- } else {
125
+ }
126
+ else {
190
127
  modifiers = {};
191
128
  rest = modifiersAndKeys;
192
129
  }
193
-
194
130
  const KeyNULL = String.fromCharCode(57344);
195
131
  const modifierKeys = toWebdriverIOModifierKeys(modifiers);
196
132
  const keys = rest
197
133
  .map(k => (typeof k === 'string' ? k.split('') : [keyMap[k]]))
198
134
  .reduce((arr, k) => arr.concat(k), [])
199
135
  .reduce((arr, k) => {
200
- if (modifierKeys.length > 0) {
201
- return arr.concat(...modifierKeys, k, KeyNULL);
202
- }
203
- return arr.concat(k);
204
- }, [] as string[]);
205
-
136
+ if (modifierKeys.length > 0) {
137
+ return arr.concat(...modifierKeys, k, KeyNULL);
138
+ }
139
+ return arr.concat(k);
140
+ }, []);
206
141
  this.logAction('SEND_KEYS', `[${keys.join(', ')}]`);
207
142
  if (keys.length !== 0) {
208
143
  await this.focus();
209
144
  return browser.keys(keys);
210
145
  }
211
146
  }
212
-
213
147
  /** Gets the text from the element. */
214
- async text(options?: TextOptions): Promise<string> {
148
+ async text(options) {
215
149
  this.logAction('TEXT', `{ exclude: ${options?.exclude} }`);
216
150
  if (options?.exclude) {
217
151
  return browser.executeScript(`
@@ -226,66 +160,55 @@ export class WebdriverIOTestElement implements TestElement {
226
160
  // We don't go through WebdriverIO's `getText`, because it excludes text from hidden elements.
227
161
  return browser.executeScript(`return (arguments[0].textContent ?? '').trim()`, [this.hostElement]);
228
162
  }
229
-
230
163
  /** Gets the value for the given attribute from the element. */
231
- async getAttribute(name: string): Promise<string | null> {
164
+ async getAttribute(name) {
232
165
  this.logAction('GET_ATTRIBUTE', name);
233
166
  return this.hostElement.getAttribute(name);
234
167
  }
235
-
236
168
  /** Checks whether the element has the given class. */
237
- async hasClass(name: string): Promise<boolean> {
169
+ async hasClass(name) {
238
170
  this.logAction('HAS_CLASS', name);
239
171
  const classes = (await this.getAttribute('class')) || '';
240
172
  return new Set(classes.split(/\s+/).filter(c => c)).has(name);
241
173
  }
242
-
243
174
  /** Gets the dimensions of the element. */
244
- async getDimensions(): Promise<ElementDimensions> {
175
+ async getDimensions() {
245
176
  this.logAction('GET_DIMENSIONS');
246
177
  const { width, height } = await this.hostElement.getSize();
247
178
  const { x: left, y: top } = await this.hostElement.getLocation();
248
179
  return { width, height, left, top };
249
180
  }
250
-
251
181
  /** Gets the value of a property of an element. */
252
- async getProperty(name: string): Promise<any> {
182
+ async getProperty(name) {
253
183
  this.logAction('GET_PROPERTY', name);
254
184
  return this.hostElement.getProperty(name);
255
185
  }
256
-
257
186
  /** Checks whether this element matches the given selector. */
258
- async matchesSelector(selector: string): Promise<boolean> {
187
+ async matchesSelector(selector) {
259
188
  this.logAction('MATCHES_SELECTOR', selector);
260
189
  return browser.executeScript(`
261
190
  return (Element.prototype.matches ?? Element.prototype.msMatchesSelector).call(arguments[0], arguments[1])
262
191
  `, [this.hostElement, selector]);
263
192
  }
264
-
265
193
  /** Checks whether the element is focused. */
266
- async isFocused(): Promise<boolean> {
194
+ async isFocused() {
267
195
  this.logAction('IS_FOCUSED');
268
196
  return this.hostElement.isFocused();
269
197
  }
270
-
271
198
  /** Sets the value of a property of an input. */
272
- async setInputValue(value: string): Promise<void> {
199
+ async setInputValue(value) {
273
200
  this.logAction('SET_INPUT_VALUE', value);
274
201
  return this.hostElement.setValue(value);
275
202
  }
276
-
277
203
  /** Selects the options at the specified indexes inside of a native `select` element. */
278
- async selectOptions(...optionIndexes: number[]): Promise<void> {
204
+ async selectOptions(...optionIndexes) {
279
205
  this.logAction('SELECT_OPTIONS', `[${optionIndexes.join(', ')}]`);
280
-
281
206
  const options = await this.hostElement.$$('option');
282
207
  const indexes = new Set(optionIndexes); // Convert to a set to remove duplicates.
283
-
284
208
  if (options.length && indexes.size) {
285
209
  // Reset the value so all the selected states are cleared. We can
286
210
  // reuse the input-specific method since the logic is the same.
287
211
  await this.setInputValue('');
288
-
289
212
  for (let i = 0; i < options.length; i++) {
290
213
  if (indexes.has(i)) {
291
214
  // We have to hold the control key while clicking on options so that multiple can be
@@ -297,9 +220,8 @@ export class WebdriverIOTestElement implements TestElement {
297
220
  }
298
221
  }
299
222
  }
300
-
301
223
  /** Dispatches an event with a particular name. */
302
- async dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void> {
224
+ async dispatchEvent(name, data) {
303
225
  this.logAction('DISPATCH_EVENT', name);
304
226
  return browser.executeScript(`
305
227
  const event = document.createEvent('Event');
@@ -310,44 +232,36 @@ export class WebdriverIOTestElement implements TestElement {
310
232
  arguments[1]['dispatchEvent'](event);
311
233
  `, [name, this.hostElement, data]);
312
234
  }
313
-
314
235
  /** Performs a key-down action. */
315
- async keyDown(value: string): Promise<void> {
236
+ async keyDown(value) {
316
237
  this.logAction('KEY_DOWN:', value);
317
238
  return browser.performActions([{
318
- id: 'keyboard',
319
- type: 'key',
320
- actions: [{ type: 'keyDown', value }],
321
- }]);
239
+ id: 'keyboard',
240
+ type: 'key',
241
+ actions: [{ type: 'keyDown', value }],
242
+ }]);
322
243
  }
323
-
324
244
  /** Performs a key-up action. */
325
- async keyUp(value: string): Promise<void> {
245
+ async keyUp(value) {
326
246
  this.logAction('KEY_UP:', value);
327
247
  return browser.performActions([{
328
- id: 'keyboard',
329
- type: 'key',
330
- actions: [{ type: 'keyUp', value }],
331
- }]);
248
+ id: 'keyboard',
249
+ type: 'key',
250
+ actions: [{ type: 'keyUp', value }],
251
+ }]);
332
252
  }
333
-
334
253
  // --- HELPER(s) ---
335
-
336
254
  /** Dispatches all the events that are part of a click event sequence. */
337
- private async dispatchClickEventSequence(
338
- args: [ModifierKeys?] | ['center', ModifierKeys?] | [number, number, ModifierKeys?], button: Button,
339
- ) {
340
- let modifiers: ModifierKeys = {};
255
+ async dispatchClickEventSequence(args, button) {
256
+ let modifiers = {};
341
257
  if (args.length && typeof args[args.length - 1] === 'object') {
342
- modifiers = args.pop() as ModifierKeys;
258
+ modifiers = args.pop();
343
259
  }
344
260
  const modifierKeys = toWebdriverIOModifierKeys(modifiers);
345
-
346
261
  // Omitting the offset argument to mouseMove results in clicking the center.
347
262
  // This is the default behavior we want, so we use an empty array of offsetArgs if
348
263
  // no args remain after popping the modifiers from the args passed to this function.
349
264
  const offsetArgs = (args.length === 2 ? { xOffset: Number(args[0]), yOffset: Number(args[1]) } : undefined);
350
-
351
265
  await this.hostElement.moveTo(offsetArgs);
352
266
  for (const modifierKey of modifierKeys) {
353
267
  this.keyDown(modifierKey);
@@ -357,9 +271,8 @@ export class WebdriverIOTestElement implements TestElement {
357
271
  this.keyUp(modifierKey);
358
272
  }
359
273
  }
360
-
361
274
  /** Writes info to the console outputs. */
362
- private logAction(action: string, args?: string): void {
275
+ logAction(action, args) {
363
276
  log.info(`${magenta(action)} ${green(this.hostElement.selector.toString())} ${args ? args : ''}`);
364
277
  }
365
278
  }
package/index.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { ComponentHarness, HarnessQuery } from '@angular/cdk/testing';
2
+ import { WebdriverIOHarnessEnvironment } from './WebdriverIOHarnessEnvironment.js';
3
+ /**
4
+ * Searches for all instances of the component corresponding to the given harness type under the
5
+ * `HarnessLoader`'s root element, and returns a list of `ComponentHarness` for each instance.
6
+ * @param query A query for a harness to create
7
+ * @return A list of instances of the given harness type
8
+ */
9
+ export declare const getAllHarnesses: <T extends ComponentHarness>(query: HarnessQuery<T>) => Promise<T[]>;
10
+ /**
11
+ * Searches for an instance of the component corresponding to the given harness type or host element
12
+ * under the `HarnessLoader`'s root element, and returns a `ComponentHarness` for that instance. If
13
+ * multiple matching components are found, a harness for the first one is returned. If no matching
14
+ * component is found, an error is thrown.
15
+ * @param query A query for a harness to create
16
+ * @param element A raw host element for a harness to create
17
+ * @return An instance of the given harness type
18
+ * @throws If a matching component instance can't be found
19
+ */
20
+ export declare const getHarness: <T extends ComponentHarness>(query: HarnessQuery<T>, element?: WebdriverIO.Element) => Promise<T>;
21
+ /**
22
+ * Returns a base harness environment instance.
23
+ * @return An HarnessLoader instance for the current HTML document, rooted at the document's root element
24
+ */
25
+ export declare const createHarnessEnvironment: (rootElement?: WebdriverIO.Element) => Promise<WebdriverIOHarnessEnvironment>;
26
+ /**
27
+ * Returns a promise that resolves when the application is "stable".
28
+ * The promise might be rejected due to timeouts, or async tasks in the client
29
+ * app that never finish (timers, etc).
30
+ */
31
+ export declare const waitForAngular: () => Promise<void>;
@@ -1,20 +1,14 @@
1
- import { ComponentHarness, ComponentHarnessConstructor, HarnessQuery } from '@angular/cdk/testing';
2
1
  import { browser, $ } from '@wdio/globals';
3
-
4
2
  import { WebdriverIOHarnessEnvironment } from './WebdriverIOHarnessEnvironment.js';
5
-
6
3
  /**
7
4
  * Searches for all instances of the component corresponding to the given harness type under the
8
5
  * `HarnessLoader`'s root element, and returns a list of `ComponentHarness` for each instance.
9
6
  * @param query A query for a harness to create
10
7
  * @return A list of instances of the given harness type
11
8
  */
12
- export const getAllHarnesses = async <T extends ComponentHarness>(
13
- query: HarnessQuery<T>
14
- ): Promise<T[]> => {
9
+ export const getAllHarnesses = async (query) => {
15
10
  return (await createHarnessEnvironment()).getAllHarnesses(query);
16
11
  };
17
-
18
12
  /**
19
13
  * Searches for an instance of the component corresponding to the given harness type or host element
20
14
  * under the `HarnessLoader`'s root element, and returns a `ComponentHarness` for that instance. If
@@ -25,33 +19,26 @@ export const getAllHarnesses = async <T extends ComponentHarness>(
25
19
  * @return An instance of the given harness type
26
20
  * @throws If a matching component instance can't be found
27
21
  */
28
- export const getHarness = async <T extends ComponentHarness>(
29
- query: HarnessQuery<T>,
30
- element?: WebdriverIO.Element
31
- ): Promise<T> => {
22
+ export const getHarness = async (query, element) => {
32
23
  const env = await createHarnessEnvironment(element);
33
24
  if (element) {
34
- return env.createComponentHarness(query as ComponentHarnessConstructor<T>, element);
25
+ return env.createComponentHarness(query, element);
35
26
  }
36
27
  return env.getHarness(query);
37
28
  };
38
-
39
29
  /**
40
30
  * Returns a base harness environment instance.
41
31
  * @return An HarnessLoader instance for the current HTML document, rooted at the document's root element
42
32
  */
43
- export const createHarnessEnvironment = async (
44
- rootElement?: WebdriverIO.Element
45
- ): Promise<WebdriverIOHarnessEnvironment> => {
33
+ export const createHarnessEnvironment = async (rootElement) => {
46
34
  return WebdriverIOHarnessEnvironment.loader(rootElement || await $('body'));
47
35
  };
48
-
49
36
  /**
50
37
  * Returns a promise that resolves when the application is "stable".
51
38
  * The promise might be rejected due to timeouts, or async tasks in the client
52
39
  * app that never finish (timers, etc).
53
40
  */
54
- export const waitForAngular = async (): Promise<void> => {
41
+ export const waitForAngular = async () => {
55
42
  await browser.waitUntil(() => browser.executeScript(`
56
43
  return Array.isArray(window.frameworkStabilizers)
57
44
  `, []));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@badisi/wdio-harness",
3
- "version": "3.1.5",
3
+ "version": "3.1.6",
4
4
  "description": "WebdriverIO support for Angular component test harnesses.",
5
5
  "homepage": "https://github.com/badisi/wdio-harness",
6
6
  "license": "MIT",
@@ -35,10 +35,6 @@
35
35
  "webdriver",
36
36
  "webdriverio"
37
37
  ],
38
- "scripts": {
39
- "build": "node ./build.mjs",
40
- "release": "npm publish ../../dist"
41
- },
42
38
  "engines": {
43
39
  "node": "^16.14.0 || >=18.10.0"
44
40
  },
@@ -49,14 +45,5 @@
49
45
  },
50
46
  "dependencies": {
51
47
  "@colors/colors": "^1.6.0"
52
- },
53
- "devDependencies": {
54
- "@angular/cdk": "^18.1.4",
55
- "@wdio/logger": "^8.38.0",
56
- "webdriverio": "^8.40.2",
57
- "cpy": "^11.1.0"
58
- },
59
- "publishConfig": {
60
- "access": "public"
61
48
  }
62
- }
49
+ }
package/build.mjs DELETED
@@ -1,77 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/naming-convention, no-underscore-dangle */
2
-
3
- import colors from '@colors/colors/safe.js';
4
- import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
5
- import { dirname, resolve as pathResolve } from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { exec } from 'child_process';
8
- import cpy from 'cpy';
9
-
10
- const { green, magenta } = colors;
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
-
13
- const DIST_PATH = pathResolve(__dirname, '../../dist');
14
-
15
- const log = str => console.log(magenta(str));
16
-
17
- const execCmd = (cmd, opts) => new Promise((resolve, reject) => {
18
- exec(cmd, opts, (err, stdout, stderr) => {
19
- if (err) {
20
- console.error(stdout, stderr);
21
- return reject(err);
22
- }
23
- return resolve(stdout);
24
- });
25
- });
26
-
27
- const cleanDir = path => new Promise(resolve => {
28
- const exists = existsSync(path);
29
- if (exists) {
30
- rmSync(path, { recursive: true, cwd: __dirname });
31
- }
32
- // Gives time to rmSync to unlock the file on Windows
33
- setTimeout(() => {
34
- mkdirSync(path, { recursive: true, cwd: __dirname });
35
- resolve();
36
- }, exists ? 1000 : 0);
37
- });
38
-
39
- const copyAssets = async () => {
40
- await cpy('../../README.md', DIST_PATH, { flat: true });
41
- await cpy('../../LICENSE', DIST_PATH, { flat: true });
42
- await cpy('package.json', DIST_PATH, { flat: true });
43
- };
44
-
45
- const customizePackageJson = () => {
46
- const pkgJsonPath = pathResolve(DIST_PATH, 'package.json');
47
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, { encoding: 'utf8' }));
48
- delete pkgJson.scripts;
49
- delete pkgJson.devDependencies;
50
- delete pkgJson.publishConfig;
51
- writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4), { encoding: 'utf8' });
52
- };
53
-
54
- const build = async () => {
55
- log('> Cleaning..');
56
- await cleanDir(DIST_PATH);
57
-
58
- log('> Building library..');
59
- await execCmd('tsc --project tsconfig.json', { cwd: __dirname });
60
-
61
- log('> Copying assets..');
62
- await copyAssets();
63
-
64
- log('> Customizing package.json..');
65
- customizePackageJson();
66
-
67
- log(`> ${green('Done!')}\n`);
68
- };
69
-
70
- void (async () => {
71
- try {
72
- await build();
73
- } catch (err) {
74
- console.error(err);
75
- process.exit(1);
76
- }
77
- })();
package/tsconfig.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "baseUrl": "src",
4
- "rootDir": "src",
5
- "outDir": "../../dist/",
6
- "module": "ESNext",
7
- "target": "es2022",
8
- "moduleResolution": "node",
9
- "lib": [
10
- "es2020",
11
- "dom"
12
- ],
13
- "allowJs": true,
14
- "allowSyntheticDefaultImports": true,
15
- "esModuleInterop": true,
16
- "declaration": true,
17
- "inlineSourceMap": false,
18
- "listEmittedFiles": false,
19
- "listFiles": false,
20
- "noFallthroughCasesInSwitch": true,
21
- "pretty": true,
22
- "skipLibCheck": true,
23
- "strict": true,
24
- "traceResolution": false,
25
- "importHelpers": true,
26
- "strictPropertyInitialization": true,
27
- "noUnusedLocals": true,
28
- "types": [
29
- "node"
30
- ]
31
- },
32
- "compileOnSave": false,
33
- "include": [
34
- "src/**/*.ts"
35
- ]
36
- }