Add dendrite support to cypress tests (#9884)

* Minimum hacks required to run cypress tests with dendrite

* Remove wget hack since dendrite containers now have curl

* Add basic dendritedocker plugin & hack into login spec for testing

* Add generic HomeserverInstance interface

* Add env var to configure which homeserver to use

* Remove synapse specific homeserver support api

* Update the rest of the tests to use HomeserverInstance

* Update cypress docs to reference new homeserver abstraction

* Fix formatting issues

* Change dendrite to use main branch container
This commit is contained in:
devonh 2023-01-10 23:29:56 +00:00 committed by GitHub
parent b642df98e9
commit 79033eb034
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 947 additions and 362 deletions

1
.gitignore vendored
View file

@ -24,6 +24,7 @@ package-lock.json
/cypress/downloads
/cypress/screenshots
/cypress/synapselogs
/cypress/dendritelogs
# These could have files in them but don't currently
# Cypress will still auto-create them though...
/cypress/performance

View file

@ -33,6 +33,7 @@ export default defineConfig({
env: {
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
SLIDING_SYNC_PROXY_TAG: "v0.6.0",
HOMESERVER: "synapse",
},
retries: {
runMode: 4,

View file

@ -16,25 +16,25 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
describe("Composer", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("CIDER", () => {
beforeEach(() => {
cy.initTestUser(synapse, "Janet").then(() => {
cy.initTestUser(homeserver, "Janet").then(() => {
cy.createRoom({ name: "Composing Room" });
});
cy.viewRoomByName("Composing Room");
@ -101,7 +101,7 @@ describe("Composer", () => {
describe("WYSIWYG", () => {
beforeEach(() => {
cy.enableLabsFeature("feature_wysiwyg_composer");
cy.initTestUser(synapse, "Janet").then(() => {
cy.initTestUser(homeserver, "Janet").then(() => {
cy.createRoom({ name: "Composing Room" });
});
cy.viewRoomByName("Composing Room");

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
function openCreateRoomDialog(): Chainable<JQuery<HTMLElement>> {
@ -26,18 +26,18 @@ function openCreateRoomDialog(): Chainable<JQuery<HTMLElement>> {
}
describe("Create Room", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Jim");
cy.initTestUser(homeserver, "Jim");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should allow us to create a public room with name, topic & address set", () => {

View file

@ -18,12 +18,12 @@ import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/m
import type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import type { CypressBot } from "../../support/bot";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
type EmojiMapping = [emoji: string, name: string];
interface CryptoTestContext extends Mocha.Context {
synapse: SynapseInstance;
homeserver: HomeserverInstance;
bob: CypressBot;
}
@ -155,16 +155,16 @@ const verify = function (this: CryptoTestContext) {
describe("Cryptography", function () {
beforeEach(function () {
cy.startSynapse("default")
.as("synapse")
.then((synapse: SynapseInstance) => {
cy.initTestUser(synapse, "Alice", undefined, "alice_");
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob");
cy.startHomeserver("default")
.as("homeserver")
.then((homeserver: HomeserverInstance) => {
cy.initTestUser(homeserver, "Alice", undefined, "alice_");
cy.getBot(homeserver, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob");
});
});
afterEach(function (this: CryptoTestContext) {
cy.stopSynapse(this.synapse);
cy.stopHomeserver(this.homeserver);
});
it("setting up secure key backup should work", () => {
@ -215,7 +215,7 @@ describe("Cryptography", function () {
cy.bootstrapCrossSigning();
// bob has a second, not cross-signed, device
cy.loginBot(this.synapse, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
autoJoin(this.bob);

View file

@ -17,7 +17,7 @@ limitations under the License.
import type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
import Chainable = Cypress.Chainable;
@ -56,20 +56,20 @@ const handleVerificationRequest = (request: VerificationRequest): Chainable<Emoj
};
describe("Decryption Failure Bar", () => {
let synapse: SynapseInstance | undefined;
let homeserver: HomeserverInstance | undefined;
let testUser: UserCredentials | undefined;
let bot: MatrixClient | undefined;
let roomId: string;
beforeEach(function () {
cy.startSynapse("default").then((syn: SynapseInstance) => {
synapse = syn;
cy.initTestUser(synapse, TEST_USER)
cy.startHomeserver("default").then((hs: HomeserverInstance) => {
homeserver = hs;
cy.initTestUser(homeserver, TEST_USER)
.then((creds: UserCredentials) => {
testUser = creds;
})
.then(() => {
cy.getBot(synapse, { displayName: BOT_USER }).then((cli) => {
cy.getBot(homeserver, { displayName: BOT_USER }).then((cli) => {
bot = cli;
});
})
@ -97,7 +97,7 @@ describe("Decryption Failure Bar", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it(
@ -105,7 +105,7 @@ describe("Decryption Failure Bar", () => {
"and there are other verified devices or backups",
() => {
let otherDevice: MatrixClient | undefined;
cy.loginBot(synapse, testUser.username, testUser.password, {})
cy.loginBot(homeserver, testUser.username, testUser.password, {})
.then(async (cli) => {
otherDevice = cli;
await otherDevice.bootstrapCrossSigning({
@ -169,7 +169,7 @@ describe("Decryption Failure Bar", () => {
"should prompt the user to reset keys, if this device isn't verified " +
"and there are no other verified devices or backups",
() => {
cy.loginBot(synapse, testUser.username, testUser.password, {}).then(async (cli) => {
cy.loginBot(homeserver, testUser.username, testUser.password, {}).then(async (cli) => {
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
await makeRequest({});

View file

@ -19,7 +19,7 @@ limitations under the License.
import type { MsgType } from "matrix-js-sdk/src/@types/event";
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
import type { EventType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
@ -30,12 +30,12 @@ const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
};
describe("Editing", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Edith").then(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Edith").then(() => {
cy.injectAxe();
return cy.createRoom({ name: "Test room" }).as("roomId");
});
@ -43,7 +43,7 @@ describe("Editing", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should close the composer when clicking save after making a change and undoing it", () => {

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -73,17 +73,17 @@ function sendActionFromIntegrationManager(integrationManagerUrl: string) {
describe("Integration Manager: Get OpenID Token", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -122,7 +122,7 @@ describe("Integration Manager: Get OpenID Token", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import { UserCredentials } from "../../support/login";
@ -94,17 +94,17 @@ function expectKickedMessage(shouldExist: boolean) {
describe("Integration Manager: Kick", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -140,12 +140,12 @@ describe("Integration Manager: Kick", () => {
name: ROOM_NAME,
}).as("roomId");
cy.getBot(synapse, { displayName: BOT_DISPLAY_NAME, autoAcceptInvites: true }).as("bob");
cy.getBot(homeserver, { displayName: BOT_DISPLAY_NAME, autoAcceptInvites: true }).as("bob");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -87,17 +87,17 @@ function sendActionFromIntegrationManager(
describe("Integration Manager: Read Events", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -136,7 +136,7 @@ describe("Integration Manager: Read Events", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -93,17 +93,17 @@ function sendActionFromIntegrationManager(
describe("Integration Manager: Send Event", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -142,7 +142,7 @@ describe("Integration Manager: Send Event", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import Chainable = Cypress.Chainable;
@ -26,7 +26,7 @@ interface Charly {
}
describe("Lazy Loading", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let bob: MatrixClient;
const charlies: Charly[] = [];
@ -35,12 +35,12 @@ describe("Lazy Loading", () => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice");
cy.initTestUser(homeserver, "Alice");
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Bob",
startClient: false,
autoAcceptInvites: false,
@ -50,7 +50,7 @@ describe("Lazy Loading", () => {
for (let i = 1; i <= 10; i++) {
const displayName = `Charly #${i}`;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName,
startClient: false,
autoAcceptInvites: false,
@ -62,7 +62,7 @@ describe("Lazy Loading", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
const name = "Lazy Loading Test";

View file

@ -16,11 +16,11 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
describe("Location sharing", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const selectLocationShareTypeOption = (shareType: string): Chainable<JQuery> => {
return cy.get(`[data-test-id="share-location-option-${shareType}"]`);
@ -34,15 +34,15 @@ describe("Location sharing", () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("sends and displays pin drop location message successfully", () => {

View file

@ -18,21 +18,21 @@ limitations under the License.
import { SinonStub } from "cypress/types/sinon";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Consent", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Bob");
cy.initTestUser(homeserver, "Bob");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should prompt the user to consent to terms when server deems it necessary", () => {
@ -53,8 +53,8 @@ describe("Consent", () => {
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 }) => {
// Go to Homeserver's consent page and accept it
cy.origin(homeserver.baseUrl, { args: { url } }, ({ url }) => {
cy.visit(url);
cy.get('[type="submit"]').click();

View file

@ -16,17 +16,17 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Login", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.stubDefaultServer();
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("m.login.password", () => {
@ -34,9 +34,9 @@ describe("Login", () => {
const password = "p4s5W0rD";
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.registerUser(synapse, username, password);
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.registerUser(homeserver, username, password);
cy.visit("/#/login");
});
});
@ -49,7 +49,7 @@ describe("Login", () => {
cy.checkA11y();
cy.get(".mx_ServerPicker_change").click();
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");
@ -64,9 +64,9 @@ describe("Login", () => {
describe("logout", () => {
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Erin");
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Erin");
});
});

View file

@ -18,14 +18,14 @@ limitations under the License.
import { PollResponseEvent } from "matrix-events-sdk";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import Chainable = Cypress.Chainable;
const hideTimestampCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
describe("Polls", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
type CreatePollOptions = {
title: string;
@ -81,20 +81,20 @@ describe("Polls", () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should be creatable and votable", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -163,7 +163,7 @@ describe("Polls", () => {
it("should be editable from context menu if no votes have been cast", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -206,7 +206,7 @@ describe("Polls", () => {
it("should not be editable from context menu if votes have been cast", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -256,10 +256,10 @@ describe("Polls", () => {
it("should be displayed correctly in thread panel", () => {
let botBob: MatrixClient;
let botCharlie: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
botBob = _bot;
});
cy.getBot(synapse, { displayName: "BotCharlie" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotCharlie" }).then((_bot) => {
botCharlie = _bot;
});

View file

@ -16,21 +16,21 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Registration", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.stubDefaultServer();
cy.visit("/#/register");
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.startHomeserver("consent").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("registers an account and lands on the home screen", () => {
@ -42,13 +42,13 @@ describe("Registration", () => {
cy.get(".mx_Dialog").percySnapshotElement("Server Picker", { widths: [516] });
cy.checkA11y();
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");
cy.get("#mx_RegistrationForm_username").should("be.visible");
// Hide the server text as it contains the randomly allocated Synapse port
// Hide the server text as it contains the randomly allocated Homeserver port
const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
cy.percySnapshot("Registration", { percyCSS });
cy.checkA11y();
@ -88,7 +88,7 @@ describe("Registration", () => {
it("should require username to fulfil requirements and be available", () => {
cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
cy.get(".mx_ServerPickerDialog_continue").should("be.visible");
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");

View file

@ -16,21 +16,21 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Pills", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should navigate clicks internally to the app", () => {

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
const ROOM_NAME = "Test room";
@ -43,12 +43,12 @@ const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => {
};
describe("RightPanel", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, NAME).then(() =>
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, NAME).then(() =>
cy.window({ log: false }).then(() => {
cy.createRoom({ name: ROOM_NAME });
cy.createSpace({ name: SPACE_NAME });
@ -58,7 +58,7 @@ describe("RightPanel", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("in rooms", () => {

View file

@ -16,23 +16,23 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("Room Directory", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Ray");
cy.getBot(synapse, { displayName: "Paul" }).as("bot");
cy.initTestUser(homeserver, "Ray");
cy.getBot(homeserver, { displayName: "Paul" }).as("bot");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should allow admin to add alias & publish room to directory", () => {

View file

@ -18,34 +18,34 @@ limitations under the License.
import { EventType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("Room Directory", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice");
cy.initTestUser(homeserver, "Alice");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should switch between existing dm rooms without a loader", () => {
let bobClient: MatrixClient;
let charlieClient: MatrixClient;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Bob",
}).then((bob) => {
bobClient = bob;
});
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Charlie",
}).then((charlie) => {
charlieClient = charlie;

View file

@ -16,34 +16,34 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import type { UserCredentials } from "../../support/login";
describe("Device manager", () => {
let synapse: SynapseInstance | undefined;
let homeserver: HomeserverInstance | undefined;
let user: UserCredentials | undefined;
beforeEach(() => {
cy.enableLabsFeature("feature_new_device_manager");
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice")
cy.initTestUser(homeserver, "Alice")
.then((credentials) => {
user = credentials;
})
.then(() => {
// create some extra sessions to manage
return cy.loginUser(synapse, user.username, user.password);
return cy.loginUser(homeserver, user.username, user.password);
})
.then(() => {
return cy.loginUser(synapse, user.username, user.password);
return cy.loginUser(homeserver, user.username, user.password);
});
});
});
afterEach(() => {
cy.stopSynapse(synapse!);
cy.stopHomeserver(homeserver!);
});
it("should display sessions", () => {

View file

@ -16,10 +16,10 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
function seedLabs(synapse: SynapseInstance, labsVal: boolean | null): void {
cy.initTestUser(synapse, "Sally", () => {
function seedLabs(homeserver: HomeserverInstance, labsVal: boolean | null): void {
cy.initTestUser(homeserver, "Sally", () => {
// seed labs flag
cy.window({ log: false }).then((win) => {
if (typeof labsVal === "boolean") {
@ -61,30 +61,30 @@ describe("Hidden Read Receipts Setting Migration", () => {
// For a security-sensitive feature like hidden read receipts, it's absolutely vital
// that we migrate the setting appropriately.
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should not migrate the lack of a labs flag", () => {
seedLabs(synapse, null);
seedLabs(homeserver, null);
testForVal(null);
});
it("should migrate labsHiddenRR=false as sendRR=true", () => {
seedLabs(synapse, false);
seedLabs(homeserver, false);
testForVal(true);
});
it("should migrate labsHiddenRR=true as sendRR=false", () => {
seedLabs(synapse, true);
seedLabs(homeserver, true);
testForVal(false);
});
});

View file

@ -20,43 +20,45 @@ import _ from "lodash";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Interception } from "cypress/types/net-stubbing";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { Layout } from "../../../src/settings/enums/Layout";
import { ProxyInstance } from "../../plugins/sliding-sync";
describe("Sliding Sync", () => {
beforeEach(() => {
cy.startSynapse("default")
.as("synapse")
.then((synapse) => {
cy.startProxy(synapse).as("proxy");
cy.startHomeserver("default")
.as("homeserver")
.then((homeserver) => {
cy.startProxy(homeserver).as("proxy");
});
cy.all([cy.get<SynapseInstance>("@synapse"), cy.get<ProxyInstance>("@proxy")]).then(([synapse, proxy]) => {
cy.enableLabsFeature("feature_sliding_sync");
cy.all([cy.get<HomeserverInstance>("@homeserver"), cy.get<ProxyInstance>("@proxy")]).then(
([homeserver, proxy]) => {
cy.enableLabsFeature("feature_sliding_sync");
cy.intercept("/config.json?cachebuster=*", (req) => {
return req.continue((res) => {
res.send(200, {
...res.body,
setting_defaults: {
feature_sliding_sync_proxy_url: `http://localhost:${proxy.port}`,
},
cy.intercept("/config.json?cachebuster=*", (req) => {
return req.continue((res) => {
res.send(200, {
...res.body,
setting_defaults: {
feature_sliding_sync_proxy_url: `http://localhost:${proxy.port}`,
},
});
});
});
});
cy.initTestUser(synapse, "Sloth").then(() => {
return cy.window({ log: false }).then(() => {
cy.createRoom({ name: "Test Room" }).as("roomId");
cy.initTestUser(homeserver, "Sloth").then(() => {
return cy.window({ log: false }).then(() => {
cy.createRoom({ name: "Test Room" }).as("roomId");
});
});
});
});
},
);
});
afterEach(() => {
cy.get<SynapseInstance>("@synapse").then(cy.stopSynapse);
cy.get<HomeserverInstance>("@homeserver").then(cy.stopHomeserver);
cy.get<ProxyInstance>("@proxy").then(cy.stopProxy);
});
@ -84,9 +86,9 @@ describe("Sliding Sync", () => {
};
const createAndJoinBob = () => {
// create a Bob user
cy.get<SynapseInstance>("@synapse").then((synapse) => {
cy.get<HomeserverInstance>("@homeserver").then((homeserver) => {
return cy
.getBot(synapse, {
.getBot(homeserver, {
displayName: "Bob",
})
.as("bob");

View file

@ -18,7 +18,7 @@ limitations under the License.
import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import { UserCredentials } from "../../support/login";
@ -59,14 +59,14 @@ function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"
}
describe("Spaces", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sue").then((_user) => {
cy.initTestUser(homeserver, "Sue").then((_user) => {
user = _user;
cy.mockClipboard();
});
@ -74,7 +74,7 @@ describe("Spaces", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it.only("should allow user to create public space", () => {
@ -173,7 +173,7 @@ describe("Spaces", () => {
it("should allow user to invite another to a space", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -208,7 +208,7 @@ describe("Spaces", () => {
});
cy.getSpacePanelButton("My Space").should("exist");
cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
await bot.invite(roomId, user.userId);
});

View file

@ -17,7 +17,7 @@ limitations under the License.
/// <reference types="cypress" />
import { MatrixClient } from "../../global";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import Loggable = Cypress.Loggable;
import Timeoutable = Cypress.Timeoutable;
@ -136,7 +136,7 @@ Cypress.Commands.add("startDM", (name: string) => {
});
describe("Spotlight", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const bot1Name = "BotBob";
let bot1: MatrixClient;
@ -154,16 +154,16 @@ describe("Spotlight", () => {
let room3Id: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jim")
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jim")
.then(() =>
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
cy.getBot(homeserver, { displayName: bot1Name }).then((_bot1) => {
bot1 = _bot1;
}),
)
.then(() =>
cy.getBot(synapse, { displayName: bot2Name }).then((_bot2) => {
cy.getBot(homeserver, { displayName: bot2Name }).then((_bot2) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bot2 = _bot2;
}),
@ -205,7 +205,7 @@ describe("Spotlight", () => {
afterEach(() => {
cy.visit("/#/home");
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should be able to add and remove filters via keyboard", () => {

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
function markWindowBeforeReload(): void {
@ -25,7 +25,7 @@ function markWindowBeforeReload(): void {
}
describe("Threads", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
// Default threads to ON for this spec
@ -33,15 +33,15 @@ describe("Threads", () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should reload when enabling threads beta", () => {
@ -75,7 +75,7 @@ describe("Threads", () => {
it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "BotBob",
autoAcceptInvites: false,
}).then((_bot) => {

View file

@ -18,7 +18,7 @@ limitations under the License.
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
import type { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { Layout } from "../../../src/settings/enums/Layout";
import Chainable = Cypress.Chainable;
@ -67,7 +67,7 @@ const sendEvent = (roomId: string, html = false): Chainable<ISendEventResponse>
};
describe("Timeline", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let roomId: string;
@ -75,9 +75,9 @@ describe("Timeline", () => {
let newAvatarUrl: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, OLD_NAME).then(() =>
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, OLD_NAME).then(() =>
cy.createRoom({ name: ROOM_NAME }).then((_room1Id) => {
roomId = _room1Id;
}),
@ -86,7 +86,7 @@ describe("Timeline", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("useOnlyCurrentProfiles", () => {

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
function assertNoToasts(): void {
@ -40,10 +40,10 @@ function rejectToast(expectedTitle: string): void {
}
describe("Analytics Toast", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should not show an analytics toast if config has nothing about posthog", () => {
@ -55,9 +55,9 @@ describe("Analytics Toast", () => {
});
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Tod");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Tod");
});
rejectToast("Notifications");
@ -78,9 +78,9 @@ describe("Analytics Toast", () => {
});
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Tod");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Tod");
rejectToast("Notifications");
});
});

View file

@ -16,19 +16,19 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Update", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should navigate to ?updated=$VERSION if realises it is immediately out of date on load", () => {
@ -42,7 +42,7 @@ describe("Update", () => {
},
}).as("version");
cy.initTestUser(synapse, "Ursa");
cy.initTestUser(homeserver, "Ursa");
cy.wait("@version");
cy.url()

View file

@ -16,25 +16,25 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import type { UserCredentials } from "../../support/login";
describe("User Menu", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Jeff").then((credentials) => {
cy.initTestUser(homeserver, "Jeff").then((credentials) => {
user = credentials;
});
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should contain our name & userId", () => {

View file

@ -17,18 +17,18 @@ limitations under the License.
/// <reference types="cypress" />
import { MatrixClient } from "../../global";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("User Onboarding (new user)", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const bot1Name = "BotBob";
let bot1: MatrixClient;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jane Doe");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jane Doe");
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("mx_registration_time", "1656633601");
});
@ -36,7 +36,7 @@ describe("User Onboarding (new user)", () => {
// wait for the app to load
return cy.get(".mx_MatrixChat", { timeout: 15000 });
});
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
cy.getBot(homeserver, { displayName: bot1Name }).then((_bot1) => {
bot1 = _bot1;
});
cy.get(".mx_UserOnboardingPage").should("exist");
@ -51,7 +51,7 @@ describe("User Onboarding (new user)", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("page is shown and preference exists", () => {

View file

@ -16,15 +16,15 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("User Onboarding (old user)", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jane Doe");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jane Doe");
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("mx_registration_time", "2");
});
@ -37,7 +37,7 @@ describe("User Onboarding (old user)", () => {
afterEach(() => {
cy.visit("/#/home");
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("page and preference are hidden", () => {

View file

@ -16,23 +16,23 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("UserView", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Violet");
cy.getBot(synapse, { displayName: "Usman" }).as("bot");
cy.initTestUser(homeserver, "Violet");
cy.getBot(homeserver, { displayName: "Usman" }).as("bot");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should render the user view as expected", () => {

View file

@ -17,7 +17,7 @@ limitations under the License.
import { IWidget } from "matrix-widget-api";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
const ROOM_NAME = "Test Room";
const WIDGET_ID = "fake-widget";
@ -34,14 +34,14 @@ const WIDGET_HTML = `
describe("Widget Layout", () => {
let widgetUrl: string;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let roomId: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
widgetUrl = url;
@ -91,7 +91,7 @@ describe("Widget Layout", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker";
const STICKER_PICKER_WIDGET_NAME = "Fake Stickers";
@ -102,13 +102,13 @@ describe("Stickers", () => {
// See sendStickerFromPicker() for more detail on iframe comms.
let stickerPickerUrl: string;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
stickerPickerUrl = url;
@ -116,7 +116,7 @@ describe("Stickers", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -20,7 +20,7 @@ limitations under the License.
import { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
import type { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const DEMO_WIDGET_ID = "demo-widget-id";
@ -90,7 +90,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str
}
describe("Widget PIP", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
let bot: MatrixClient;
let demoWidgetUrl: string;
@ -173,13 +173,13 @@ describe("Widget PIP", () => {
}
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Mike").then((_user) => {
cy.initTestUser(homeserver, "Mike").then((_user) => {
user = _user;
});
cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
cy.getBot(homeserver, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
bot = _bot;
});
});
@ -189,7 +189,7 @@ describe("Widget PIP", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View file

@ -0,0 +1,181 @@
/*
Copyright 2023 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 os from "os";
import * as crypto from "crypto";
import * as fse from "fs-extra";
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { getFreePort } from "../utils/port";
import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
// A cypress plugins to add command to start & stop dendrites in
// docker with preset templates.
const dendrites = new Map<string, HomeserverInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<HomeserverConfig> {
template = "default";
const templateDir = path.join(__dirname, "templates", template);
const configFile = "dendrite.yaml";
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-dendritedocker-"));
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== configFile });
const registrationSecret = randB64Bytes(16);
const port = await getFreePort();
const baseUrl = `http://localhost:${port}`;
// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, configFile)}`);
let hsYaml = await fse.readFile(path.join(templateDir, configFile), "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
await fse.writeFile(path.join(tempDir, configFile), hsYaml);
await dockerRun({
image: "matrixdotorg/dendrite-monolith:main",
params: ["--rm", "--entrypoint=", "-v", `${tempDir}:/mnt`],
containerName: `react-sdk-cypress-dendrite-keygen`,
cmd: ["/usr/bin/generate-keys", "-private-key", "/mnt/matrix_key.pem"],
});
return {
port,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
// Start a dendrite instance: the template must be the name of
// one of the templates in the cypress/plugins/dendritedocker/templates
// directory
async function dendriteStart(template: string): Promise<HomeserverInstance> {
const denCfg = await cfgDirFromTemplate(template);
console.log(`Starting dendrite with config dir ${denCfg.configDir}...`);
const dendriteId = await dockerRun({
image: "matrixdotorg/dendrite-monolith:main",
params: [
"--rm",
"-v",
`${denCfg.configDir}:/etc/dendrite`,
"-p",
`${denCfg.port}:8008/tcp`,
"--entrypoint",
"/usr/bin/dendrite-monolith-server",
],
containerName: `react-sdk-cypress-dendrite`,
cmd: ["--really-enable-open-registration", "true", "run"],
});
console.log(`Started dendrite with id ${dendriteId} on port ${denCfg.port}.`);
// Await Dendrite healthcheck
await dockerExec({
containerId: dendriteId,
params: [
"curl",
"--connect-timeout",
"30",
"--retry",
"30",
"--retry-delay",
"1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/_matrix/client/versions",
],
});
const dendrite: HomeserverInstance = { serverId: dendriteId, ...denCfg };
dendrites.set(dendriteId, dendrite);
return dendrite;
}
async function dendriteStop(id: string): Promise<void> {
const denCfg = dendrites.get(id);
if (!denCfg) throw new Error("Unknown dendrite ID");
const dendriteLogsPath = path.join("cypress", "dendritelogs", id);
await fse.ensureDir(dendriteLogsPath);
await dockerLogs({
containerId: id,
stdoutFile: path.join(dendriteLogsPath, "stdout.log"),
stderrFile: path.join(dendriteLogsPath, "stderr.log"),
});
await dockerStop({
containerId: id,
});
await fse.remove(denCfg.configDir);
dendrites.delete(id);
console.log(`Stopped dendrite id ${id}.`);
// cypress deliberately fails if you return 'undefined', so
// return null to signal all is well, and we've handled the task.
return null;
}
/**
* @type {Cypress.PluginConfig}
*/
export function dendriteDocker(on: PluginEvents, config: PluginConfigOptions) {
on("task", {
dendriteStart,
dendriteStop,
});
on("after:spec", async (spec) => {
// Cleans up any remaining dendrite instances after a spec run
// This is on the theory that we should avoid re-using dendrite
// instances between spec runs: they should be cheap enough to
// start that we can have a separate one for each spec run or even
// test. If we accidentally re-use dendrites, we could inadvertently
// make our tests depend on each other.
for (const denId of dendrites.keys()) {
console.warn(`Cleaning up dendrite ID ${denId} after ${spec.name}`);
await dendriteStop(denId);
}
});
on("before:run", async () => {
// tidy up old dendrite log files before each run
await fse.emptyDir(path.join("cypress", "dendritelogs"));
});
}

View file

@ -0,0 +1,374 @@
# This is the Dendrite configuration file.
#
# The configuration is split up into sections - each Dendrite component has a
# configuration section, in addition to the "global" section which applies to
# all components.
# The version of the configuration file.
version: 2
# Global Matrix configuration. This configuration applies to all components.
global:
# The domain name of this homeserver.
server_name: localhost
# The path to the signing private key file, used to sign requests and events.
# Note that this is NOT the same private key as used for TLS! To generate a
# signing key, use "./bin/generate-keys --private-key matrix_key.pem".
private_key: matrix_key.pem
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
# to old signing keys that were formerly in use on this domain name. These
# keys will not be used for federation request or event signing, but will be
# provided to any other homeserver that asks when trying to verify old events.
old_private_keys:
# If the old private key file is available:
# - private_key: old_matrix_key.pem
# expired_at: 1601024554498
# If only the public key (in base64 format) and key ID are known:
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
# key_id: ed25519:mykeyid
# expired_at: 1601024554498
# How long a remote server can cache our server signing key before requesting it
# again. Increasing this number will reduce the number of requests made by other
# servers for our key but increases the period that a compromised key will be
# considered valid by other homeservers.
key_validity_period: 168h0m0s
# Global database connection pool, for PostgreSQL monolith deployments only. If
# this section is populated then you can omit the "database" blocks in all other
# sections. For polylith deployments, or monolith deployments using SQLite databases,
# you must configure the "database" block for each component instead.
# database:
# connection_string: postgresql://username:password@hostname/dendrite?sslmode=disable
# max_open_conns: 90
# max_idle_conns: 5
# conn_max_lifetime: -1
# Configuration for in-memory caches. Caches can often improve performance by
# keeping frequently accessed items (like events, identifiers etc.) in memory
# rather than having to read them from the database.
cache:
# The estimated maximum size for the global cache in bytes, or in terabytes,
# gigabytes, megabytes or kilobytes when the appropriate 'tb', 'gb', 'mb' or
# 'kb' suffix is specified. Note that this is not a hard limit, nor is it a
# memory limit for the entire process. A cache that is too small may ultimately
# provide little or no benefit.
max_size_estimated: 1gb
# The maximum amount of time that a cache entry can live for in memory before
# it will be evicted and/or refreshed from the database. Lower values result in
# easier admission of new cache entries but may also increase database load in
# comparison to higher values, so adjust conservatively. Higher values may make
# it harder for new items to make it into the cache, e.g. if new rooms suddenly
# become popular.
max_age: 1h
# The server name to delegate server-server communications to, with optional port
# e.g. localhost:443
well_known_server_name: ""
# The server name to delegate client-server communications to, with optional port
# e.g. localhost:443
well_known_client_name: ""
# Lists of domains that the server will trust as identity servers to verify third
# party identifiers such as phone numbers and email addresses.
trusted_third_party_id_servers:
- matrix.org
- vector.im
# Disables federation. Dendrite will not be able to communicate with other servers
# in the Matrix federation and the federation API will not be exposed.
disable_federation: false
# Configures the handling of presence events. Inbound controls whether we receive
# presence events from other servers, outbound controls whether we send presence
# events for our local users to other servers.
presence:
enable_inbound: false
enable_outbound: false
# Configures phone-home statistics reporting. These statistics contain the server
# name, number of active users and some information on your deployment config.
# We use this information to understand how Dendrite is being used in the wild.
report_stats:
enabled: false
endpoint: https://matrix.org/report-usage-stats/push
# Server notices allows server admins to send messages to all users on the server.
server_notices:
enabled: false
# The local part, display name and avatar URL (as a mxc:// URL) for the user that
# will send the server notices. These are visible to all users on the deployment.
local_part: "_server"
display_name: "Server Alerts"
avatar_url: ""
# The room name to be used when sending server notices. This room name will
# appear in user clients.
room_name: "Server Alerts"
# Configuration for NATS JetStream
jetstream:
# A list of NATS Server addresses to connect to. If none are specified, an
# internal NATS server will be started automatically when running Dendrite in
# monolith mode. For polylith deployments, it is required to specify the address
# of at least one NATS Server node.
addresses:
# - localhost:4222
# Disable the validation of TLS certificates of NATS. This is
# not recommended in production since it may allow NATS traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Persistent directory to store JetStream streams in. This directory should be
# preserved across Dendrite restarts.
storage_path: ./
# The prefix to use for stream names for this homeserver - really only useful
# if you are running more than one Dendrite server on the same NATS deployment.
topic_prefix: Dendrite
# Configuration for Prometheus metric collection.
metrics:
enabled: false
basic_auth:
username: metrics
password: metrics
# Optional DNS cache. The DNS cache may reduce the load on DNS servers if there
# is no local caching resolver available for use.
dns_cache:
enabled: false
cache_size: 256
cache_lifetime: "5m" # 5 minutes; https://pkg.go.dev/time@master#ParseDuration
# Configuration for the Appservice API.
app_service_api:
# Disable the validation of TLS certificates of appservices. This is
# not recommended in production since it may allow appservice traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Appservice configuration files to load into this homeserver.
config_files:
# - /path/to/appservice_registration.yaml
# Configuration for the Client API.
client_api:
# Prevents new users from being able to register on this homeserver, except when
# using the registration shared secret below.
registration_disabled: false
# Prevents new guest accounts from being created. Guest registration is also
# disabled implicitly by setting 'registration_disabled' above.
guests_disabled: true
# If set, allows registration by anyone who knows the shared secret, regardless
# of whether registration is otherwise disabled.
registration_shared_secret: "{{REGISTRATION_SECRET}}"
# Whether to require reCAPTCHA for registration. If you have enabled registration
# then this is HIGHLY RECOMMENDED to reduce the risk of your homeserver being used
# for coordinated spam attacks.
enable_registration_captcha: false
# Settings for ReCAPTCHA.
recaptcha_public_key: ""
recaptcha_private_key: ""
recaptcha_bypass_secret: ""
# To use hcaptcha.com instead of ReCAPTCHA, set the following parameters, otherwise just keep them empty.
# recaptcha_siteverify_api: "https://hcaptcha.com/siteverify"
# recaptcha_api_js_url: "https://js.hcaptcha.com/1/api.js"
# recaptcha_form_field: "h-captcha-response"
# recaptcha_sitekey_class: "h-captcha"
# TURN server information that this homeserver should send to clients.
turn:
turn_user_lifetime: "5m"
turn_uris:
# - turn:turn.server.org?transport=udp
# - turn:turn.server.org?transport=tcp
turn_shared_secret: ""
# If your TURN server requires static credentials, then you will need to enter
# them here instead of supplying a shared secret. Note that these credentials
# will be visible to clients!
# turn_username: ""
# turn_password: ""
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
# number of "slots" have been taken by requests from a specific host. Each "slot"
# will be released after the cooloff time in milliseconds. Server administrators
# and appservice users are exempt from rate limiting by default.
rate_limiting:
enabled: true
threshold: 20
cooloff_ms: 500
exempt_user_ids:
# - "@user:domain.com"
# Configuration for the Federation API.
federation_api:
# How many times we will try to resend a failed transaction to a specific server. The
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc. Once
# the max retries are exceeded, Dendrite will no longer try to send transactions to
# that server until it comes back to life and connects to us again.
send_max_retries: 16
# Disable the validation of TLS certificates of remote federated homeservers. Do not
# enable this option in production as it presents a security risk!
disable_tls_validation: false
# Disable HTTP keepalives, which also prevents connection reuse. Dendrite will typically
# keep HTTP connections open to remote hosts for 5 minutes as they can be reused much
# more quickly than opening new connections each time. Disabling keepalives will close
# HTTP connections immediately after a successful request but may result in more CPU and
# memory being used on TLS handshakes for each new connection instead.
disable_http_keepalives: false
# Perspective keyservers to use as a backup when direct key fetches fail. This may
# be required to satisfy key requests for servers that are no longer online when
# joining some rooms.
key_perspectives:
- server_name: matrix.org
keys:
- key_id: ed25519:auto
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
- key_id: ed25519:a_RXGa
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
# This option will control whether Dendrite will prefer to look up keys directly
# or whether it should try perspective servers first, using direct fetches as a
# last resort.
prefer_direct_fetch: false
database:
connection_string: file:dendrite-federationapi.db
# Configuration for the Media API.
media_api:
# Storage path for uploaded media. May be relative or absolute.
base_path: ./media_store
# The maximum allowed file size (in bytes) for media uploads to this homeserver
# (0 = unlimited). If using a reverse proxy, ensure it allows requests at least
#this large (e.g. the client_max_body_size setting in nginx).
max_file_size_bytes: 10485760
# Whether to dynamically generate thumbnails if needed.
dynamic_thumbnails: false
# The maximum number of simultaneous thumbnail generators to run.
max_thumbnail_generators: 10
# A list of thumbnail sizes to be generated for media content.
thumbnail_sizes:
- width: 32
height: 32
method: crop
- width: 96
height: 96
method: crop
- width: 640
height: 480
method: scale
database:
connection_string: file:dendrite-mediaapi.db
# Configuration for enabling experimental MSCs on this homeserver.
mscs:
mscs:
# - msc2836 # (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836)
# - msc2946 # (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946)
database:
connection_string: file:dendrite-msc.db
# Configuration for the Sync API.
sync_api:
# This option controls which HTTP header to inspect to find the real remote IP
# address of the client. This is likely required if Dendrite is running behind
# a reverse proxy server.
# real_ip_header: X-Real-IP
# Configuration for the full-text search engine.
search:
# Whether or not search is enabled.
enabled: false
# The path where the search index will be created in.
index_path: "./searchindex"
# The language most likely to be used on the server - used when indexing, to
# ensure the returned results match expectations. A full list of possible languages
# can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
language: "en"
database:
connection_string: file:dendrite-syncapi.db
# Configuration for the User API.
user_api:
# The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31
# See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information.
# Setting this lower makes registration/login consume less CPU resources at the cost
# of security should the database be compromised. Setting this higher makes registration/login
# consume more CPU resources but makes it harder to brute force password hashes. This value
# can be lowered if performing tests or on embedded Dendrite instances (e.g WASM builds).
bcrypt_cost: 10
# The length of time that a token issued for a relying party from
# /_matrix/client/r0/user/{userId}/openid/request_token endpoint
# is considered to be valid in milliseconds.
# The default lifetime is 3600000ms (60 minutes).
# openid_token_lifetime_ms: 3600000
# Users who register on this homeserver will automatically be joined to the rooms listed under "auto_join_rooms" option.
# By default, any room aliases included in this list will be created as a publicly joinable room
# when the first user registers for the homeserver. If the room already exists,
# make certain it is a publicly joinable room, i.e. the join rule of the room must be set to 'public'.
# As Spaces are just rooms under the hood, Space aliases may also be used.
auto_join_rooms:
# - "#main:matrix.org"
account_database:
connection_string: file:dendrite-userapi.db
room_server:
database:
connection_string: file:dendrite-roomserverapi.db
key_server:
database:
connection_string: file:dendrite-keyserverapi.db
# Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up.
tracing:
enabled: false
jaeger:
serviceName: ""
disabled: false
rpc_metrics: false
tags: []
sampler: null
reporter: null
headers: null
baggage_restrictions: null
throttler: null
# Logging configuration. The "std" logging type controls the logs being sent to
# stdout. The "file" logging type controls logs being written to a log folder on
# the disk. Supported log levels are "debug", "info", "warn", "error".
logging:
- type: std
level: debug
- type: file
level: debug
params:
path: ./logs

View file

@ -30,7 +30,7 @@ export function dockerRun(opts: {
image: string;
containerName: string;
params?: string[];
cmd?: string;
cmd?: string[];
}): Promise<string> {
const userInfo = os.userInfo();
const params = opts.params ?? [];
@ -49,7 +49,7 @@ export function dockerRun(opts: {
opts.image,
];
if (opts.cmd) args.push(opts.cmd);
if (opts.cmd) args.push(...opts.cmd);
return new Promise<string>((resolve, reject) => {
childProcess.execFile("docker", args, (err, stdout) => {

View file

@ -19,6 +19,7 @@ limitations under the License.
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { synapseDocker } from "./synapsedocker";
import { dendriteDocker } from "./dendritedocker";
import { slidingSyncProxyDocker } from "./sliding-sync";
import { webserver } from "./webserver";
import { docker } from "./docker";
@ -30,6 +31,7 @@ import { log } from "./log";
export default function (on: PluginEvents, config: PluginConfigOptions) {
docker(on, config);
synapseDocker(on, config);
dendriteDocker(on, config);
slidingSyncProxyDocker(on, config);
webserver(on, config);
log(on, config);

View file

@ -20,7 +20,7 @@ import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { dockerExec, dockerIp, dockerRun, dockerStop } from "../docker";
import { getFreePort } from "../utils/port";
import { SynapseInstance } from "../synapsedocker";
import { HomeserverInstance } from "../utils/homeserver";
// A cypress plugin to add command to start & stop https://github.com/matrix-org/sliding-sync
// SLIDING_SYNC_PROXY_TAG env used as the docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
@ -35,7 +35,7 @@ const instances = new Map<string, ProxyInstance>();
const PG_PASSWORD = "p4S5w0rD";
async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<ProxyInstance> {
async function proxyStart(dockerTag: string, homeserver: HomeserverInstance): Promise<ProxyInstance> {
console.log(new Date(), "Starting sliding sync proxy...");
const postgresId = await dockerRun({
@ -45,7 +45,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
});
const postgresIp = await dockerIp({ containerId: postgresId });
const synapseIp = await dockerIp({ containerId: synapse.synapseId });
const homeserverIp = await dockerIp({ containerId: homeserver.serverId });
console.log(new Date(), "postgres container up");
const waitTimeMillis = 30000;
@ -81,7 +81,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
"-e",
"SYNCV3_SECRET=bwahahaha",
"-e",
`SYNCV3_SERVER=http://${synapseIp}:8008`,
`SYNCV3_SERVER=http://${homeserverIp}:8008`,
"-e",
`SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`,
],

View file

@ -25,29 +25,18 @@ import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { getFreePort } from "../utils/port";
import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
// A cypress plugins to add command to start & stop synapses in
// docker with preset templates.
interface SynapseConfig {
configDir: string;
registrationSecret: string;
// Synapse must be configured with its public_baseurl so we have to allocate a port & url at this stage
baseUrl: string;
port: number;
}
export interface SynapseInstance extends SynapseConfig {
synapseId: string;
}
const synapses = new Map<string, SynapseInstance>();
const synapses = new Map<string, HomeserverInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
async function cfgDirFromTemplate(template: string): Promise<HomeserverConfig> {
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
@ -94,7 +83,7 @@ async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
// Start a synapse instance: the template must be the name of
// one of the templates in the cypress/plugins/synapsedocker/templates
// directory
async function synapseStart(template: string): Promise<SynapseInstance> {
async function synapseStart(template: string): Promise<HomeserverInstance> {
const synCfg = await cfgDirFromTemplate(template);
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
@ -103,7 +92,7 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
image: "matrixdotorg/synapse:develop",
containerName: `react-sdk-cypress-synapse`,
params: ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`],
cmd: "run",
cmd: ["run"],
});
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
@ -125,7 +114,7 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
],
});
const synapse: SynapseInstance = { synapseId, ...synCfg };
const synapse: HomeserverInstance = { serverId: synapseId, ...synCfg };
synapses.set(synapseId, synapse);
return synapse;
}

View file

@ -0,0 +1,28 @@
/*
Copyright 2023 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" />
export interface HomeserverConfig {
configDir: string;
registrationSecret: string;
baseUrl: string;
port: number;
}
export interface HomeserverInstance extends HomeserverConfig {
serverId: string;
}

View file

@ -17,8 +17,8 @@ limitations under the License.
/// <reference types="cypress" />
import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../plugins/synapsedocker";
import { Credentials } from "./synapse";
import { HomeserverInstance } from "../plugins/utils/homeserver";
import { Credentials } from "./homeserver";
import Chainable = Cypress.Chainable;
interface CreateBotOpts {
@ -61,19 +61,19 @@ declare global {
interface Chainable {
/**
* Returns a new Bot instance
* @param synapse the instance on which to register the bot user
* @param homeserver the instance on which to register the bot user
* @param opts create bot options
*/
getBot(synapse: SynapseInstance, opts: CreateBotOpts): Chainable<CypressBot>;
getBot(homeserver: HomeserverInstance, opts: CreateBotOpts): Chainable<CypressBot>;
/**
* Returns a new Bot instance logged in as an existing user
* @param synapse the instance on which to register the bot user
* @param homeserver the instance on which to register the bot user
* @param username the username for the bot to log in with
* @param password the password for the bot to log in with
* @param opts create bot options
*/
loginBot(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
opts: CreateBotOpts,
@ -102,7 +102,7 @@ declare global {
}
function setupBotClient(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
credentials: Credentials,
opts: CreateBotOpts,
): Chainable<MatrixClient> {
@ -119,7 +119,7 @@ function setupBotClient(
};
const cli = new win.matrixcs.MatrixClient({
baseUrl: synapse.baseUrl,
baseUrl: homeserver.baseUrl,
userId: credentials.userId,
deviceId: credentials.deviceId,
accessToken: credentials.accessToken,
@ -160,15 +160,15 @@ function setupBotClient(
});
}
Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts): Chainable<CypressBot> => {
Cypress.Commands.add("getBot", (homeserver: HomeserverInstance, opts: CreateBotOpts): Chainable<CypressBot> => {
opts = Object.assign({}, defaultCreateBotOptions, opts);
const username = Cypress._.uniqueId(opts.userIdPrefix);
const password = Cypress._.uniqueId("password_");
return cy
.registerUser(synapse, username, password, opts.displayName)
.registerUser(homeserver, username, password, opts.displayName)
.then((credentials) => {
cy.log(`Registered bot user ${username} with displayname ${opts.displayName}`);
return setupBotClient(synapse, credentials, opts);
return setupBotClient(homeserver, credentials, opts);
})
.then((client): Chainable<CypressBot> => {
Object.assign(client, { __cypress_password: password });
@ -178,10 +178,15 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
Cypress.Commands.add(
"loginBot",
(synapse: SynapseInstance, username: string, password: string, opts: CreateBotOpts): Chainable<MatrixClient> => {
(
homeserver: HomeserverInstance,
username: string,
password: string,
opts: CreateBotOpts,
): Chainable<MatrixClient> => {
opts = Object.assign({}, defaultCreateBotOptions, { bootstrapCrossSigning: false }, opts);
return cy.loginUser(synapse, username, password).then((credentials) => {
return setupBotClient(synapse, credentials, opts);
return cy.loginUser(homeserver, username, password).then((credentials) => {
return setupBotClient(homeserver, credentials, opts);
});
},
);

View file

@ -19,7 +19,7 @@ limitations under the License.
import "@percy/cypress";
import "cypress-real-events";
import "./synapse";
import "./homeserver";
import "./login";
import "./labs";
import "./client";

View file

@ -1,5 +1,5 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Copyright 2023 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.
@ -20,34 +20,34 @@ import * as crypto from "crypto";
import Chainable = Cypress.Chainable;
import AUTWindow = Cypress.AUTWindow;
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Start a synapse instance with a given config template.
* @param template path to template within cypress/plugins/synapsedocker/template/ directory.
* Start a homeserver instance with a given config template.
* @param template path to template within cypress/plugins/{homeserver}docker/template/ directory.
*/
startSynapse(template: string): Chainable<SynapseInstance>;
startHomeserver(template: string): Chainable<HomeserverInstance>;
/**
* Custom command wrapping task:synapseStop whilst preventing uncaught exceptions
* for if Synapse stopping races with the app's background sync loop.
* @param synapse the synapse instance returned by startSynapse
* Custom command wrapping task:{homeserver}Stop whilst preventing uncaught exceptions
* for if Homeserver stopping races with the app's background sync loop.
* @param homeserver the homeserver instance returned by start{Homeserver}
*/
stopSynapse(synapse: SynapseInstance): Chainable<AUTWindow>;
stopHomeserver(homeserver: HomeserverInstance): Chainable<AUTWindow>;
/**
* Register a user on the given Synapse using the shared registration secret.
* @param synapse the synapse instance returned by startSynapse
* Register a user on the given Homeserver using the shared registration secret.
* @param homeserver the homeserver instance returned by start{Homeserver}
* @param username the username of the user to register
* @param password the password of the user to register
* @param displayName optional display name to set on the newly registered user
*/
registerUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
displayName?: string,
@ -56,16 +56,18 @@ declare global {
}
}
function startSynapse(template: string): Chainable<SynapseInstance> {
return cy.task<SynapseInstance>("synapseStart", template);
function startHomeserver(template: string): Chainable<HomeserverInstance> {
const homeserverName = Cypress.env("HOMESERVER");
return cy.task<HomeserverInstance>(homeserverName + "Start", template);
}
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
function stopHomeserver(homeserver?: HomeserverInstance): Chainable<AUTWindow> {
if (!homeserver) return;
// Navigate away from app to stop the background network requests which will race with Homeserver shutting down
return cy.window({ log: false }).then((win) => {
win.location.href = "about:blank";
cy.task("synapseStop", synapse.synapseId);
const homeserverName = Cypress.env("HOMESERVER");
cy.task(homeserverName + "Stop", homeserver.serverId);
});
}
@ -77,12 +79,12 @@ export interface Credentials {
}
function registerUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
displayName?: string,
): Chainable<Credentials> {
const url = `${synapse.baseUrl}/_synapse/admin/v1/register`;
const url = `${homeserver.baseUrl}/_synapse/admin/v1/register`;
return cy
.then(() => {
// get a nonce
@ -91,7 +93,7 @@ function registerUser(
.then((response) => {
const { nonce } = response.body;
const mac = crypto
.createHmac("sha1", synapse.registrationSecret)
.createHmac("sha1", homeserver.registrationSecret)
.update(`${nonce}\0${username}\0${password}\0notadmin`)
.digest("hex");
@ -121,6 +123,6 @@ function registerUser(
}));
}
Cypress.Commands.add("startSynapse", startSynapse);
Cypress.Commands.add("stopSynapse", stopSynapse);
Cypress.Commands.add("startHomeserver", startHomeserver);
Cypress.Commands.add("stopHomeserver", stopHomeserver);
Cypress.Commands.add("registerUser", registerUser);

View file

@ -17,7 +17,7 @@ limitations under the License.
/// <reference types="cypress" />
import Chainable = Cypress.Chainable;
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
export interface UserCredentials {
accessToken: string;
@ -41,7 +41,7 @@ declare global {
* useed.
*/
initTestUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
displayName: string,
prelaunchFn?: () => void,
userIdPrefix?: string,
@ -52,7 +52,7 @@ declare global {
* @param username login username
* @param password login password
*/
loginUser(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials>;
loginUser(synapse: HomeserverInstance, username: string, password: string): Chainable<UserCredentials>;
}
}
}
@ -60,8 +60,8 @@ declare global {
// eslint-disable-next-line max-len
Cypress.Commands.add(
"loginUser",
(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials> => {
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
(homeserver: HomeserverInstance, username: string, password: string): Chainable<UserCredentials> => {
const url = `${homeserver.baseUrl}/_matrix/client/r0/login`;
return cy
.request<{
access_token: string;
@ -95,7 +95,7 @@ Cypress.Commands.add(
Cypress.Commands.add(
"initTestUser",
(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
displayName: string,
prelaunchFn?: () => void,
userIdPrefix = "user_",
@ -112,15 +112,15 @@ Cypress.Commands.add(
const username = Cypress._.uniqueId(userIdPrefix);
const password = Cypress._.uniqueId("password_");
return cy
.registerUser(synapse, username, password, displayName)
.registerUser(homeserver, username, password, displayName)
.then(() => {
return cy.loginUser(synapse, username, password);
return cy.loginUser(homeserver, username, password);
})
.then((response) => {
cy.log(`Registered test user ${username} with displayname ${displayName}`);
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_hs_url", homeserver.baseUrl);
win.localStorage.setItem("mx_user_id", response.userId);
win.localStorage.setItem("mx_access_token", response.accessToken);
win.localStorage.setItem("mx_device_id", response.deviceId);

View file

@ -19,7 +19,7 @@ limitations under the License.
import Chainable = Cypress.Chainable;
import AUTWindow = Cypress.AUTWindow;
import { ProxyInstance } from "../plugins/sliding-sync";
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@ -27,9 +27,9 @@ declare global {
interface Chainable {
/**
* Start a sliding sync proxy instance.
* @param synapse the synapse instance returned by startSynapse
* @param homeserver the homeserver instance returned by startHomeserver
*/
startProxy(synapse: SynapseInstance): Chainable<ProxyInstance>;
startProxy(homeserver: HomeserverInstance): Chainable<ProxyInstance>;
/**
* Custom command wrapping task:proxyStop whilst preventing uncaught exceptions
@ -41,13 +41,13 @@ declare global {
}
}
function startProxy(synapse: SynapseInstance): Chainable<ProxyInstance> {
return cy.task<ProxyInstance>("proxyStart", synapse);
function startProxy(homeserver: HomeserverInstance): Chainable<ProxyInstance> {
return cy.task<ProxyInstance>("proxyStart", homeserver);
}
function stopProxy(proxy?: ProxyInstance): Chainable<AUTWindow> {
if (!proxy) return;
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
// Navigate away from app to stop the background network requests which will race with Homeserver shutting down
return cy.window({ log: false }).then((win) => {
win.location.href = "about:blank";
cy.task("proxyStop", proxy);

View file

@ -21,7 +21,7 @@ be tested. When running Cypress tests yourself, the standard `yarn start` from t
element-web project is fine: leave it running it a different terminal as you would
when developing.
The tests use Docker to launch Synapse instances to test against, so you'll also
The tests use Docker to launch Homeserver (Synapse or Dendrite) instances to test against, so you'll also
need to have Docker installed and working in order to run the Cypress tests.
There are a few different ways to run the tests yourself. The simplest is to run:
@ -58,10 +58,10 @@ Synapse can be launched with different configurations in order to test element
in different configurations. `cypress/plugins/synapsedocker/templates` contains
template configuration files for each different configuration.
Each test suite can then launch whatever Synapse instances it needs it whatever
Each test suite can then launch whatever Synapse instances it needs in whatever
configurations.
Note that although tests should stop the Synapse instances after running and the
Note that although tests should stop the Homeserver instances after running and the
plugin also stop any remaining instances after all tests have run, it is possible
to be left with some stray containers if, for example, you terminate a test such
that the `after()` does not run and also exit Cypress uncleanly. All the containers
@ -82,29 +82,29 @@ a read.
### Getting a Synapse
The key difference is in starting Synapse instances. Tests use this plugin via
`cy.startSynapse()` to provide a Synapse instance to log into:
`cy.startHomeserver()` to provide a Homeserver instance to log into:
```javascript
cy.startSynapse("consent").then((result) => {
synapse = result;
cy.startHomeserver("consent").then((result) => {
homeserver = result;
});
```
This returns an object with information about the Synapse instance, including what port
This returns an object with information about the Homeserver instance, including what port
it was started on and the ID that needs to be passed to shut it down again. It also
returns the registration shared secret (`registrationSecret`) that can be used to
register users via the REST API. The Synapse has been ensured ready to go by awaiting
register users via the REST API. The Homeserver has been ensured ready to go by awaiting
its internal health-check.
Synapse instances should be reasonably cheap to start (you may see the first one take a
Homeserver instances should be reasonably cheap to start (you may see the first one take a
while as it pulls the Docker image), so it's generally expected that tests will start a
Synapse instance for each test suite, i.e. in `before()`, and then tear it down in `after()`.
Homeserver instance for each test suite, i.e. in `before()`, and then tear it down in `after()`.
To later destroy your Synapse you should call `stopSynapse`, passing the SynapseInstance
To later destroy your Homeserver you should call `stopHomeserver`, passing the HomeserverInstance
object you received when starting it.
```javascript
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
```
### Synapse Config Templates
@ -131,10 +131,10 @@ in a template can be referenced in the config as `/data/foo.html`.
There exists a basic utility to start the app with a random user already logged in:
```javascript
cy.initTestUser(synapse, "Jeff");
cy.initTestUser(homeserver, "Jeff");
```
It takes the SynapseInstance you received from `startSynapse` and a display name for your test user.
It takes the HomeserverInstance you received from `startHomeserver` and a display name for your test user.
This custom command will register a random userId using the registrationSecret with a random password
and the given display name. The returned Chainable will contain details about the credentials for if
they are needed for User-Interactive Auth or similar but localStorage will already be seeded with them
@ -147,11 +147,11 @@ but the signature can be maintained for simpler maintenance.
Many tests will also want to start with the client in a room, ready to send & receive messages. Best
way to do this may be to get an access token for the user and use this to create a room with the REST
API before logging the user in. You can make use of `cy.getBot(synapse)` and `cy.getClient()` to do this.
API before logging the user in. You can make use of `cy.getBot(homeserver)` and `cy.getClient()` to do this.
### Convenience APIs
We should probably end up with convenience APIs that wrap the synapse creation, logging in and room
We should probably end up with convenience APIs that wrap the homeserver creation, logging in and room
creation that can be called to set up tests.
### Using matrix-js-sdk