Skip to content

Commit 7177f3f

Browse files
authored
test(button-harness): add performance tests for buttons using the pro… (#20222)
* test(button-harness): add performance tests for buttons using the protractor harness env * The main thing we are looking at here is not the button's performance. Instead we are looking at the performance overhead of using the protractor and testbed harness env. * We are running tests with & without the ProtractorHarnessEnvironment and with & without the TestbedHarnessEnvironment. * We are benchmarking 5 operations with a varying number of buttons: 1. Get the first button 2. Click the first button 3. Click the middle button 4. Click the last button 5. Click every button * fixup! test(button-harness): add performance tests for buttons using the protractor harness env * fixup! test(button-harness): add performance tests for buttons using the protractor harness env * fix(harness-benchmarks): lots of small changes * fixup! fix(harness-benchmarks): lots of small changes * refactor(harness-benchmarks): use hrtime instead of console.time * move test to test/benchmarks/cdk/testing * fix comment
1 parent f951bf4 commit 7177f3f

File tree

7 files changed

+424
-0
lines changed

7 files changed

+424
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
load("@npm_angular_dev_infra_private//benchmark/component_benchmark:component_benchmark.bzl", "component_benchmark")
2+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
3+
4+
package(default_visibility = ["//visibility:public"])
5+
6+
# These are two separate files that provide the same tidy interface for running a performance
7+
# benchmark.
8+
#
9+
# The testbed and protractor tests use different methods for measuring time. Since Karma does not
10+
# patch console.time, the results are not pushed back from the page to the Karma server. Instead
11+
# we use performance.now() to measure performance.
12+
13+
ts_library(
14+
name = "constants",
15+
srcs = [":constants.ts"],
16+
)
17+
18+
ts_library(
19+
name = "protractor-benchmark-utilities",
20+
srcs = [":protractor-benchmark-utilities.ts"],
21+
deps = [
22+
":constants",
23+
"@npm//@angular/dev-infra-private",
24+
"@npm//@types/node",
25+
],
26+
)
27+
28+
ts_library(
29+
name = "testbed-benchmark-utilities",
30+
srcs = [":testbed-benchmark-utilities.ts"],
31+
deps = [":constants"],
32+
)
33+
34+
# ProtractorHarnessEnvironment
35+
36+
component_benchmark(
37+
name = "e2e_benchmark",
38+
driver = ":protractor.perf-spec.ts",
39+
driver_deps = [
40+
":constants",
41+
":protractor-benchmark-utilities",
42+
"@npm//protractor",
43+
"@npm//@types/jasmine",
44+
"@npm//@types/node",
45+
"//src/cdk/testing",
46+
"//src/material/button/testing",
47+
"//src/cdk/testing/protractor",
48+
],
49+
ng_deps = [
50+
":constants",
51+
"@npm//@angular/core",
52+
"@npm//@angular/platform-browser",
53+
"//src/material/button",
54+
],
55+
ng_srcs = [":app.module.ts"],
56+
prefix = "",
57+
styles = ["//src/material/prebuilt-themes:indigo-pink"],
58+
)
59+
60+
# TestbedHarnessEnvironment
61+
62+
ng_test_library(
63+
name = "unit_tests_lib",
64+
srcs = ["testbed.perf-spec.ts"],
65+
deps = [
66+
":constants",
67+
":testbed-benchmark-utilities",
68+
"//src/cdk/testing",
69+
"//src/cdk/testing/testbed",
70+
"//src/material/button",
71+
"//src/material/button/testing",
72+
"@npm//@angular/dev-infra-private",
73+
],
74+
)
75+
76+
ng_web_test_suite(
77+
name = "unit_benchmark_tests",
78+
deps = [":unit_tests_lib"],
79+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
10+
import {BrowserModule} from '@angular/platform-browser';
11+
import {MatButtonModule} from '@angular/material/button';
12+
import {NUM_BUTTONS} from './constants';
13+
14+
/** component: component-harness-test */
15+
16+
@Component({
17+
selector: 'app-root',
18+
template: `
19+
<button *ngFor="let val of vals" mat-button> {{ val }} </button>
20+
`,
21+
encapsulation: ViewEncapsulation.None,
22+
styleUrls: ['../../../../../src/material/core/theming/prebuilt/indigo-pink.css'],
23+
})
24+
export class ButtonHarnessTest {
25+
vals = Array.from({ length: NUM_BUTTONS }, (_, i) => i);
26+
}
27+
28+
@NgModule({
29+
declarations: [ButtonHarnessTest],
30+
imports: [
31+
BrowserModule,
32+
MatButtonModule,
33+
],
34+
bootstrap: [ButtonHarnessTest],
35+
})
36+
export class AppModule {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
10+
/**
11+
* Benchpress gives us fine-grained metrics on browser interactions. In some cases, we want to
12+
* just get a whole measurement of how long the entire interaction took.
13+
*/
14+
export const USE_BENCHPRESS = false;
15+
16+
export const NUM_BUTTONS = 25;
17+
export const MIDDLE_BUTTON = `${Math.floor(NUM_BUTTONS / 2)}`;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {runBenchmark} from '@angular/dev-infra-private/benchmark/driver-utilities';
10+
import {USE_BENCHPRESS} from './constants';
11+
12+
/**
13+
* Records the performance of the given function.
14+
*
15+
* @param id A unique identifier.
16+
* @param callback A function whose performance will be recorded.
17+
*/
18+
export async function benchmark(id: string, callback: () => Promise<unknown>) {
19+
if (USE_BENCHPRESS) {
20+
await benchmarkWithBenchpress(id, callback);
21+
} else {
22+
await benchmarkWithConsoleAPI(id, callback);
23+
}
24+
}
25+
26+
/**
27+
* A simple wrapper for runBenchmark which is a wrapper for benchpress.
28+
*/
29+
async function benchmarkWithBenchpress(id: string, callback: () => Promise<unknown>) {
30+
await runBenchmark({
31+
id,
32+
url: '',
33+
ignoreBrowserSynchronization: true,
34+
params: [],
35+
work: async () => await callback(),
36+
});
37+
}
38+
39+
/**
40+
* Measures the time it takes to invoke the given function and prints the duration to the console.
41+
*/
42+
async function benchmarkWithConsoleAPI(id: string, callback: () => Promise<unknown>, runs = 5) {
43+
const t0 = Number(process.hrtime.bigint()) / 1000000;
44+
for (let i = 0; i < runs; i++) {
45+
await callback();
46+
}
47+
const t1 = Number(process.hrtime.bigint()) / 1000000;
48+
console.warn(`${id}: ${((t1 - t0) / runs).toFixed(2)}ms (avg over ${runs} runs)`);
49+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {HarnessLoader} from '@angular/cdk/testing';
10+
import {MatButtonHarness} from '@angular/material/button/testing/button-harness';
11+
import {ProtractorHarnessEnvironment} from '@angular/cdk/testing/protractor';
12+
import {$$, element, by, browser} from 'protractor';
13+
import {benchmark} from './protractor-benchmark-utilities';
14+
import {NUM_BUTTONS} from './constants';
15+
16+
const FIRST_BUTTON = '0';
17+
const MIDDLE_BUTTON = `${Math.floor(NUM_BUTTONS / 2)}`;
18+
const LAST_BUTTON = `${NUM_BUTTONS - 1}`;
19+
20+
describe('baseline tests for interacting with the page through Protractor directly', () => {
21+
beforeEach(async () => {
22+
await browser.get('');
23+
});
24+
25+
it('(baseline) should retrieve all of the buttons', async () => {
26+
await benchmark('(baseline) get every button', async () => {
27+
await $$('.mat-button');
28+
});
29+
});
30+
31+
it('(baseline) should click the first button', async () => {
32+
await benchmark('(baseline) click first button', async () => {
33+
await element(by.buttonText(FIRST_BUTTON)).click();
34+
});
35+
});
36+
37+
it('(baseline) should click the middle button', async () => {
38+
await benchmark('(baseline) click middle button', async () => {
39+
await element(by.buttonText(MIDDLE_BUTTON)).click();
40+
});
41+
});
42+
43+
it('(baseline) should click the last button', async () => {
44+
await benchmark('(baseline) click last button', async () => {
45+
await element(by.buttonText(LAST_BUTTON)).click();
46+
});
47+
});
48+
49+
it('(baseline) should click all of the buttons', async () => {
50+
await benchmark('(baseline) click every button', async () => {
51+
const buttons = $$('.mat-button');
52+
await buttons.each(async (button) => await button!.click());
53+
});
54+
});
55+
});
56+
57+
describe('performance tests for the protractor button harness', () => {
58+
let loader: HarnessLoader;
59+
60+
beforeEach(async () => {
61+
await browser.get('');
62+
loader = ProtractorHarnessEnvironment.loader();
63+
});
64+
65+
it('should retrieve all of the buttons', async () => {
66+
await benchmark('get every button', async () => {
67+
await loader.getAllHarnesses(MatButtonHarness);
68+
});
69+
});
70+
71+
it('should click the first button', async () => {
72+
await benchmark('click first button', async () => {
73+
const button = await loader.getHarness(MatButtonHarness.with({text: FIRST_BUTTON}));
74+
await button.click();
75+
});
76+
});
77+
78+
it('should click the middle button', async () => {
79+
await benchmark('click middle button', async () => {
80+
const button = await loader.getHarness(MatButtonHarness.with({text: MIDDLE_BUTTON}));
81+
await button.click();
82+
});
83+
});
84+
85+
it('should click the last button', async () => {
86+
await benchmark('click last button', async () => {
87+
const button = await loader.getHarness(MatButtonHarness.with({text: LAST_BUTTON}));
88+
await button.click();
89+
});
90+
});
91+
92+
it('should click all of the buttons', async () => {
93+
await benchmark('click every button', async () => {
94+
const buttons = await loader.getAllHarnesses(MatButtonHarness);
95+
for (let i = 0; i < buttons.length; i++) {
96+
const button = buttons[i];
97+
await button.click();
98+
}
99+
});
100+
});
101+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* Measures the time it takes to invoke the given function and prints the duration to the console.
11+
* @param id A unique identifier for the callback being measured.
12+
* @param callback A function whose performance will be recorded.
13+
* @param runs The number of times to run the callback.
14+
*/
15+
export async function benchmark(id: string, callback: () => Promise<unknown>, runs = 5) {
16+
// Currently karma does not send the logs from console.time to the server so console.time will
17+
// not show. As an alternative, we use performance.now()
18+
const t0 = performance.now();
19+
for (let i = 0; i < runs; i++) {
20+
await callback();
21+
}
22+
const t1 = performance.now();
23+
console.warn(`${id}: ${((t1 - t0) / runs).toFixed(2)}ms (avg over ${runs} runs)`);
24+
}

0 commit comments

Comments
 (0)