Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add basic performance testing via Cypress #8586

Merged
merged 10 commits into from
May 17, 2022
Merged
13 changes: 13 additions & 0 deletions .github/workflows/element-build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ jobs:
cypress/videos
cypress/synapselogs

- name: Store benchmark result
uses: matrix-org/github-action-benchmark@jsperfentry-1
with:
name: Cypress measurements
tool: 'jsperformanceentry'
output-file-path: cypress/performance/measurements.json
# The dashboard is available at https://matrix-org.github.io/matrix-react-sdk/cypress/bench/
benchmark-data-dir-path: cypress/bench
fail-on-alert: false
comment-on-alert: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not enable either of these?

Copy link
Collaborator Author

@jryans jryans May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do, up to the team really. The current configuration is just copied from a previous usage of this benchmarking action. Perhaps it would be good to get a few data points in the dashboard and then define an appropriate threshold for alerting?

github-token: ${{ secrets.DEPLOY_GH_PAGES }}
auto-push: ${{ github.ref == 'refs/heads/develop' }}
jryans marked this conversation as resolved.
Show resolved Hide resolved
t3chguy marked this conversation as resolved.
Show resolved Hide resolved

app-tests:
name: Element Web Integration Tests
runs-on: ubuntu-latest
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/end-to-end-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,16 @@ jobs:
test/end-to-end-tests/synapse/installations/consent/homeserver.log
retention-days: 14

- name: Download previous benchmark data
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a dead block, as we aren't using the "separate file" version of this benchmark action.

uses: actions/cache@v1
with:
path: ./cache
key: ${{ runner.os }}-benchmark

- name: Store benchmark result
uses: matrix-org/github-action-benchmark@jsperfentry-1
with:
tool: 'jsperformanceentry'
output-file-path: test/end-to-end-tests/performance-entries.json
# This is the default dashboard path. It's included here anyway to
# make the difference from the Cypress variant in
# `element-build-and-test.yaml` more obvious.
# The dashboard is available at https://matrix-org.github.io/matrix-react-sdk/dev/bench/
benchmark-data-dir-path: dev/bench
fail-on-alert: false
comment-on-alert: false
# Only temporary to monitor where failures occur
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ package-lock.json
# These could have files in them but don't currently
# Cypress will still auto-create them though...
/cypress/fixtures
/cypress/performance
2 changes: 2 additions & 0 deletions cypress/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import "matrix-js-sdk/src/@types/global";
import type { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client";
import type { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
import type { MatrixDispatcher } from "../src/dispatcher/dispatcher";
import type PerformanceMonitor from "../src/performance";

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
Expand All @@ -27,6 +28,7 @@ declare global {
matrixClient?: MatrixClient;
};
mxDispatcher: MatrixDispatcher;
mxPerformanceMonitor: PerformanceMonitor;
beforeReload?: boolean; // for detecting reloads
// Partial type for the matrix-js-sdk module, exported by browser-matrix
matrixcs: {
Expand Down
4 changes: 4 additions & 0 deletions cypress/integration/1-register/register.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ describe("Registration", () => {
cy.get("#mx_RegistrationForm_username").type("alice");
cy.get("#mx_RegistrationForm_password").type("totally a great password");
cy.get("#mx_RegistrationForm_passwordConfirm").type("totally a great password");
cy.startMeasuring("registration:create-acccount");
cy.get(".mx_Login_submit").click();

cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click();
cy.stopMeasuring("registration:create-account");
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click();
cy.startMeasuring("registration:from-submit-to-home");
cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click();
cy.url().should('contain', '/#/home');
cy.stopMeasuring("registration:from-submit-to-home");
});
});
2 changes: 2 additions & 0 deletions cypress/integration/2-login/login.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ describe("Login", () => {

cy.get("#mx_LoginForm_username").type(username);
cy.get("#mx_LoginForm_password").type(password);
cy.startMeasuring("login:from-submit-to-home");
cy.get(".mx_Login_submit").click();

cy.url().should('contain', '/#/home');
cy.stopMeasuring("login:from-submit-to-home");
});
});
});
2 changes: 2 additions & 0 deletions cypress/integration/4-create-room/create-room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ describe("Create Room", () => {
// Fill room address
cy.get('[label="Room address"]').type("test-room-1");
// Submit
cy.startMeasuring("create-room:from-submit-to-room");
cy.get(".mx_Dialog_primary").click();
});

cy.url().should("contain", "/#/room/#test-room-1:localhost");
cy.stopMeasuring("create-room:from-submit-to-room");
cy.get(".mx_RoomHeader_nametext").contains(name);
cy.get(".mx_RoomHeader_topic").contains(topic);
});
Expand Down
4 changes: 3 additions & 1 deletion cypress/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ limitations under the License.

/// <reference types="cypress" />

import { synapseDocker } from "./synapsedocker";
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { performance } from "./performance";
import { synapseDocker } from "./synapsedocker";

/**
* @type {Cypress.PluginConfig}
*/
export default function(on: PluginEvents, config: PluginConfigOptions) {
performance(on, config);
synapseDocker(on, config);
}
47 changes: 47 additions & 0 deletions cypress/plugins/performance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/// <reference types="cypress" />

import * as path from "path";
import * as fse from "fs-extra";

import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;

// This holds all the performance measurements throughout the run
let bufferedMeasurements: PerformanceEntry[] = [];

