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, })); }); });