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:
Michael Telatynski
2022-05-10 17:09:31 +00:00
committed by GitHub
parent 14127c777b
commit ad4d3f9a88
27 changed files with 810 additions and 288 deletions
+45
View File
@@ -0,0 +1,45 @@
/*
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.
*/
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";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface ApplicationWindow {
mxMatrixClientPeg: {
matrixClient?: MatrixClient;
};
mxDispatcher: MatrixDispatcher;
beforeReload?: boolean; // for detecting reloads
// Partial type for the matrix-js-sdk module, exported by browser-matrix
matrixcs: {
MatrixClient: typeof MatrixClient;
ClientEvent: typeof ClientEvent;
RoomMemberEvent: typeof RoomMemberEvent;
};
}
}
interface Window {
mxDispatcher: MatrixDispatcher; // to appease the MatrixDispatcher import
}
}
export { MatrixClient };
@@ -0,0 +1,73 @@
/*
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 { SinonStub } from "cypress/types/sinon";
import { SynapseInstance } from "../../plugins/synapsedocker";
describe("Consent", () => {
let synapse: SynapseInstance;
beforeEach(() => {
cy.startSynapse("consent").then(data => {
synapse = data;
cy.initTestUser(synapse, "Bob");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
});
it("should prompt the user to consent to terms when server deems it necessary", () => {
// Attempt to create a room using the js-sdk which should return an error with `M_CONSENT_NOT_GIVEN`
cy.window().then(win => {
win.mxMatrixClientPeg.matrixClient.createRoom({}).catch(() => {});
// Stub `window.open` - clicking the primary button below will call it
cy.stub(win, "open").as("windowOpen").returns({});
});
// Accept terms & conditions
cy.get(".mx_QuestionDialog").within(() => {
cy.get("#mx_BaseDialog_title").contains("Terms and Conditions");
cy.get(".mx_Dialog_primary").click();
});
cy.get<SinonStub>("@windowOpen").then(stub => {
const url = stub.getCall(0).args[0];
// Go to Synapse's consent page and accept it
cy.origin(synapse.baseUrl, { args: { url } }, ({ url }) => {
cy.visit(url);
cy.get('[type="submit"]').click();
cy.get("p").contains("Danke schon");
});
});
// go back to the app
cy.visit("/");
// wait for the app to re-load
cy.get(".mx_MatrixChat", { timeout: 15000 });
// attempt to perform the same action again and expect it to not fail
cy.createRoom({});
});
});
@@ -19,12 +19,12 @@ limitations under the License.
import { SynapseInstance } from "../../plugins/synapsedocker";
import type { UserCredentials } from "../../support/login";
describe("UserMenu", () => {
describe("User Menu", () => {
let synapse: SynapseInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startSynapse("consent").then(data => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, "Jeff").then(credentials => {
@@ -38,8 +38,10 @@ describe("UserMenu", () => {
});
it("should contain our name & userId", () => {
cy.get('[aria-label="User menu"]', { timeout: 15000 }).click();
cy.get(".mx_UserMenu_contextMenu_displayName").should("contain", "Jeff");
cy.get(".mx_UserMenu_contextMenu_userId").should("contain", user.userId);
cy.get('[aria-label="User menu"]').click();
cy.get(".mx_ContextualMenu").within(() => {
cy.get(".mx_UserMenu_contextMenu_displayName").should("contain", "Jeff");
cy.get(".mx_UserMenu_contextMenu_userId").should("contain", user.userId);
});
});
});
@@ -0,0 +1,64 @@
/*
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 { SynapseInstance } from "../../plugins/synapsedocker";
import Chainable = Cypress.Chainable;
function openCreateRoomDialog(): Chainable<JQuery<HTMLElement>> {
cy.get('[aria-label="Add room"]').click();
cy.get('.mx_ContextualMenu [aria-label="New room"]').click();
return cy.get(".mx_CreateRoomDialog");
}
describe("Create Room", () => {
let synapse: SynapseInstance;
beforeEach(() => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, "Jim");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
});
it("should allow us to create a public room with name, topic & address set", () => {
const name = "Test room 1";
const topic = "This room is dedicated to this test and this test only!";
openCreateRoomDialog().within(() => {
// Fill name & topic
cy.get('[label="Name"]').type(name);
cy.get('[label="Topic (optional)"]').type(topic);
// Change room to public
cy.get('[aria-label="Room visibility"]').click();
cy.get("#mx_JoinRuleDropdown__public").click();
// Fill room address
cy.get('[label="Room address"]').type("test-room-1");
// Submit
cy.get(".mx_Dialog_primary").click();
});
cy.url().should("contain", "/#/room/#test-room-1:localhost");
cy.get(".mx_RoomHeader_nametext").contains(name);
cy.get(".mx_RoomHeader_topic").contains(topic);
});
});
@@ -0,0 +1,214 @@
/*
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 { SynapseInstance } from "../../plugins/synapsedocker";
import { MatrixClient } from "../../global";
function markWindowBeforeReload(): void {
// mark our window object to "know" when it gets reloaded
cy.window().then(w => w.beforeReload = true);
}
describe("Threads", () => {
let synapse: SynapseInstance;
beforeEach(() => {
cy.window().then(win => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
win.localStorage.setItem("mx_labs_feature_feature_thread", "true"); // Default threads to ON for this spec
});
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
});
it("should reload when enabling threads beta", () => {
markWindowBeforeReload();
// Turn off
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);
cy.leaveBeta("Threads");
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});
cy.get(".mx_MatrixChat", { timeout: 15000 }); // wait for the app
markWindowBeforeReload();
// Turn on
cy.openUserSettings("Labs").within(() => {
// initially the new property is there
cy.window().should("have.prop", "beforeReload", true);
cy.joinBeta("Threads");
// after reload the property should be gone
cy.window().should("not.have.prop", "beforeReload");
});
});
it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(synapse, "BotBob").then(_bot => {
bot = _bot;
});
let roomId: string;
cy.createRoom({}).then(_roomId => {
roomId = _roomId;
cy.inviteUser(roomId, bot.getUserId());
cy.visit("/#/room/" + roomId);
});
// User sends message
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
// Wait for message to send, get its ID and save as @threadId
cy.get(".mx_RoomView_body .mx_EventTile").contains("Hello Mr. Bot")
.closest(".mx_EventTile[data-scroll-tokens]").invoke("attr", "data-scroll-tokens").as("threadId");
// Bot starts thread
cy.get<string>("@threadId").then(threadId => {
bot.sendMessage(roomId, threadId, {
body: "Hello there",
msgtype: "m.text",
});
});
// User asserts timeline thread summary visible & clicks it
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Hello there");
cy.get(".mx_RoomView_body .mx_ThreadSummary").click();
// User responds in thread
cy.get(".mx_ThreadView .mx_BasicMessageComposer_input").type("Test{enter}");
// User asserts summary was updated correctly
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Test");
// User reacts to message instead
cy.get(".mx_ThreadView .mx_EventTile").contains("Hello there").closest(".mx_EventTile_line")
.find('[aria-label="React"]').click({ force: true }); // Cypress has no ability to hover
cy.get(".mx_EmojiPicker").within(() => {
cy.get('input[type="text"]').type("wave");
cy.get('[role="menuitem"]').contains("👋").click();
});
// User redacts their prior response
cy.get(".mx_ThreadView .mx_EventTile").contains("Test").closest(".mx_EventTile_line")
.find('[aria-label="Options"]').click({ force: true }); // Cypress has no ability to hover
cy.get(".mx_IconizedContextMenu").within(() => {
cy.get('[role="menuitem"]').contains("Remove").click();
});
cy.get(".mx_TextInputDialog").within(() => {
cy.get(".mx_Dialog_primary").contains("Remove").click();
});
// User asserts summary was updated correctly
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Hello there");
// User closes right panel after clicking back to thread list
cy.get(".mx_ThreadView .mx_BaseCard_back").click();
cy.get(".mx_ThreadPanel .mx_BaseCard_close").click();
// Bot responds to thread
cy.get<string>("@threadId").then(threadId => {
bot.sendMessage(roomId, threadId, {
body: "How are things?",
msgtype: "m.text",
});
});
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "How are things?");
// User asserts thread list unread indicator
cy.get('.mx_HeaderButtons [aria-label="Threads"]').should("have.class", "mx_RightPanel_headerButton_unread");
// User opens thread list
cy.get('.mx_HeaderButtons [aria-label="Threads"]').click();
// User asserts thread with correct root & latest events & unread dot
cy.get(".mx_ThreadPanel .mx_EventTile_last").within(() => {
cy.get(".mx_EventTile_body").should("contain", "Hello Mr. Bot");
cy.get(".mx_ThreadSummary_content").should("contain", "How are things?");
// User opens thread via threads list
cy.get(".mx_EventTile_line").click();
});
// User responds & asserts
cy.get(".mx_ThreadView .mx_BasicMessageComposer_input").type("Great!{enter}");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Great!");
// User edits & asserts
cy.get(".mx_ThreadView .mx_EventTile_last").contains("Great!").closest(".mx_EventTile_line").within(() => {
cy.get('[aria-label="Edit"]').click({ force: true }); // Cypress has no ability to hover
cy.get(".mx_BasicMessageComposer_input").type(" How about yourself?{enter}");
});
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
.should("contain", "Great! How about yourself?");
// User closes right panel
cy.get(".mx_ThreadView .mx_BaseCard_close").click();
// Bot responds to thread and saves the id of their message to @eventId
cy.get<string>("@threadId").then(threadId => {
cy.wrap(bot.sendMessage(roomId, threadId, {
body: "I'm very good thanks",
msgtype: "m.text",
}).then(res => res.event_id)).as("eventId");
});
// User asserts
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
.should("contain", "I'm very good thanks");
// Bot edits their latest event
cy.get<string>("@eventId").then(eventId => {
bot.sendMessage(roomId, {
"body": "* I'm very good thanks :)",
"msgtype": "m.text",
"m.new_content": {
"body": "I'm very good thanks :)",
"msgtype": "m.text",
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": eventId,
},
});
});
// User asserts
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "BotBob");
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content")
.should("contain", "I'm very good thanks :)");
});
});
@@ -0,0 +1 @@
A synapse configured with user privacy consent disabled
@@ -0,0 +1,52 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ['::']
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
@@ -0,0 +1,50 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false
+63
View 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
View 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);
});
});
+3
View File
@@ -18,3 +18,6 @@ limitations under the License.
import "./synapse";
import "./login";
import "./client";
import "./settings";
import "./bot";
+21 -9
View File
@@ -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
View 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();
});
});
+2 -1
View File
@@ -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';