Skip to content

Commit 5b725d6

Browse files
authored
perf(cdk/testing): Improve performance of clicking in unit tests. (#20441)
* perf(cdk/testing): Improve performance of clicking in unit tests. This PR changes the semantics of `TestElement.click()` when called with no arguments. Rather than guarantee clicking the center of the element, the location of the click will be up to the particular `HarnessEnvironment`. This allows the `TestbedHarnessEnvironment` to skip the expensive and often unnecessary calculation to determine the center of the element. To replace the old behavior of `TestElement.click()`, the PR adds a new signature, `TestElement.click('center')` that can be used to click the center of the element. Calling `TestElement.click(x, y)` to click a point relative to the top left of the element remains uncahnged. I tested the performance gain of these changes by running a unit test to click 1000 buttons. Prior to this change it took 3000.2ms, with this change it took 1842.1ms (38.6% improvement). * update API golds * address comments * allow users to specify location of button click * fix type of click args * test(material-experimental/mdc-tabs): fix tab harness
1 parent cf8e8ee commit 5b725d6

File tree

11 files changed

+59
-20
lines changed

11 files changed

+59
-20
lines changed

src/cdk/testing/protractor/protractor-element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ export class ProtractorElement implements TestElement {
8080
return this.element.clear();
8181
}
8282

83-
async click(...args: number[]): Promise<void> {
83+
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
8484
// Omitting the offset argument to mouseMove results in clicking the center.
8585
// This is the default behavior we want, so we use an empty array of offsetArgs if no args are
8686
// passed to this method.
87-
const offsetArgs = args.length ? [{x: args[0], y: args[1]}] : [];
87+
const offsetArgs = args.length === 2 ? [{x: args[0], y: args[1]}] : [];
8888

8989
await browser.actions()
9090
.mouseMove(await this.element.getWebElement(), ...offsetArgs)

src/cdk/testing/test-element.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,16 @@ export interface TestElement {
6767
/** Clear the element's input (for input and textarea elements only). */
6868
clear(): Promise<void>;
6969

70-
/** Click the element at the element's center. */
70+
/**
71+
* Click the element at the default location for the current environment. If you need to guarantee
72+
* the element is clicked at a specific location, consider using `click('center')` or
73+
* `click(x, y)` instead.
74+
*/
7175
click(): Promise<void>;
7276

77+
/** Click the element at the element's center. */
78+
click(location: 'center'): Promise<void>;
79+
7380
/**
7481
* Click the element at the specified coordinates relative to the top-left of the element.
7582
* @param relativeX Coordinate within the element, along the X-axis at which to click.

src/cdk/testing/testbed/unit-test-element.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,19 @@ export class UnitTestElement implements TestElement {
7777
await this._stabilize();
7878
}
7979

80-
async click(...args: number[]): Promise<void> {
81-
const {left, top, width, height} = await this.getDimensions();
82-
const relativeX = args.length ? args[0] : width / 2;
83-
const relativeY = args.length ? args[1] : height / 2;
84-
85-
// Round the computed click position as decimal pixels are not
86-
// supported by mouse events and could lead to unexpected results.
87-
const clientX = Math.round(left + relativeX);
88-
const clientY = Math.round(top + relativeY);
80+
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
81+
let clientX: number | undefined = undefined;
82+
let clientY: number | undefined = undefined;
83+
if (args.length) {
84+
const {left, top, width, height} = await this.getDimensions();
85+
const relativeX = args[0] === 'center' ? width / 2 : args[0];
86+
const relativeY = args[0] === 'center' ? height / 2 : args[1];
87+
88+
// Round the computed click position as decimal pixels are not
89+
// supported by mouse events and could lead to unexpected results.
90+
clientX = Math.round(left + relativeX);
91+
clientY = Math.round(top + relativeY);
92+
}
8993

9094
this._dispatchPointerEventIfSupported('pointerdown', clientX, clientY);
9195
dispatchMouseEvent(this.element, 'mousedown', clientX, clientY);

src/cdk/testing/tests/cross-environment.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,14 @@ export function crossEnvironmentSpecs(
322322
it('should be able to click at a specific position within an element', async () => {
323323
const clickTest = await harness.clickTest();
324324
const clickTestResult = await harness.clickTestResult();
325-
await clickTest.click(50, 50);
325+
await clickTest.click(10, 10);
326+
expect(await clickTestResult.text()).toBe('10-10');
327+
});
328+
329+
it('should be able to click the center of an element', async () => {
330+
const clickTest = await harness.clickTest();
331+
const clickTestResult = await harness.clickTestResult();
332+
await clickTest.click('center');
326333
expect(await clickTestResult.text()).toBe('50-50');
327334
});
328335

src/material-experimental/mdc-button/testing/button-harness.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,18 @@ export class MatButtonHarness extends ComponentHarness {
3737
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
3838
}
3939

40+
/**
41+
* Clicks the button at the given position relative to its top-left.
42+
* @param relativeX The relative x position of the click.
43+
* @param relativeY The relative y position of the click.
44+
*/
45+
click(relativeX: number, relativeY: number): Promise<void>;
46+
/** Clicks the button at its center. */
47+
click(location: 'center'): Promise<void>;
4048
/** Clicks the button. */
41-
async click(): Promise<void> {
42-
return (await this.host()).click();
49+
click(): Promise<void>;
50+
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
51+
return (await this.host()).click(...(args as []));
4352
}
4453

4554
/** Gets a boolean promise indicating if the button is disabled. */

src/material-experimental/mdc-tabs/testing/tab-harness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class MatTabHarness extends ContentContainerComponentHarness<string> {
6161

6262
/** Selects the given tab by clicking on the label. Tab cannot be selected if disabled. */
6363
async select(): Promise<void> {
64-
await (await this.host()).click();
64+
await (await this.host()).click('center');
6565
}
6666

6767
/** Gets the text content of the tab. */

src/material/button/testing/button-harness.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,18 @@ export class MatButtonHarness extends ComponentHarness {
3737
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
3838
}
3939

40+
/**
41+
* Clicks the button at the given position relative to its top-left.
42+
* @param relativeX The relative x position of the click.
43+
* @param relativeY The relative y position of the click.
44+
*/
45+
click(relativeX: number, relativeY: number): Promise<void>;
46+
/** Clicks the button at its center. */
47+
click(location: 'center'): Promise<void>;
4048
/** Clicks the button. */
41-
async click(): Promise<void> {
42-
return (await this.host()).click();
49+
click(): Promise<void>;
50+
async click(...args: [] | ['center'] | [number, number]): Promise<void> {
51+
return (await this.host()).click(...(args as []));
4352
}
4453

4554
/** Whether the button is disabled. */

tools/public_api_guard/cdk/testing.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export interface TestElement {
119119
blur(): Promise<void>;
120120
clear(): Promise<void>;
121121
click(): Promise<void>;
122+
click(location: 'center'): Promise<void>;
122123
click(relativeX: number, relativeY: number): Promise<void>;
123124
focus(): Promise<void>;
124125
getAttribute(name: string): Promise<string | null>;

tools/public_api_guard/cdk/testing/protractor.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export declare class ProtractorElement implements TestElement {
33
constructor(element: ElementFinder);
44
blur(): Promise<void>;
55
clear(): Promise<void>;
6-
click(...args: number[]): Promise<void>;
6+
click(...args: [] | ['center'] | [number, number]): Promise<void>;
77
focus(): Promise<void>;
88
getAttribute(name: string): Promise<string | null>;
99
getCssValue(property: string): Promise<string>;

tools/public_api_guard/cdk/testing/testbed.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export declare class UnitTestElement implements TestElement {
2121
constructor(element: Element, _stabilize: () => Promise<void>);
2222
blur(): Promise<void>;
2323
clear(): Promise<void>;
24-
click(...args: number[]): Promise<void>;
24+
click(...args: [] | ['center'] | [number, number]): Promise<void>;
2525
focus(): Promise<void>;
2626
getAttribute(name: string): Promise<string | null>;
2727
getCssValue(property: string): Promise<string>;

tools/public_api_guard/material/button/testing.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export interface ButtonHarnessFilters extends BaseHarnessFilters {
44

55
export declare class MatButtonHarness extends ComponentHarness {
66
blur(): Promise<void>;
7+
click(relativeX: number, relativeY: number): Promise<void>;
8+
click(location: 'center'): Promise<void>;
79
click(): Promise<void>;
810
focus(): Promise<void>;
911
getText(): Promise<string>;

0 commit comments

Comments
 (0)