function addMeasurements(measurements: PerformanceEntry[]): void {
bufferedMeasurements = bufferedMeasurements.concat(measurements);
return null;
}

async function writeMeasurementsFile() {
try {
const measurementsPath = path.join("cypress", "performance", "measurements.json");
await fse.outputJSON(measurementsPath, bufferedMeasurements, {
spaces: 4,
});
} finally {
bufferedMeasurements = [];
}
}

export function performance(on: PluginEvents, config: PluginConfigOptions) {
on("task", { addMeasurements });
on("after:run", writeMeasurementsFile);
}
2 changes: 1 addition & 1 deletion cypress/plugins/synapsedocker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ async function synapseStop(id: string): Promise<void> {
synapses.delete(id);

console.log(`Stopped synapse id ${id}.`);
// cypres deliberately fails if you return 'undefined', so
// cypress deliberately fails if you return 'undefined', so
// return null to signal all is well and we've handled the task.
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion cypress/support/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, displayName?: string):
const username = Cypress._.uniqueId("userId_");
const password = Cypress._.uniqueId("password_");
return cy.registerUser(synapse, username, password, displayName).then(credentials => {
return cy.window().then(win => {
return cy.window({ log: false }).then(win => {
const cli = new win.matrixcs.MatrixClient({
baseUrl: synapse.baseUrl,
userId: credentials.userId,
Expand Down
4 changes: 2 additions & 2 deletions cypress/support/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ declare global {
}

Cypress.Commands.add("getClient", (): Chainable<MatrixClient | undefined> => {
return cy.window().then(win => win.mxMatrixClientPeg.matrixClient);
return cy.window({ log: false }).then(win => win.mxMatrixClientPeg.matrixClient);
});

Cypress.Commands.add("createRoom", (options: ICreateRoomOpts): Chainable<string> => {
return cy.window().then(async win => {
return cy.window({ log: false }).then(async win => {
const cli = win.mxMatrixClientPeg.matrixClient;
const resp = await cli.createRoom(options);
const roomId = resp.room_id;
Expand Down
1 change: 1 addition & 0 deletions cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.

/// <reference types="cypress" />

import "./performance";
import "./synapse";
import "./login";
import "./client";
Expand Down
4 changes: 2 additions & 2 deletions cypress/support/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ declare global {

Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string): Chainable<UserCredentials> => {
// XXX: work around Cypress not clearing IDB between tests
cy.window().then(win => {
cy.window({ log: false }).then(win => {
win.indexedDB.databases().then(databases => {
databases.forEach(database => {
win.indexedDB.deleteDatabase(database.name);
Expand Down Expand Up @@ -73,7 +73,7 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
},
});
}).then(response => {
cy.window().then(win => {
cy.window({ log: false }).then(win => {
// Seed the localStorage with the required credentials
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
win.localStorage.setItem("mx_user_id", response.body.user_id);
Expand Down
71 changes: 71 additions & 0 deletions cypress/support/performance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/// <reference types="cypress" />

import Chainable = Cypress.Chainable;
import AUTWindow = Cypress.AUTWindow;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Start measuring the duration of some task.
* @param task The task name.
*/
startMeasuring(task: string): Chainable<AUTWindow>;
/**
* Stop measuring the duration of some task.
* The duration is reported in the Cypress log.
* @param task The task name.
*/
stopMeasuring(task: string): Chainable<AUTWindow>;
}
}
}

const PREFIX = "cy:";

function startMeasuring(task: string): Chainable<AUTWindow> {
return cy.window({ log: false }).then((win) => {
win.mxPerformanceMonitor.start(PREFIX + task);
});
}

function stopMeasuring(task: string): Chainable<AUTWindow> {
return cy.window({ log: false }).then((win) => {
const measure = win.mxPerformanceMonitor.stop(PREFIX + task);
cy.log(`**${task}** ${measure.duration} ms`);
});
}

Cypress.Commands.add("startMeasuring", startMeasuring);
Cypress.Commands.add("stopMeasuring", stopMeasuring);

Cypress.on("window:before:unload", (event: BeforeUnloadEvent) => {
const doc = event.target as Document;
if (doc.location.href === "about:blank") return;
const win = doc.defaultView as AUTWindow;
if (!win.mxPerformanceMonitor) return;
const entries = win.mxPerformanceMonitor.getEntries().filter(entry => {
return entry.name.startsWith(PREFIX);
});
cy.task("addMeasurements", entries);
});

// Needed to make this file a module
export { };
Comment on lines +73 to +74
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's the thing I was missing for the two files with dummy imports 🤦

2 changes: 1 addition & 1 deletion cypress/support/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function startSynapse(template: string): Chainable<SynapseInstance> {
function stopSynapse(synapse?: SynapseInstance): Chainable<AUTWindow> {
if (!synapse) return;
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
return cy.window().then((win) => {
return cy.window({ log: false }).then((win) => {
win.location.href = 'about:blank';
cy.task("synapseStop", synapse.synapseId);
});
Expand Down
2 changes: 1 addition & 1 deletion src/performance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default class PerformanceMonitor {
* with the start marker
* @param name Name of the recording
* @param id Specify an identifier appended to the measurement name
* @returns {void}
* @returns The measurement
*/
stop(name: string, id?: string): PerformanceEntry {
if (!this.supportsPerformanceApi()) {
Expand Down