diff --git a/cypress/e2e/settings/device-management.spec.ts b/cypress/e2e/settings/device-management.spec.ts
new file mode 100644
index 0000000000..c3ef4db838
--- /dev/null
+++ b/cypress/e2e/settings/device-management.spec.ts
@@ -0,0 +1,117 @@
+/*
+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 { SynapseInstance } from "../../plugins/synapsedocker";
+import type { UserCredentials } from "../../support/login";
+
+describe("Device manager", () => {
+ let synapse: SynapseInstance | undefined;
+ let user: UserCredentials | undefined;
+
+ beforeEach(() => {
+ cy.enableLabsFeature("feature_new_device_manager");
+ cy.startSynapse("default").then(data => {
+ synapse = data;
+
+ cy.initTestUser(synapse, "Alice").then(credentials => {
+ user = credentials;
+ }).then(() => {
+ // create some extra sessions to manage
+ return cy.loginUser(synapse, user.username, user.password);
+ }).then(() => {
+ return cy.loginUser(synapse, user.username, user.password);
+ });
+ });
+ });
+
+ afterEach(() => {
+ cy.stopSynapse(synapse!);
+ });
+
+ it("should display sessions", () => {
+ cy.openUserSettings("Sessions");
+ cy.contains('Current session').should('exist');
+
+ cy.get('[data-testid="current-session-section"]').within(() => {
+ cy.contains('Unverified session').should('exist');
+ cy.get('.mx_DeviceSecurityCard_actions [role="button"]').should('exist');
+ });
+
+ // current session details opened
+ cy.get('[data-testid="current-session-toggle-details"]').click();
+ cy.contains('Session details').should('exist');
+
+ // close current session details
+ cy.get('[data-testid="current-session-toggle-details"]').click();
+ cy.contains('Session details').should('not.exist');
+
+ cy.get('[data-testid="security-recommendations-section"]').within(() => {
+ cy.contains('Security recommendations').should('exist');
+ cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (3)').click();
+ });
+
+ /**
+ * Other sessions section
+ */
+ cy.contains('Other sessions').should('exist');
+ // filter applied after clicking through from security recommendations
+ cy.get('[aria-label="Filter devices"]').should('have.text', 'Show: Unverified');
+ cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 3);
+
+ // select two sessions
+ cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem').first().click();
+ cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem').last().click();
+ // sign out from list selection action buttons
+ cy.get('[data-testid="sign-out-selection-cta"]').click();
+ // list updated after sign out
+ cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1);
+ // security recommendation count updated
+ cy.get('[data-testid="unverified-devices-cta"]').should('have.text', 'View all (1)');
+
+ const sessionName = `Alice's device`;
+ // select the first session
+ cy.get('.mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem').first().within(() => {
+ cy.get('[aria-label="Toggle device details"]').click();
+
+ cy.contains('Session details').should('exist');
+
+ cy.get('[data-testid="device-heading-rename-cta"]').click();
+ cy.get('[data-testid="device-rename-input"]').type(sessionName);
+ cy.get('[data-testid="device-rename-submit-cta"]').click();
+ // there should be a spinner while device updates
+ cy.get(".mx_Spinner").should("exist");
+ // wait for spinner to complete
+ cy.get(".mx_Spinner").should("not.exist");
+
+ // session name updated in details
+ cy.get('.mx_DeviceDetailHeading h3').should('have.text', sessionName);
+ // and main list item
+ cy.get('.mx_DeviceTile h4').should('have.text', sessionName);
+
+ // sign out using the device details sign out
+ cy.get('[data-testid="device-detail-sign-out-cta"]').click();
+ });
+
+ // list updated after sign out
+ cy.get('.mx_FilteredDeviceList_list').find('.mx_FilteredDeviceList_listItem').should('have.length', 1);
+
+ // no other sessions or security recommendations sections when only one session
+ cy.contains('Other sessions').should('not.exist');
+ cy.get('[data-testid="security-recommendations-section"]').should('not.exist');
+ });
+});
diff --git a/cypress/support/login.ts b/cypress/support/login.ts
index 46b1b7a89f..e44be78123 100644
--- a/cypress/support/login.ts
+++ b/cypress/support/login.ts
@@ -21,6 +21,7 @@ import { SynapseInstance } from "../plugins/synapsedocker";
export interface UserCredentials {
accessToken: string;
+ username: string;
userId: string;
deviceId: string;
password: string;
@@ -42,26 +43,25 @@ declare global {
displayName: string,
prelaunchFn?: () => void,
): Chainable;
+ /**
+ * Logs into synapse with the given username/password
+ * @param synapse the synapse returned by startSynapse
+ * @param username login username
+ * @param password login password
+ */
+ loginUser(
+ synapse: SynapseInstance,
+ username: string,
+ password: string,
+ ): Chainable;
}
}
}
// eslint-disable-next-line max-len
-Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable => {
- // XXX: work around Cypress not clearing IDB between tests
- cy.window({ log: false }).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(() => {
- const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
- return cy.request<{
+Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, password: string): Chainable => {
+ const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
+ return cy.request<{
access_token: string;
user_id: string;
device_id: string;
@@ -77,14 +77,38 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
},
"password": password,
},
+ }).then(response => ({
+ password,
+ username,
+ accessToken: response.body.access_token,
+ userId: response.body.user_id,
+ deviceId: response.body.device_id,
+ homeServer: response.body.home_server,
+ }));
+});
+
+// eslint-disable-next-line max-len
+Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable => {
+ // XXX: work around Cypress not clearing IDB between tests
+ cy.window({ log: false }).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(() => {
+ return cy.loginUser(synapse, username, password);
}).then(response => {
cy.window({ log: false }).then(win => {
// Seed the localStorage with the required credentials
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
- win.localStorage.setItem("mx_user_id", response.body.user_id);
- win.localStorage.setItem("mx_access_token", response.body.access_token);
- win.localStorage.setItem("mx_device_id", response.body.device_id);
+ win.localStorage.setItem("mx_user_id", response.userId);
+ win.localStorage.setItem("mx_access_token", response.accessToken);
+ win.localStorage.setItem("mx_device_id", response.deviceId);
win.localStorage.setItem("mx_is_guest", "false");
win.localStorage.setItem("mx_has_pickle_key", "false");
win.localStorage.setItem("mx_has_access_token", "true");
@@ -100,10 +124,11 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
return cy.get(".mx_MatrixChat", { timeout: 30000 });
}).then(() => ({
password,
- accessToken: response.body.access_token,
- userId: response.body.user_id,
- deviceId: response.body.device_id,
- homeServer: response.body.home_server,
+ username,
+ accessToken: response.accessToken,
+ userId: response.userId,
+ deviceId: response.deviceId,
+ homeServer: response.homeServer,
}));
});
});