Move threads e2e tests over to cypress (#8501)
* Add non-consent (default) Synapse template * Add consent test * Add create room test * Stash work * Initial threads tests * fix * Delete old threads e2e tests, plan new ones * Fix typed s'more * Try something else * specify d.ts * Fix types once and for all? * Fix the consent tests * Iterate threads test harness * Fix dispatcher types * Iterate threads test * fix typing * Alternative import attempt * let it break let it break let it break * Tweak types * Stash * delint and update docs * null-guard scrollIntoView * Iterate threads test * Apply suggestions from code review
This commit is contained in:
parent
14127c777b
commit
ad4d3f9a88
27 changed files with 810 additions and 288 deletions
63
cypress/support/bot.ts
Normal file
63
cypress/support/bot.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 request from "browser-request";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { SynapseInstance } from "../plugins/synapsedocker";
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Returns a new Bot instance
|
||||
* @param synapse the instance on which to register the bot user
|
||||
* @param displayName the display name to give to the bot user
|
||||
*/
|
||||
getBot(synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("getBot", (synapse: SynapseInstance, displayName?: string): Chainable<MatrixClient> => {
|
||||
const username = Cypress._.uniqueId("userId_");
|
||||
const password = Cypress._.uniqueId("password_");
|
||||
return cy.registerUser(synapse, username, password, displayName).then(credentials => {
|
||||
return cy.window().then(win => {
|
||||
const cli = new win.matrixcs.MatrixClient({
|
||||
baseUrl: synapse.baseUrl,
|
||||
userId: credentials.userId,
|
||||
deviceId: credentials.deviceId,
|
||||
accessToken: credentials.accessToken,
|
||||
request,
|
||||
});
|
||||
|
||||
cli.on(win.matrixcs.RoomMemberEvent.Membership, (event, member) => {
|
||||
if (member.membership === "invite" && member.userId === cli.getUserId()) {
|
||||
cli.joinRoom(member.roomId);
|
||||
}
|
||||
});
|
||||
|
||||
cli.startClient();
|
||||
|
||||
return cli;
|
||||
});
|
||||
});
|
||||
});
|
78
cypress/support/client.ts
Normal file
78
cypress/support/client.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
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 type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Returns the MatrixClient from the MatrixClientPeg
|
||||
*/
|
||||
getClient(): Chainable<MatrixClient | undefined>;
|
||||
/**
|
||||
* Create a room with given options.
|
||||
* @param options the options to apply when creating the room
|
||||
* @return the ID of the newly created room
|
||||
*/
|
||||
createRoom(options: ICreateRoomOpts): Chainable<string>;
|
||||
/**
|
||||
* Invites the given user to the given room.
|
||||
* @param roomId the id of the room to invite to
|
||||
* @param userId the id of the user to invite
|
||||
*/
|
||||
inviteUser(roomId: string, userId: string): Chainable<{}>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("getClient", (): Chainable<MatrixClient | undefined> => {
|
||||
return cy.window().then(win => win.mxMatrixClientPeg.matrixClient);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("createRoom", (options: ICreateRoomOpts): Chainable<string> => {
|
||||
return cy.window().then(async win => {
|
||||
const cli = win.mxMatrixClientPeg.matrixClient;
|
||||
const resp = await cli.createRoom(options);
|
||||
const roomId = resp.room_id;
|
||||
|
||||
if (!cli.getRoom(roomId)) {
|
||||
await new Promise<void>(resolve => {
|
||||
const onRoom = (room: Room) => {
|
||||
if (room.roomId === roomId) {
|
||||
cli.off(win.matrixcs.ClientEvent.Room, onRoom);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
cli.on(win.matrixcs.ClientEvent.Room, onRoom);
|
||||
});
|
||||
}
|
||||
|
||||
return roomId;
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("inviteUser", (roomId: string, userId: string): Chainable<{}> => {
|
||||
return cy.getClient().then(async (cli: MatrixClient) => {
|
||||
return cli.invite(roomId, userId);
|
||||
});
|
||||
});
|
|
@ -18,3 +18,6 @@ limitations under the License.
|
|||
|
||||
import "./synapse";
|
||||
import "./login";
|
||||
import "./client";
|
||||
import "./settings";
|
||||
import "./bot";
|
||||
|
|
|
@ -42,6 +42,15 @@ 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 => {
|
||||
win.indexedDB.databases().then(databases => {
|
||||
databases.forEach(database => {
|
||||
win.indexedDB.deleteDatabase(database.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const username = Cypress._.uniqueId("userId_");
|
||||
const password = Cypress._.uniqueId("password_");
|
||||
return cy.registerUser(synapse, username, password, displayName).then(() => {
|
||||
|
@ -64,7 +73,7 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
|
|||
},
|
||||
});
|
||||
}).then(response => {
|
||||
return cy.window().then(win => {
|
||||
cy.window().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);
|
||||
|
@ -73,14 +82,17 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
|
|||
win.localStorage.setItem("mx_is_guest", "false");
|
||||
win.localStorage.setItem("mx_has_pickle_key", "false");
|
||||
win.localStorage.setItem("mx_has_access_token", "true");
|
||||
|
||||
return cy.visit("/").then(() => ({
|
||||
password,
|
||||
accessToken: response.body.access_token,
|
||||
userId: response.body.user_id,
|
||||
deviceId: response.body.device_id,
|
||||
homeServer: response.body.home_server,
|
||||
}));
|
||||
});
|
||||
|
||||
return cy.visit("/").then(() => {
|
||||
// wait for the app to load
|
||||
return cy.get(".mx_MatrixChat", { timeout: 15000 });
|
||||
}).then(() => ({
|
||||
password,
|
||||
accessToken: response.body.access_token,
|
||||
userId: response.body.user_id,
|
||||
deviceId: response.body.device_id,
|
||||
homeServer: response.body.home_server,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
101
cypress/support/settings.ts
Normal file
101
cypress/support/settings.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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 "./client"; // XXX: without an (any) import here, types break down
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Open the top left user menu, returning a handle to the resulting context menu.
|
||||
*/
|
||||
openUserMenu(): Chainable<JQuery<HTMLElement>>;
|
||||
|
||||
/**
|
||||
* Open user settings (via user menu), returning a handle to the resulting dialog.
|
||||
* @param tab the name of the tab to switch to after opening, optional.
|
||||
*/
|
||||
openUserSettings(tab?: string): Chainable<JQuery<HTMLElement>>;
|
||||
|
||||
/**
|
||||
* Switch settings tab to the one by the given name, ideally call this in the context of the dialog.
|
||||
* @param tab the name of the tab to switch to.
|
||||
*/
|
||||
switchTabUserSettings(tab: string): Chainable<JQuery<HTMLElement>>;
|
||||
|
||||
/**
|
||||
* Close user settings, ideally call this in the context of the dialog.
|
||||
*/
|
||||
closeUserSettings(): Chainable<JQuery<HTMLElement>>;
|
||||
|
||||
/**
|
||||
* Join the given beta, the `Labs` tab must already be opened,
|
||||
* ideally call this in the context of the dialog.
|
||||
* @param name the name of the beta to join.
|
||||
*/
|
||||
joinBeta(name: string): Chainable<JQuery<HTMLElement>>;
|
||||
|
||||
/**
|
||||
* Leave the given beta, the `Labs` tab must already be opened,
|
||||
* ideally call this in the context of the dialog.
|
||||
* @param name the name of the beta to leave.
|
||||
*/
|
||||
leaveBeta(name: string): Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add("openUserMenu", (): Chainable<JQuery<HTMLElement>> => {
|
||||
cy.get('[aria-label="User menu"]').click();
|
||||
return cy.get(".mx_ContextualMenu");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("openUserSettings", (tab?: string): Chainable<JQuery<HTMLElement>> => {
|
||||
cy.openUserMenu().within(() => {
|
||||
cy.get('[aria-label="All settings"]').click();
|
||||
});
|
||||
return cy.get(".mx_UserSettingsDialog").within(() => {
|
||||
if (tab) {
|
||||
cy.switchTabUserSettings(tab);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("switchTabUserSettings", (tab: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_TabbedView_tabLabels").within(() => {
|
||||
cy.get(".mx_TabbedView_tabLabel").contains(tab).click();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("closeUserSettings", (): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get('[aria-label="Close dialog"]').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("joinBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_BetaCard_title").contains(name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("leaveBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_BetaCard_title").contains(name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
||||
});
|
||||
});
|
|
@ -60,7 +60,8 @@ function startSynapse(template: string): Chainable<SynapseInstance> {
|
|||
return cy.task<SynapseInstance>("synapseStart", template);
|
||||
}
|
||||
|
||||
function stopSynapse(synapse: SynapseInstance): Chainable<AUTWindow> {
|
||||
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) => {
|
||||
win.location.href = 'about:blank';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue