diff --git a/cypress/e2e/settings/appearance-user-settings-tab.spec.ts b/cypress/e2e/settings/appearance-user-settings-tab.spec.ts
deleted file mode 100644
index 46de23f591..0000000000
--- a/cypress/e2e/settings/appearance-user-settings-tab.spec.ts
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-import { SettingLevel } from "../../../src/settings/SettingLevel";
-
-describe("Appearance user settings tab", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, "Hanako");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should be rendered properly", () => {
- cy.openUserSettings("Appearance");
-
- cy.findByTestId("mx_AppearanceUserSettingsTab").within(() => {
- cy.get("h2").should("have.text", "Customise your appearance").should("be.visible");
- });
-
- cy.findByTestId("mx_AppearanceUserSettingsTab").percySnapshotElement(
- "User settings tab - Appearance (advanced options collapsed)",
- {
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- },
- );
-
- // Click "Show advanced" link button
- cy.findByRole("button", { name: "Show advanced" }).click();
-
- // Assert that "Hide advanced" link button is rendered
- cy.findByRole("button", { name: "Hide advanced" }).should("exist");
-
- cy.findByTestId("mx_AppearanceUserSettingsTab").percySnapshotElement(
- "User settings tab - Appearance (advanced options expanded)",
- {
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- },
- );
- });
-
- it("should support switching layouts", () => {
- // Create and view a room first
- cy.createRoom({ name: "Test Room" }).viewRoomByName("Test Room");
-
- cy.openUserSettings("Appearance");
-
- cy.get(".mx_LayoutSwitcher_RadioButtons").within(() => {
- // Assert that the layout selected by default is "Modern"
- cy.get(".mx_LayoutSwitcher_RadioButton_selected .mx_StyledRadioButton_enabled").within(() => {
- cy.findByLabelText("Modern").should("exist");
- });
- });
-
- // Assert that the room layout is set to group (modern) layout
- cy.get(".mx_RoomView_body[data-layout='group']").should("exist");
-
- cy.get(".mx_LayoutSwitcher_RadioButtons").within(() => {
- // Select the first layout
- cy.get(".mx_LayoutSwitcher_RadioButton").first().click();
-
- // Assert that the layout selected is "IRC (Experimental)"
- cy.get(".mx_LayoutSwitcher_RadioButton_selected .mx_StyledRadioButton_enabled").within(() => {
- cy.findByLabelText("IRC (Experimental)").should("exist");
- });
- });
-
- // Assert that the room layout is set to IRC layout
- cy.get(".mx_RoomView_body[data-layout='irc']").should("exist");
-
- cy.get(".mx_LayoutSwitcher_RadioButtons").within(() => {
- // Select the last layout
- cy.get(".mx_LayoutSwitcher_RadioButton").last().click();
-
- // Assert that the layout selected is "Message bubbles"
- cy.get(".mx_LayoutSwitcher_RadioButton_selected .mx_StyledRadioButton_enabled").within(() => {
- cy.findByLabelText("Message bubbles").should("exist");
- });
- });
-
- // Assert that the room layout is set to bubble layout
- cy.get(".mx_RoomView_body[data-layout='bubble']").should("exist");
- });
-
- it("should support changing font size by clicking the font slider", () => {
- cy.openUserSettings("Appearance");
-
- cy.findByTestId("mx_AppearanceUserSettingsTab").within(() => {
- cy.get(".mx_FontScalingPanel_fontSlider").within(() => {
- cy.findByLabelText("Font size").should("exist");
- });
-
- cy.get(".mx_FontScalingPanel_fontSlider").within(() => {
- // Click the left position of the slider
- cy.get("input").realClick({ position: "left" });
-
- const MIN_FONT_SIZE = 11;
- // Assert that the smallest font size is selected
- cy.get(`input[value='${MIN_FONT_SIZE}']`).should("exist");
- cy.get("output .mx_Slider_selection_label").findByText(MIN_FONT_SIZE);
- });
-
- cy.get(".mx_FontScalingPanel_fontSlider").percySnapshotElement("Font size slider - smallest (13)", {
- widths: [486], // actual size (content-box, including inline padding)
- });
-
- cy.get(".mx_FontScalingPanel_fontSlider").within(() => {
- // Click the right position of the slider
- cy.get("input").realClick({ position: "right" });
-
- const MAX_FONT_SIZE = 21;
- // Assert that the largest font size is selected
- cy.get(`input[value='${MAX_FONT_SIZE}']`).should("exist");
- cy.get("output .mx_Slider_selection_label").findByText(MAX_FONT_SIZE);
- });
-
- cy.get(".mx_FontScalingPanel_fontSlider").percySnapshotElement("Font size slider - largest (21)", {
- widths: [486],
- });
- });
- });
-
- it("should disable font size slider when custom font size is used", () => {
- cy.openUserSettings("Appearance");
-
- cy.findByTestId("mx_FontScalingPanel").within(() => {
- cy.findByLabelText("Use custom size").click({ force: true }); // force click as checkbox size is zero
-
- // Assert that the font slider is disabled
- cy.get(".mx_FontScalingPanel_fontSlider input[disabled]").should("exist");
- });
- });
-
- it("should support enabling compact group (modern) layout", () => {
- // Create and view a room first
- cy.createRoom({ name: "Test Room" }).viewRoomByName("Test Room");
-
- cy.openUserSettings("Appearance");
-
- // Click "Show advanced" link button
- cy.findByRole("button", { name: "Show advanced" }).click();
-
- // force click as checkbox size is zero
- cy.findByLabelText("Use a more compact 'Modern' layout").click({ force: true });
-
- // Assert that the room layout is set to compact group (modern) layout
- cy.get("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout").should("exist");
- });
-
- it("should disable compact group (modern) layout option on IRC layout and bubble layout", () => {
- const checkDisabled = () => {
- cy.findByLabelText("Use a more compact 'Modern' layout").should("be.disabled");
- };
-
- cy.openUserSettings("Appearance");
-
- // Click "Show advanced" link button
- cy.findByRole("button", { name: "Show advanced" }).click();
-
- // Enable IRC layout
- cy.get(".mx_LayoutSwitcher_RadioButtons").within(() => {
- // Select the first layout
- cy.get(".mx_LayoutSwitcher_RadioButton").first().click();
-
- // Assert that the layout selected is "IRC (Experimental)"
- cy.get(".mx_LayoutSwitcher_RadioButton_selected .mx_StyledRadioButton_enabled").within(() => {
- cy.findByLabelText("IRC (Experimental)").should("exist");
- });
- });
-
- checkDisabled();
-
- // Enable bubble layout
- cy.get(".mx_LayoutSwitcher_RadioButtons").within(() => {
- // Select the first layout
- cy.get(".mx_LayoutSwitcher_RadioButton").last().click();
-
- // Assert that the layout selected is "IRC (Experimental)"
- cy.get(".mx_LayoutSwitcher_RadioButton_selected .mx_StyledRadioButton_enabled").within(() => {
- cy.findByLabelText("Message bubbles").should("exist");
- });
- });
-
- checkDisabled();
- });
-
- it("should support enabling system font", () => {
- cy.openUserSettings("Appearance");
-
- // Click "Show advanced" link button
- cy.findByRole("button", { name: "Show advanced" }).click();
-
- // force click as checkbox size is zero
- cy.findByLabelText("Use bundled emoji font").click({ force: true });
- cy.findByLabelText("Use a system font").click({ force: true });
-
- // Assert that the font-family value was removed
- cy.get("body").should("have.css", "font-family", '""');
- });
-
- describe("Theme Choice Panel", () => {
- beforeEach(() => {
- // Disable the default theme for consistency in case ThemeWatcher automatically chooses it
- cy.setSettingValue("use_system_theme", null, SettingLevel.DEVICE, false);
- });
-
- it("should be rendered with the light theme selected", () => {
- cy.openUserSettings("Appearance")
- .findByTestId("mx_ThemeChoicePanel")
- .within(() => {
- cy.findByTestId("checkbox-use-system-theme").within(() => {
- cy.findByText("Match system theme").should("be.visible");
-
- // Assert that 'Match system theme' is not checked
- // Note that mx_Checkbox_checkmark exists and is hidden by CSS if it is not checked
- cy.get(".mx_Checkbox_checkmark").should("not.be.visible");
- });
-
- cy.findByTestId("theme-choice-panel-selectors").within(() => {
- cy.get(".mx_ThemeSelector_light").should("exist");
- cy.get(".mx_ThemeSelector_dark").should("exist");
-
- // Assert that the light theme is selected
- cy.get(".mx_ThemeSelector_light.mx_StyledRadioButton_enabled").should("exist");
-
- // Assert that the buttons for the light and dark theme are not enabled
- cy.get(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled").should("not.exist");
- cy.get(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled").should("not.exist");
- });
-
- // Assert that the checkbox for the high contrast theme is rendered
- cy.findByLabelText("Use high contrast").should("exist");
- });
- });
-
- it(
- "should disable the labels for themes and the checkbox for the high contrast theme if the checkbox for " +
- "the system theme is clicked",
- () => {
- cy.openUserSettings("Appearance")
- .findByTestId("mx_ThemeChoicePanel")
- .findByLabelText("Match system theme")
- .click({ force: true }); // force click because the size of the checkbox is zero
-
- cy.findByTestId("mx_ThemeChoicePanel").within(() => {
- // Assert that the labels for the light theme and dark theme are disabled
- cy.get(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled").should("exist");
- cy.get(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled").should("exist");
-
- // Assert that there does not exist a label for an enabled theme
- cy.get("label.mx_StyledRadioButton_enabled").should("not.exist");
-
- // Assert that the checkbox and label to enable the the high contrast theme should not exist
- cy.findByLabelText("Use high contrast").should("not.exist");
- });
- },
- );
-
- it(
- "should not render the checkbox and the label for the high contrast theme " +
- "if the dark theme is selected",
- () => {
- cy.openUserSettings("Appearance");
-
- // Assert that the checkbox and the label to enable the high contrast theme should exist
- cy.findByLabelText("Use high contrast").should("exist");
-
- // Enable the dark theme
- cy.get(".mx_ThemeSelector_dark").click();
-
- // Assert that the checkbox and the label should not exist
- cy.findByLabelText("Use high contrast").should("not.exist");
- },
- );
- });
-});
diff --git a/cypress/e2e/settings/device-management.spec.ts b/cypress/e2e/settings/device-management.spec.ts
deleted file mode 100644
index 06795b68be..0000000000
--- a/cypress/e2e/settings/device-management.spec.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-import type { UserCredentials } from "../../support/login";
-
-describe("Device manager", () => {
- let homeserver: HomeserverInstance | undefined;
- let user: UserCredentials | undefined;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
-
- cy.initTestUser(homeserver, "Alice")
- .then((credentials) => {
- user = credentials;
- })
- .then(() => {
- // create some extra sessions to manage
- return cy.loginUser(homeserver, user.username, user.password);
- })
- .then(() => {
- return cy.loginUser(homeserver, user.username, user.password);
- });
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver!);
- });
-
- it("should display sessions", () => {
- cy.openUserSettings("Sessions");
- cy.findByText("Current session").should("exist");
-
- cy.findByTestId("current-session-section").within(() => {
- cy.findByText("Unverified session").should("exist");
-
- // current session details opened
- cy.findByRole("button", { name: "Show details" }).click();
- cy.findByText("Session details").should("exist");
-
- // close current session details
- cy.findByRole("button", { name: "Hide details" }).click();
- cy.findByText("Session details").should("not.exist");
- });
-
- cy.findByTestId("security-recommendations-section").within(() => {
- cy.findByText("Security recommendations").should("exist");
- cy.findByRole("button", { name: "View all (3)" }).click();
- });
-
- /**
- * Other sessions section
- */
- cy.findByText("Other sessions").should("exist");
- // filter applied after clicking through from security recommendations
- cy.findByLabelText("Filter devices").should("have.text", "Show: Unverified");
- cy.get(".mx_FilteredDeviceList_list").within(() => {
- cy.get(".mx_FilteredDeviceList_listItem").should("have.length", 3);
-
- // select two sessions
- cy.get(".mx_FilteredDeviceList_listItem")
- .first()
- .within(() => {
- // force click as the input element itself is not visible (its size is zero)
- cy.findByRole("checkbox").click({ force: true });
- });
- cy.get(".mx_FilteredDeviceList_listItem")
- .last()
- .within(() => {
- // force click as the input element itself is not visible (its size is zero)
- cy.findByRole("checkbox").click({ force: true });
- });
- });
- // sign out from list selection action buttons
- cy.findByRole("button", { name: "Sign out" }).click();
- cy.get(".mx_Dialog .mx_QuestionDialog").within(() => {
- cy.findByRole("button", { name: "Sign out" }).click();
- });
- // list updated after sign out
- cy.get(".mx_FilteredDeviceList_list").find(".mx_FilteredDeviceList_listItem").should("have.length", 1);
- // security recommendation count updated
- cy.findByRole("button", { name: "View all (1)" });
-
- const sessionName = `Alice's device`;
- // open the first session
- cy.get(".mx_FilteredDeviceList_list .mx_FilteredDeviceList_listItem")
- .first()
- .within(() => {
- cy.findByRole("button", { name: "Show details" }).click();
-
- cy.findByText("Session details").should("exist");
-
- cy.findByRole("button", { name: "Rename" }).click();
- cy.findByTestId("device-rename-input").type(sessionName);
- cy.findByRole("button", { name: "Save" }).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 h4").within(() => {
- cy.findByText(sessionName);
- });
- // and main list item
- cy.get(".mx_DeviceTile h4").within(() => {
- cy.findByText(sessionName);
- });
-
- // sign out using the device details sign out
- cy.findByRole("button", { name: "Sign out of this session" }).click();
- });
- // confirm the signout
- cy.get(".mx_Dialog .mx_QuestionDialog").within(() => {
- cy.findByRole("button", { name: "Sign out" }).click();
- });
-
- // no other sessions or security recommendations sections when only one session
- cy.findByText("Other sessions").should("not.exist");
- cy.findByTestId("security-recommendations-section").should("not.exist");
- });
-});
diff --git a/cypress/e2e/settings/general-room-settings-tab.spec.ts b/cypress/e2e/settings/general-room-settings-tab.spec.ts
deleted file mode 100644
index 864b57edf6..0000000000
--- a/cypress/e2e/settings/general-room-settings-tab.spec.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-describe("General room settings tab", () => {
- let homeserver: HomeserverInstance;
- const roomName = "Test Room";
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, "Hanako");
-
- cy.createRoom({ name: roomName }).viewRoomByName(roomName);
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should be rendered properly", () => {
- cy.openRoomSettings("General");
-
- // Assert that "Show less" details element is rendered
- cy.findByText("Show less").should("exist");
-
- cy.findByTestId("General").percySnapshotElement(
- "Room settings tab - General (Local addresses details area expanded)",
- {
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- },
- );
-
- // Click the "Show less" details element
- cy.findByText("Show less").click();
-
- // Assert that "Show more" details element is rendered instead of "Show more"
- cy.findByText("Show less").should("not.exist");
- cy.findByText("Show more").should("exist");
-
- cy.findByTestId("General").percySnapshotElement(
- "Room settings tab - General (Local addresses details area collapsed)",
- {
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- },
- );
- });
-
- it("long address should not cause dialog to overflow", () => {
- cy.openRoomSettings("General");
- // 1. Set the room-address to be a really long string
- const longString =
- "abcasdhjasjhdaj1jh1asdhasjdhajsdhjavhjksdnfjasdhfjh21jh3j12h3jashfcjbabbabasdbdasjh1j23hk1l2j3lamajshdjkltyiuwioeuqpirjdfmngsdnf8378234jskdfjkdfnbnsdfbasjbdjashdajshfgngnsdkfsdkkqwijeqiwjeiqhrkldfnaskldklasdn";
- cy.get("#roomAliases").within(() => {
- cy.get("input[label='Room address']").type(longString);
- cy.contains("Add").click();
- });
-
- // 2. wait for the new setting to apply ...
- cy.get("#canonicalAlias").should("have.value", `#${longString}:localhost`);
-
- // 3. Check if the dialog overflows
- cy.get(".mx_Dialog")
- .invoke("outerWidth")
- .then((dialogWidth) => {
- cy.get("#canonicalAlias")
- .invoke("outerWidth")
- .then((fieldWidth) => {
- // Assert that the width of the select element is less than that of .mx_Dialog div.
- expect(fieldWidth).to.be.lessThan(dialogWidth);
- });
- });
- });
-});
diff --git a/cypress/e2e/settings/general-user-settings-tab.spec.ts b/cypress/e2e/settings/general-user-settings-tab.spec.ts
deleted file mode 100644
index 725caf2038..0000000000
--- a/cypress/e2e/settings/general-user-settings-tab.spec.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-const USER_NAME = "Bob";
-const USER_NAME_NEW = "Alice";
-const IntegrationManager = "scalar.vector.im";
-
-describe("General user settings tab", () => {
- let homeserver: HomeserverInstance;
- let userId: string;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, USER_NAME).then((user) => (userId = user.userId));
- cy.tweakConfig({ default_country_code: "US" }); // For checking the international country calling code
- });
- cy.openUserSettings("General");
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should be rendered properly", () => {
- // Exclude userId from snapshots
- const percyCSS = ".mx_ProfileSettings_profile_controls_userId { visibility: hidden !important; }";
-
- cy.findByTestId("mx_GeneralUserSettingsTab").percySnapshotElement("User settings tab - General", {
- percyCSS,
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- });
-
- cy.findByTestId("mx_GeneralUserSettingsTab").within(() => {
- // Assert that the top heading is rendered
- cy.findByText("General").should("be.visible");
-
- cy.get(".mx_ProfileSettings_profile")
- .scrollIntoView()
- .within(() => {
- // Assert USER_NAME is rendered
- cy.findByRole("textbox", { name: "Display Name" })
- .get(`input[value='${USER_NAME}']`)
- .should("be.visible");
-
- // Assert that a userId is rendered
- cy.get(".mx_ProfileSettings_profile_controls_userId").within(() => {
- cy.findByText(userId).should("exist");
- });
-
- // Check avatar setting
- cy.get(".mx_AvatarSetting_avatar")
- .should("exist")
- .realHover()
- .get(".mx_AvatarSetting_avatar_hovering")
- .within(() => {
- // Hover effect
- cy.get(".mx_AvatarSetting_hoverBg").should("exist");
- cy.get(".mx_AvatarSetting_hover span").within(() => {
- cy.findByText("Upload").should("exist");
- });
- });
- });
-
- // Wait until spinners disappear
- cy.findByTestId("accountSection").within(() => {
- cy.get(".mx_Spinner").should("not.exist");
- });
- cy.findByTestId("discoverySection").within(() => {
- cy.get(".mx_Spinner").should("not.exist");
- });
-
- cy.findByTestId("accountSection").within(() => {
- // Assert that input areas for changing a password exists
- cy.get("form.mx_GeneralUserSettingsTab_section--account_changePassword")
- .scrollIntoView()
- .within(() => {
- cy.findByLabelText("Current password").should("be.visible");
- cy.findByLabelText("New Password").should("be.visible");
- cy.findByLabelText("Confirm password").should("be.visible");
- });
- });
- // Check email addresses area
- cy.findByTestId("mx_AccountEmailAddresses")
- .scrollIntoView()
- .within(() => {
- // Assert that an input area for a new email address is rendered
- cy.findByRole("textbox", { name: "Email Address" }).should("be.visible");
-
- // Assert the add button is visible
- cy.findByRole("button", { name: "Add" }).should("be.visible");
- });
-
- // Check phone numbers area
- cy.findByTestId("mx_AccountPhoneNumbers")
- .scrollIntoView()
- .within(() => {
- // Assert that an input area for a new phone number is rendered
- cy.findByRole("textbox", { name: "Phone Number" }).should("be.visible");
-
- // Assert that the add button is rendered
- cy.findByRole("button", { name: "Add" }).should("be.visible");
- });
-
- // Check language and region setting dropdown
- cy.get(".mx_GeneralUserSettingsTab_section_languageInput")
- .scrollIntoView()
- .within(() => {
- // Check the default value
- cy.findByText("English").should("be.visible");
-
- // Click the button to display the dropdown menu
- cy.findByRole("button", { name: "Language Dropdown" }).click();
-
- // Assert that the default option is rendered and highlighted
- cy.findByRole("option", { name: /Albanian/ })
- .should("be.visible")
- .should("have.class", "mx_Dropdown_option_highlight");
-
- cy.findByRole("option", { name: /Deutsch/ }).should("be.visible");
-
- // Click again to close the dropdown
- cy.findByRole("button", { name: "Language Dropdown" }).click();
-
- // Assert that the default value is rendered again
- cy.findByText("English").should("be.visible");
- });
-
- cy.get("form.mx_SetIdServer")
- .scrollIntoView()
- .within(() => {
- // Assert that an input area for identity server exists
- cy.findByRole("textbox", { name: "Enter a new identity server" }).should("be.visible");
- });
-
- cy.get(".mx_SetIntegrationManager")
- .scrollIntoView()
- .within(() => {
- cy.contains(".mx_SetIntegrationManager_heading_manager", IntegrationManager).should("be.visible");
-
- // Make sure integration manager's toggle switch is enabled
- cy.get(".mx_ToggleSwitch_enabled").should("be.visible");
-
- cy.get(".mx_SetIntegrationManager_heading_manager").should(
- "have.text",
- "Manage integrations(scalar.vector.im)",
- );
- });
-
- // Assert the account deactivation button is displayed
- cy.findByTestId("account-management-section")
- .scrollIntoView()
- .findByRole("button", { name: "Deactivate Account" })
- .should("be.visible")
- .should("have.class", "mx_AccessibleButton_kind_danger");
- });
- });
-
- it("should support adding and removing a profile picture", () => {
- cy.get(".mx_SettingsTab .mx_ProfileSettings").within(() => {
- // Upload a picture
- cy.get(".mx_ProfileSettings_avatarUpload").selectFile("cypress/fixtures/riot.png", { force: true });
-
- // Find and click "Remove" link button
- cy.get(".mx_ProfileSettings_profile").within(() => {
- cy.findByRole("button", { name: "Remove" }).click();
- });
-
- // Assert that the link button disappeared
- cy.get(".mx_AvatarSetting_avatar .mx_AccessibleButton_kind_link_sm").should("not.exist");
- });
- });
-
- it("should set a country calling code based on default_country_code", () => {
- // Check phone numbers area
- cy.findByTestId("mx_AccountPhoneNumbers")
- .scrollIntoView()
- .within(() => {
- // Assert that an input area for a new phone number is rendered
- cy.findByRole("textbox", { name: "Phone Number" }).should("be.visible");
-
- // Check a new phone number dropdown menu
- cy.get(".mx_PhoneNumbers_country")
- .scrollIntoView()
- .within(() => {
- // Assert that the country calling code of United States is visible
- cy.findByText(/\+1/).should("be.visible");
-
- // Click the button to display the dropdown menu
- cy.findByRole("button", { name: "Country Dropdown" }).click();
-
- // Assert that the option for calling code of United Kingdom is visible
- cy.findByRole("option", { name: /United Kingdom/ }).should("be.visible");
-
- // Click again to close the dropdown
- cy.findByRole("button", { name: "Country Dropdown" }).click();
-
- // Assert that the default value is rendered again
- cy.findByText(/\+1/).should("be.visible");
- });
-
- cy.findByRole("button", { name: "Add" }).should("be.visible");
- });
- });
-
- it("should support changing a display name", () => {
- cy.get(".mx_SettingsTab .mx_ProfileSettings").within(() => {
- // Change the diaplay name to USER_NAME_NEW
- cy.findByRole("textbox", { name: "Display Name" }).type(`{selectAll}{del}${USER_NAME_NEW}{enter}`);
- });
-
- cy.closeDialog();
-
- // Assert the avatar's initial characters are set
- cy.get(".mx_UserMenu .mx_BaseAvatar").findByText("A").should("exist"); // Alice
- cy.get(".mx_RoomView_wrapper .mx_BaseAvatar").findByText("A").should("exist"); // Alice
- });
-});
diff --git a/cypress/e2e/settings/preferences-user-settings-tab.spec.ts b/cypress/e2e/settings/preferences-user-settings-tab.spec.ts
deleted file mode 100644
index 61f073e62c..0000000000
--- a/cypress/e2e/settings/preferences-user-settings-tab.spec.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-describe("Preferences user settings tab", () => {
- let homeserver: HomeserverInstance;
-
- beforeEach(() => {
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, "Bob");
- });
- });
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- it("should be rendered properly", () => {
- cy.openUserSettings("Preferences");
-
- cy.findByTestId("mx_PreferencesUserSettingsTab").within(() => {
- // Assert that the top heading is rendered
- cy.contains("Preferences").should("be.visible");
- });
-
- cy.findByTestId("mx_PreferencesUserSettingsTab").percySnapshotElement("User settings tab - Preferences", {
- // Emulate TabbedView's actual min and max widths
- // 580: '.mx_UserSettingsDialog .mx_TabbedView' min-width
- // 796: 1036 (mx_TabbedView_tabsOnLeft actual width) - 240 (mx_TabbedView_tabPanel margin-right)
- widths: [580, 796],
- });
- });
-});
diff --git a/cypress/e2e/settings/security-user-settings-tab.spec.ts b/cypress/e2e/settings/security-user-settings-tab.spec.ts
deleted file mode 100644
index 341624dee3..0000000000
--- a/cypress/e2e/settings/security-user-settings-tab.spec.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-Copyright 2023 Suguru Hirahara
-
-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 { HomeserverInstance } from "../../plugins/utils/homeserver";
-
-describe("Security user settings tab", () => {
- let homeserver: HomeserverInstance;
-
- afterEach(() => {
- cy.stopHomeserver(homeserver);
- });
-
- describe("with posthog enabled", () => {
- beforeEach(() => {
- // Enable posthog
- cy.intercept("/config.json?cachebuster=*", (req) => {
- req.continue((res) => {
- res.send(200, {
- ...res.body,
- posthog: {
- project_api_key: "foo",
- api_host: "bar",
- },
- privacy_policy_url: "example.tld", // Set privacy policy URL to enable privacyPolicyLink
- });
- });
- });
-
- cy.startHomeserver("default").then((data) => {
- homeserver = data;
- cy.initTestUser(homeserver, "Hanako");
- });
-
- // Hide "Notification" toast on Cypress Cloud
- cy.contains(".mx_Toast_toast h2", "Notifications")
- .should("exist")
- .closest(".mx_Toast_toast")
- .within(() => {
- cy.findByRole("button", { name: "Dismiss" }).click();
- });
-
- cy.get(".mx_Toast_buttons").within(() => {
- cy.findByRole("button", { name: "Yes" }).should("exist").click(); // Allow analytics
- });
-
- cy.openUserSettings("Security");
- });
-
- describe("AnalyticsLearnMoreDialog", () => {
- it("should be rendered properly", () => {
- cy.findByRole("button", { name: "Learn more" }).click();
-
- cy.get(".mx_AnalyticsLearnMoreDialog_wrapper").percySnapshotElement("AnalyticsLearnMoreDialog");
- });
- });
- });
-});
diff --git a/playwright/docker-entrypoint.sh b/playwright/docker-entrypoint.sh
index 55554a683a..4d2354dfa4 100644
--- a/playwright/docker-entrypoint.sh
+++ b/playwright/docker-entrypoint.sh
@@ -2,5 +2,7 @@
set -e
+yarn link
yarn --cwd ../element-web install
+yarn --cwd ../element-web link matrix-react-sdk
npx playwright test --update-snapshots --reporter line --project='Legacy Crypto' $1
diff --git a/playwright/e2e/editing/editing.spec.ts b/playwright/e2e/editing/editing.spec.ts
index f05f6f3382..d8add58d81 100644
--- a/playwright/e2e/editing/editing.spec.ts
+++ b/playwright/e2e/editing/editing.spec.ts
@@ -216,7 +216,7 @@ test.describe("Editing", () => {
await app.closeDialog();
// Enable developer mode
- await app.setSettingValue("developerMode", null, SettingLevel.ACCOUNT, true);
+ await app.settings.setValue("developerMode", null, SettingLevel.ACCOUNT, true);
await clickEditedMessage(page, "Massage");
diff --git a/playwright/e2e/settings/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab.spec.ts
new file mode 100644
index 0000000000..dc834f00da
--- /dev/null
+++ b/playwright/e2e/settings/appearance-user-settings-tab.spec.ts
@@ -0,0 +1,251 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+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 { test, expect } from "../../element-web-test";
+import { SettingLevel } from "../../../src/settings/SettingLevel";
+
+test.describe("Appearance user settings tab", () => {
+ test.use({
+ displayName: "Hanako",
+ });
+
+ test("should be rendered properly", async ({ page, user, app }) => {
+ const tab = await app.settings.openUserSettings("Appearance");
+
+ await expect(tab.getByRole("heading", { name: "Customise your appearance" })).toBeVisible();
+
+ // Click "Show advanced" link button
+ await tab.getByRole("button", { name: "Show advanced" }).click();
+
+ // Assert that "Hide advanced" link button is rendered
+ await expect(tab.getByRole("button", { name: "Hide advanced" })).toBeVisible();
+
+ await expect(tab).toHaveScreenshot();
+ });
+
+ test("should support switching layouts", async ({ page, user, app }) => {
+ // Create and view a room first
+ await app.createRoom({ name: "Test Room" });
+ await app.viewRoomByName("Test Room");
+
+ await app.settings.openUserSettings("Appearance");
+
+ const buttons = page.locator(".mx_LayoutSwitcher_RadioButton");
+
+ // Assert that the layout selected by default is "Modern"
+ await expect(
+ buttons.locator(".mx_StyledRadioButton_enabled", {
+ hasText: "Modern",
+ }),
+ ).toBeVisible();
+
+ // Assert that the room layout is set to group (modern) layout
+ await expect(page.locator(".mx_RoomView_body[data-layout='group']")).toBeVisible();
+
+ // Select the first layout
+ await buttons.first().click();
+ // Assert that the layout selected is "IRC (Experimental)"
+ await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible();
+
+ // Assert that the room layout is set to IRC layout
+ await expect(page.locator(".mx_RoomView_body[data-layout='irc']")).toBeVisible();
+
+ // Select the last layout
+ await buttons.last().click();
+
+ // Assert that the layout selected is "Message bubbles"
+ await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible();
+
+ // Assert that the room layout is set to bubble layout
+ await expect(page.locator(".mx_RoomView_body[data-layout='bubble']")).toBeVisible();
+ });
+
+ test("should support changing font size by clicking the font slider", async ({ page, app, user }) => {
+ await app.settings.openUserSettings("Appearance");
+
+ const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
+ const fontSliderSection = tab.locator(".mx_FontScalingPanel_fontSlider");
+
+ await expect(fontSliderSection.getByLabel("Font size")).toBeVisible();
+
+ const slider = fontSliderSection.getByRole("slider");
+ // Click the left position of the slider
+ await slider.click({ position: { x: 0, y: 10 } });
+
+ const MIN_FONT_SIZE = 11;
+ // Assert that the smallest font size is selected
+ await expect(fontSliderSection.locator(`input[value='${MIN_FONT_SIZE}']`)).toBeVisible();
+ await expect(
+ fontSliderSection.locator("output .mx_Slider_selection_label", { hasText: String(MIN_FONT_SIZE) }),
+ ).toBeVisible();
+
+ await expect(fontSliderSection).toHaveScreenshot(`font-slider-${MIN_FONT_SIZE}.png`);
+
+ // Click the right position of the slider
+ await slider.click({ position: { x: 572, y: 10 } });
+
+ const MAX_FONT_SIZE = 21;
+ // Assert that the largest font size is selected
+ await expect(fontSliderSection.locator(`input[value='${MAX_FONT_SIZE}']`)).toBeVisible();
+ await expect(
+ fontSliderSection.locator("output .mx_Slider_selection_label", { hasText: String(MAX_FONT_SIZE) }),
+ ).toBeVisible();
+
+ await expect(fontSliderSection).toHaveScreenshot(`font-slider-${MAX_FONT_SIZE}.png`);
+ });
+
+ test("should disable font size slider when custom font size is used", async ({ page, app, user }) => {
+ await app.settings.openUserSettings("Appearance");
+
+ const panel = page.getByTestId("mx_FontScalingPanel");
+ await panel.locator("label", { hasText: "Use custom size" }).click();
+
+ // Assert that the font slider is disabled
+ await expect(panel.locator(".mx_FontScalingPanel_fontSlider input[disabled]")).toBeVisible();
+ });
+
+ test("should support enabling compact group (modern) layout", async ({ page, app, user }) => {
+ // Create and view a room first
+ await app.createRoom({ name: "Test Room" });
+ await app.viewRoomByName("Test Room");
+
+ await app.settings.openUserSettings("Appearance");
+
+ // Click "Show advanced" link button
+ const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
+ await tab.getByRole("button", { name: "Show advanced" }).click();
+
+ await tab.locator("label", { hasText: "Use a more compact 'Modern' layout" }).click();
+
+ // Assert that the room layout is set to compact group (modern) layout
+ await expect(page.locator("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout")).toBeVisible();
+ });
+
+ test("should disable compact group (modern) layout option on IRC layout and bubble layout", async ({
+ page,
+ app,
+ user,
+ }) => {
+ await app.settings.openUserSettings("Appearance");
+ const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
+
+ const checkDisabled = async () => {
+ await expect(tab.getByRole("checkbox", { name: "Use a more compact 'Modern' layout" })).toBeDisabled();
+ };
+
+ // Click "Show advanced" link button
+ await tab.getByRole("button", { name: "Show advanced" }).click();
+
+ const buttons = page.locator(".mx_LayoutSwitcher_RadioButton");
+
+ // Enable IRC layout
+ await buttons.first().click();
+
+ // Assert that the layout selected is "IRC (Experimental)"
+ await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible();
+
+ await checkDisabled();
+
+ // Enable bubble layout
+ await buttons.last().click();
+
+ // Assert that the layout selected is "IRC (Experimental)"
+ await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible();
+
+ await checkDisabled();
+ });
+
+ test("should support enabling system font", async ({ page, app, user }) => {
+ await app.settings.openUserSettings("Appearance");
+ const tab = page.getByTestId("mx_AppearanceUserSettingsTab");
+
+ // Click "Show advanced" link button
+ await tab.getByRole("button", { name: "Show advanced" }).click();
+
+ await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click();
+ await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click();
+
+ // Assert that the font-family value was removed
+ await expect(page.locator("body")).toHaveCSS("font-family", '""');
+ });
+
+ test.describe("Theme Choice Panel", () => {
+ test.beforeEach(async ({ app, user }) => {
+ // Disable the default theme for consistency in case ThemeWatcher automatically chooses it
+ await app.settings.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
+ });
+
+ test("should be rendered with the light theme selected", async ({ page, app }) => {
+ await app.settings.openUserSettings("Appearance");
+ const themePanel = page.getByTestId("mx_ThemeChoicePanel");
+
+ const useSystemTheme = themePanel.getByTestId("checkbox-use-system-theme");
+ await expect(useSystemTheme.getByText("Match system theme")).toBeVisible();
+ // Assert that 'Match system theme' is not checked
+ // Note that mx_Checkbox_checkmark exists and is hidden by CSS if it is not checked
+ await expect(useSystemTheme.locator(".mx_Checkbox_checkmark")).not.toBeVisible();
+
+ const selectors = themePanel.getByTestId("theme-choice-panel-selectors");
+ await expect(selectors.locator(".mx_ThemeSelector_light")).toBeVisible();
+ await expect(selectors.locator(".mx_ThemeSelector_dark")).toBeVisible();
+ // Assert that the light theme is selected
+ await expect(selectors.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_enabled")).toBeVisible();
+ // Assert that the buttons for the light and dark theme are not enabled
+ await expect(selectors.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled")).not.toBeVisible();
+ await expect(selectors.locator(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled")).not.toBeVisible();
+
+ // Assert that the checkbox for the high contrast theme is rendered
+ await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).toBeVisible();
+ });
+
+ test("should disable the labels for themes and the checkbox for the high contrast theme if the checkbox for the system theme is clicked", async ({
+ page,
+ app,
+ }) => {
+ await app.settings.openUserSettings("Appearance");
+ const themePanel = page.getByTestId("mx_ThemeChoicePanel");
+
+ await themePanel.locator(".mx_Checkbox", { hasText: "Match system theme" }).click();
+
+ // Assert that the labels for the light theme and dark theme are disabled
+ await expect(themePanel.locator(".mx_ThemeSelector_light.mx_StyledRadioButton_disabled")).toBeVisible();
+ await expect(themePanel.locator(".mx_ThemeSelector_dark.mx_StyledRadioButton_disabled")).toBeVisible();
+
+ // Assert that there does not exist a label for an enabled theme
+ await expect(themePanel.locator("label.mx_StyledRadioButton_enabled")).not.toBeVisible();
+
+ // Assert that the checkbox and label to enable the high contrast theme should not exist
+ await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).not.toBeVisible();
+ });
+
+ test("should not render the checkbox and the label for the high contrast theme if the dark theme is selected", async ({
+ page,
+ app,
+ }) => {
+ await app.settings.openUserSettings("Appearance");
+ const themePanel = page.getByTestId("mx_ThemeChoicePanel");
+
+ // Assert that the checkbox and the label to enable the high contrast theme should exist
+ await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).toBeVisible();
+
+ // Enable the dark theme
+ await themePanel.locator(".mx_ThemeSelector_dark").click();
+
+ // Assert that the checkbox and the label should not exist
+ await expect(themePanel.locator(".mx_Checkbox", { hasText: "Use high contrast" })).not.toBeVisible();
+ });
+ });
+});
diff --git a/playwright/e2e/settings/device-management.spec.ts b/playwright/e2e/settings/device-management.spec.ts
new file mode 100644
index 0000000000..b4595610b8
--- /dev/null
+++ b/playwright/e2e/settings/device-management.spec.ts
@@ -0,0 +1,105 @@
+/*
+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 { test, expect } from "../../element-web-test";
+
+test.describe("Device manager", () => {
+ test.use({
+ displayName: "Alice",
+ });
+
+ test.beforeEach(async ({ homeserver, user }) => {
+ // create 3 extra sessions to manage
+ for (let i = 0; i < 3; i++) {
+ await homeserver.loginUser(user.userId, user.password);
+ }
+ });
+
+ test("should display sessions", async ({ page, app }) => {
+ await app.settings.openUserSettings("Sessions");
+ const tab = page.locator(".mx_SettingsTab");
+
+ await expect(tab.getByText("Current session", { exact: true })).toBeVisible();
+
+ const currentSessionSection = tab.getByTestId("current-session-section");
+ await expect(currentSessionSection.getByText("Unverified session")).toBeVisible();
+
+ // current session details opened
+ await currentSessionSection.getByRole("button", { name: "Show details" }).click();
+ await expect(currentSessionSection.getByText("Session details")).toBeVisible();
+
+ // close current session details
+ await currentSessionSection.getByRole("button", { name: "Hide details" }).click();
+ await expect(currentSessionSection.getByText("Session details")).not.toBeVisible();
+
+ const securityRecommendationsSection = tab.getByTestId("security-recommendations-section");
+ await expect(securityRecommendationsSection.getByText("Security recommendations")).toBeVisible();
+ await securityRecommendationsSection.getByRole("button", { name: "View all (3)" }).click();
+
+ /**
+ * Other sessions section
+ */
+ await expect(tab.getByText("Other sessions")).toBeVisible();
+ // filter applied after clicking through from security recommendations
+ await expect(tab.getByLabel("Filter devices")).toHaveText("Show: Unverified");
+ const filteredDeviceListItems = tab.locator(".mx_FilteredDeviceList_listItem");
+ await expect(filteredDeviceListItems).toHaveCount(3);
+
+ // select two sessions
+ // force click as the input element itself is not visible (its size is zero)
+ await filteredDeviceListItems.first().click({ force: true });
+ await filteredDeviceListItems.last().click({ force: true });
+
+ // sign out from list selection action buttons
+ await tab.getByRole("button", { name: "Sign out", exact: true }).click();
+ await page.getByRole("dialog").getByTestId("dialog-primary-button").click();
+
+ // list updated after sign out
+ await expect(filteredDeviceListItems).toHaveCount(1);
+ // security recommendation count updated
+ await expect(tab.getByRole("button", { name: "View all (1)" })).toBeVisible();
+
+ const sessionName = `Alice's device`;
+ // open the first session
+ const firstSession = filteredDeviceListItems.first();
+ await firstSession.getByRole("button", { name: "Show details" }).click();
+
+ await expect(firstSession.getByText("Session details")).toBeVisible();
+
+ await firstSession.getByRole("button", { name: "Rename" }).click();
+ await firstSession.getByTestId("device-rename-input").type(sessionName);
+ await firstSession.getByRole("button", { name: "Save" }).click();
+ // there should be a spinner while device updates
+ await expect(firstSession.locator(".mx_Spinner")).toBeVisible();
+ // wait for spinner to complete
+ await expect(firstSession.locator(".mx_Spinner")).not.toBeVisible();
+
+ // session name updated in details
+ await expect(firstSession.locator(".mx_DeviceDetailHeading h4").getByText(sessionName)).toBeVisible();
+ // and main list item
+ await expect(firstSession.locator(".mx_DeviceTile h4").getByText(sessionName)).toBeVisible();
+
+ // sign out using the device details sign out
+ await firstSession.getByRole("button", { name: "Sign out of this session" }).click();
+
+ // confirm the signout
+ await page.getByRole("dialog").getByTestId("dialog-primary-button").click();
+
+ // no other sessions or security recommendations sections when only one session
+ await expect(tab.getByText("Other sessions")).not.toBeVisible();
+ await expect(tab.getByTestId("security-recommendations-section")).not.toBeVisible();
+ });
+});
diff --git a/playwright/e2e/settings/general-room-settings-tab.spec.ts b/playwright/e2e/settings/general-room-settings-tab.spec.ts
new file mode 100644
index 0000000000..6ba59bf22d
--- /dev/null
+++ b/playwright/e2e/settings/general-room-settings-tab.spec.ts
@@ -0,0 +1,63 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+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 { test, expect } from "../../element-web-test";
+
+test.describe("General room settings tab", () => {
+ const roomName = "Test Room";
+
+ test.use({
+ displayName: "Hanako",
+ });
+
+ test.beforeEach(async ({ user, app }) => {
+ await app.createRoom({ name: roomName });
+ await app.viewRoomByName(roomName);
+ });
+
+ test("should be rendered properly", async ({ page, app }) => {
+ const settings = await app.settings.openRoomSettings("General");
+
+ // Assert that "Show less" details element is rendered
+ await expect(settings.getByText("Show less")).toBeVisible();
+
+ await expect(settings).toHaveScreenshot();
+
+ // Click the "Show less" details element
+ await settings.getByText("Show less").click();
+
+ // Assert that "Show more" details element is rendered instead of "Show more"
+ await expect(settings.getByText("Show less")).not.toBeVisible();
+ await expect(settings.getByText("Show more")).toBeVisible();
+ });
+
+ test("long address should not cause dialog to overflow", async ({ page, app }) => {
+ const settings = await app.settings.openRoomSettings("General");
+ // 1. Set the room-address to be a really long string
+ const longString = "abcasdhjasjhdaj1jh1asdhasjdhajsdhjavhjksd".repeat(4);
+ await settings.locator("#roomAliases input[label='Room address']").fill(longString);
+ await settings.locator("#roomAliases").getByText("Add", { exact: true }).click();
+
+ // 2. wait for the new setting to apply ...
+ await expect(settings.locator("#canonicalAlias")).toHaveValue(`#${longString}:localhost`);
+
+ // 3. Check if the dialog overflows
+ const dialogBoundingBox = await page.locator(".mx_Dialog").boundingBox();
+ const inputBoundingBox = await settings.locator("#canonicalAlias").boundingBox();
+ // Assert that the width of the select element is less than that of .mx_Dialog div.
+ expect(inputBoundingBox.width).toBeLessThan(dialogBoundingBox.width);
+ });
+});
diff --git a/playwright/e2e/settings/general-user-settings-tab.spec.ts b/playwright/e2e/settings/general-user-settings-tab.spec.ts
new file mode 100644
index 0000000000..3f4d268533
--- /dev/null
+++ b/playwright/e2e/settings/general-user-settings-tab.spec.ts
@@ -0,0 +1,189 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+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 { test, expect } from "../../element-web-test";
+
+const USER_NAME = "Bob";
+const USER_NAME_NEW = "Alice";
+const IntegrationManager = "scalar.vector.im";
+
+test.describe("General user settings tab", () => {
+ let userId: string;
+
+ test.use({
+ displayName: USER_NAME,
+ config: {
+ default_country_code: "US", // For checking the international country calling code
+ },
+ uut: async ({ app, user }, use) => {
+ const locator = await app.settings.openUserSettings("General");
+ await use(locator);
+ },
+ });
+
+ test("should be rendered properly", async ({ uut }) => {
+ await expect(uut).toHaveScreenshot("general.png", {
+ // Exclude userId from snapshots
+ mask: [uut.locator(".mx_ProfileSettings_profile_controls > p")],
+ });
+
+ // Assert that the top heading is rendered
+ await expect(uut.getByRole("heading", { name: "General" })).toBeVisible();
+
+ const profile = uut.locator(".mx_ProfileSettings_profile");
+ await profile.scrollIntoViewIfNeeded();
+ await expect(profile.getByRole("textbox", { name: "Display Name" })).toHaveValue(USER_NAME);
+
+ // Assert that a userId is rendered
+ await expect(profile.locator(".mx_ProfileSettings_profile_controls_userId", { hasText: userId })).toBeVisible();
+
+ // Check avatar setting
+ const avatar = profile.locator(".mx_AvatarSetting_avatar");
+ await avatar.hover();
+
+ // Hover effect
+ await expect(avatar.locator(".mx_AvatarSetting_hoverBg")).toBeVisible();
+ await expect(avatar.locator(".mx_AvatarSetting_hover span").getByText("Upload")).toBeVisible();
+
+ // Wait until spinners disappear
+ await expect(uut.getByTestId("accountSection").locator(".mx_Spinner")).not.toBeVisible();
+ await expect(uut.getByTestId("discoverySection").locator(".mx_Spinner")).not.toBeVisible();
+
+ const accountSection = uut.getByTestId("accountSection");
+ // Assert that input areas for changing a password exists
+ const changePassword = accountSection.locator("form.mx_GeneralUserSettingsTab_section--account_changePassword");
+ await changePassword.scrollIntoViewIfNeeded();
+ await expect(changePassword.getByLabel("Current password")).toBeVisible();
+ await expect(changePassword.getByLabel("New Password")).toBeVisible();
+ await expect(changePassword.getByLabel("Confirm password")).toBeVisible();
+
+ // Check email addresses area
+ const emailAddresses = uut.getByTestId("mx_AccountEmailAddresses");
+ await emailAddresses.scrollIntoViewIfNeeded();
+ // Assert that an input area for a new email address is rendered
+ await expect(emailAddresses.getByRole("textbox", { name: "Email Address" })).toBeVisible();
+ // Assert the add button is visible
+ await expect(emailAddresses.getByRole("button", { name: "Add" })).toBeVisible();
+
+ // Check phone numbers area
+ const phoneNumbers = uut.getByTestId("mx_AccountPhoneNumbers");
+ await phoneNumbers.scrollIntoViewIfNeeded();
+ // Assert that an input area for a new phone number is rendered
+ await expect(phoneNumbers.getByRole("textbox", { name: "Phone Number" })).toBeVisible();
+ // Assert that the add button is rendered
+ await expect(phoneNumbers.getByRole("button", { name: "Add" })).toBeVisible();
+
+ // Check language and region setting dropdown
+ const languageInput = uut.locator(".mx_GeneralUserSettingsTab_section_languageInput");
+ await languageInput.scrollIntoViewIfNeeded();
+ // Check the default value
+ await expect(languageInput.getByText("English")).toBeVisible();
+ // Click the button to display the dropdown menu
+ await languageInput.getByRole("button", { name: "Language Dropdown" }).click();
+ // Assert that the default option is rendered and highlighted
+ languageInput.getByRole("option", { name: /Albanian/ });
+ await expect(languageInput.getByRole("option", { name: /Albanian/ })).toHaveClass(
+ /mx_Dropdown_option_highlight/,
+ );
+ await expect(languageInput.getByRole("option", { name: /Deutsch/ })).toBeVisible();
+ // Click again to close the dropdown
+ await languageInput.getByRole("button", { name: "Language Dropdown" }).click();
+ // Assert that the default value is rendered again
+ await expect(languageInput.getByText("English")).toBeVisible();
+
+ const setIdServer = uut.locator(".mx_SetIdServer");
+ await setIdServer.scrollIntoViewIfNeeded();
+ // Assert that an input area for identity server exists
+ await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
+
+ const setIntegrationManager = uut.locator(".mx_SetIntegrationManager");
+ await setIntegrationManager.scrollIntoViewIfNeeded();
+ await expect(
+ setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager", { hasText: IntegrationManager }),
+ ).toBeVisible();
+ // Make sure integration manager's toggle switch is enabled
+ await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
+ await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
+ "Manage integrations(scalar.vector.im)",
+ );
+
+ // Assert the account deactivation button is displayed
+ const accountManagementSection = uut.getByTestId("account-management-section");
+ await accountManagementSection.scrollIntoViewIfNeeded();
+ await expect(accountManagementSection.getByRole("button", { name: "Deactivate Account" })).toHaveClass(
+ /mx_AccessibleButton_kind_danger/,
+ );
+ });
+
+ test("should support adding and removing a profile picture", async ({ uut }) => {
+ const profileSettings = uut.locator(".mx_ProfileSettings");
+ // Upload a picture
+ await profileSettings
+ .locator(".mx_ProfileSettings_avatarUpload")
+ .setInputFiles("playwright/sample-files/riot.png");
+
+ // Find and click "Remove" link button
+ await profileSettings.locator(".mx_ProfileSettings_profile").getByRole("button", { name: "Remove" }).click();
+
+ // Assert that the link button disappeared
+ await expect(
+ profileSettings.locator(".mx_AvatarSetting_avatar .mx_AccessibleButton_kind_link_sm"),
+ ).not.toBeVisible();
+ });
+
+ test("should set a country calling code based on default_country_code", async ({ uut }) => {
+ // Check phone numbers area
+ const accountPhoneNumbers = uut.getByTestId("mx_AccountPhoneNumbers");
+ await accountPhoneNumbers.scrollIntoViewIfNeeded();
+ // Assert that an input area for a new phone number is rendered
+ await expect(accountPhoneNumbers.getByRole("textbox", { name: "Phone Number" })).toBeVisible();
+
+ // Check a new phone number dropdown menu
+ const dropdown = accountPhoneNumbers.locator(".mx_PhoneNumbers_country");
+ await dropdown.scrollIntoViewIfNeeded();
+ // Assert that the country calling code of the United States is visible
+ await expect(dropdown.getByText(/\+1/)).toBeVisible();
+
+ // Click the button to display the dropdown menu
+ await dropdown.getByRole("button", { name: "Country Dropdown" }).click();
+
+ // Assert that the option for calling code of the United Kingdom is visible
+ await expect(dropdown.getByRole("option", { name: /United Kingdom/ })).toBeVisible();
+
+ // Click again to close the dropdown
+ await dropdown.getByRole("button", { name: "Country Dropdown" }).click();
+
+ // Assert that the default value is rendered again
+ await expect(dropdown.getByText(/\+1/)).toBeVisible();
+
+ await expect(accountPhoneNumbers.getByRole("button", { name: "Add" })).toBeVisible();
+ });
+
+ test("should support changing a display name", async ({ uut, page, app }) => {
+ // Change the diaplay name to USER_NAME_NEW
+ const displayNameInput = uut
+ .locator(".mx_SettingsTab .mx_ProfileSettings")
+ .getByRole("textbox", { name: "Display Name" });
+ await displayNameInput.fill(USER_NAME_NEW);
+ await displayNameInput.press("Enter");
+
+ await app.closeDialog();
+
+ // Assert the avatar's initial characters are set
+ await expect(page.locator(".mx_UserMenu .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice
+ await expect(page.locator(".mx_RoomView_wrapper .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice
+ });
+});
diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts
new file mode 100644
index 0000000000..884b62d0b1
--- /dev/null
+++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts
@@ -0,0 +1,31 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+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 { test, expect } from "../../element-web-test";
+
+test.describe("Preferences user settings tab", () => {
+ test.use({
+ displayName: "Bob",
+ });
+
+ test("should be rendered properly", async ({ app, user }) => {
+ const tab = await app.settings.openUserSettings("Preferences");
+
+ // Assert that the top heading is rendered
+ await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
+ await expect(tab).toHaveScreenshot();
+ });
+});
diff --git a/playwright/e2e/settings/security-user-settings-tab.spec.ts b/playwright/e2e/settings/security-user-settings-tab.spec.ts
new file mode 100644
index 0000000000..5bb9131941
--- /dev/null
+++ b/playwright/e2e/settings/security-user-settings-tab.spec.ts
@@ -0,0 +1,51 @@
+/*
+Copyright 2023 Suguru Hirahara
+
+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 { test, expect } from "../../element-web-test";
+
+test.describe("Security user settings tab", () => {
+ test.describe("with posthog enabled", () => {
+ test.use({
+ displayName: "Hanako",
+ // Enable posthog
+ config: {
+ posthog: {
+ project_api_key: "foo",
+ api_host: "bar",
+ },
+ privacy_policy_url: "example.tld", // Set privacy policy URL to enable privacyPolicyLink
+ },
+ });
+
+ test.beforeEach(async ({ page, user }) => {
+ // Dismiss "Notification" toast
+ await page
+ .locator(".mx_Toast_toast", { hasText: "Notifications" })
+ .getByRole("button", { name: "Dismiss" })
+ .click();
+
+ await page.locator(".mx_Toast_buttons").getByRole("button", { name: "Yes" }).click(); // Allow analytics
+ });
+
+ test.describe("AnalyticsLearnMoreDialog", () => {
+ test("should be rendered properly", async ({ app, page }) => {
+ const tab = await app.settings.openUserSettings("Security");
+ await tab.getByRole("button", { name: "Learn more" }).click();
+ await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toHaveScreenshot();
+ });
+ });
+ });
+});
diff --git a/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts b/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts
index 3c4db3f0cd..5d6570fcfe 100644
--- a/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts
+++ b/playwright/e2e/user-onboarding/user-onboarding-new.spec.ts
@@ -36,7 +36,7 @@ test.describe("User Onboarding (new user)", () => {
test("page is shown and preference exists", async ({ page, app }) => {
await expect(page.locator(".mx_UserOnboardingPage")).toHaveScreenshot();
- await app.openUserSettings("Preferences");
+ await app.settings.openUserSettings("Preferences");
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).toBeVisible();
});
diff --git a/playwright/e2e/user-onboarding/user-onboarding-old.spec.ts b/playwright/e2e/user-onboarding/user-onboarding-old.spec.ts
index 99bbcedd98..d9be78f349 100644
--- a/playwright/e2e/user-onboarding/user-onboarding-old.spec.ts
+++ b/playwright/e2e/user-onboarding/user-onboarding-old.spec.ts
@@ -30,7 +30,7 @@ test.describe("User Onboarding (old user)", () => {
test("page and preference are hidden", async ({ page, user, app }) => {
await expect(page.locator(".mx_UserOnboardingPage")).not.toBeVisible();
await expect(page.locator(".mx_UserOnboardingButton")).not.toBeVisible();
- await app.openUserSettings("Preferences");
+ await app.settings.openUserSettings("Preferences");
await expect(page.getByText("Show shortcut to welcome checklist above the room list")).not.toBeVisible();
});
});
diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts
index 042af50a56..8777b84b41 100644
--- a/playwright/element-web-test.ts
+++ b/playwright/element-web-test.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { test as base, expect } from "@playwright/test";
+import { test as base, expect, Locator } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
import _ from "lodash";
@@ -66,6 +66,7 @@ export const test = base.extend<
crypto: Crypto;
room?: { roomId: string };
toasts: Toasts;
+ uut?: Locator; // Unit Under Test, useful place to refer a prepared locator
}
>({
cryptoBackend: ["legacy", { option: true }],
diff --git a/playwright/pages/ElementAppPage.ts b/playwright/pages/ElementAppPage.ts
index 359c0a54b8..de39a7bc32 100644
--- a/playwright/pages/ElementAppPage.ts
+++ b/playwright/pages/ElementAppPage.ts
@@ -17,70 +17,18 @@ limitations under the License.
import { type Locator, type Page } from "@playwright/test";
import type { IContent, ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/matrix";
-import type { SettingLevel } from "../../src/settings/SettingLevel";
+import { Settings } from "./settings";
export class ElementAppPage {
public constructor(private readonly page: Page) {}
- /**
- * Sets the value for a setting. The room ID is optional if the
- * setting is not being set for a particular room, otherwise it
- * should be supplied. The value may be null to indicate that the
- * level should no longer have an override.
- * @param {string} settingName The name of the setting to change.
- * @param {String} roomId The room ID to change the value in, may be
- * null.
- * @param {SettingLevel} level The level to change the value at.
- * @param {*} value The new value of the setting, may be null.
- * @return {Promise} Resolves when the setting has been changed.
- */
- public async setSettingValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise {
- return this.page.evaluate<
- Promise,
- {
- settingName: string;
- roomId: string | null;
- level: SettingLevel;
- value: any;
- }
- >(
- ({ settingName, roomId, level, value }) => {
- return window.mxSettingsStore.setValue(settingName, roomId, level, value);
- },
- { settingName, roomId, level, value },
- );
- }
+ public settings = new Settings(this.page);
/**
* Open the top left user menu, returning a Locator to the resulting context menu.
*/
public async openUserMenu(): Promise {
- await this.page.getByRole("button", { name: "User menu" }).click();
- const locator = this.page.locator(".mx_ContextualMenu");
- await locator.waitFor();
- return locator;
- }
-
- /**
- * Switch settings tab to the one by the given name
- * @param tab the name of the tab to switch to.
- */
- public async switchTab(tab: string): Promise {
- await this.page
- .locator(".mx_TabbedView_tabLabels")
- .locator(".mx_TabbedView_tabLabel", { hasText: tab })
- .click();
- }
-
- /**
- * Open user settings (via user menu), returns a locator to the dialog
- * @param tab the name of the tab to switch to after opening, optional.
- */
- public async openUserSettings(tab?: string): Promise {
- const locator = await this.openUserMenu();
- await locator.getByRole("menuitem", { name: "All settings", exact: true }).click();
- if (tab) await this.switchTab(tab);
- return this.page.locator(".mx_UserSettingsDialog");
+ return this.settings.openUserMenu();
}
/**
@@ -96,7 +44,7 @@ export class ElementAppPage {
* Close dialog currently open dialog
*/
public async closeDialog(): Promise {
- return this.page.getByRole("button", { name: "Close dialog", exact: true }).click();
+ return this.settings.closeDialog();
}
/**
@@ -113,6 +61,34 @@ export class ElementAppPage {
}, options);
}
+ /**
+ * Opens the given room by name. The room must be visible in the
+ * room list, but the room list may be folded horizontally, and the
+ * room may contain unread messages.
+ *
+ * @param name The exact room name to find and click on/open.
+ */
+ public async viewRoomByName(name: string): Promise {
+ // We look for the room inside the room list, which is a tree called Rooms.
+ //
+ // There are 3 cases:
+ // - the room list is folded:
+ // then the aria-label on the room tile is the name (with nothing extra)
+ // - the room list is unfolder and the room has messages:
+ // then the aria-label contains the unread count, but the title of the
+ // div inside the titleContainer equals the room name
+ // - the room list is unfolded and the room has no messages:
+ // then the aria-label is the name and so is the title of a div
+ //
+ // So by matching EITHER title=name OR aria-label=name we find this exact
+ // room in all three cases.
+ return this.page
+ .getByRole("tree", { name: "Rooms" })
+ .locator(`[title="${name}"],[aria-label="${name}"]`)
+ .first()
+ .click();
+ }
+
/**
* Get the composer element
* @param isRightPanel whether to select the right panel composer, otherwise the main timeline composer
diff --git a/playwright/pages/settings.ts b/playwright/pages/settings.ts
new file mode 100644
index 0000000000..347886a0ab
--- /dev/null
+++ b/playwright/pages/settings.ts
@@ -0,0 +1,102 @@
+/*
+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.
+*/
+
+import { Locator, Page } from "@playwright/test";
+
+import type { SettingLevel } from "../../src/settings/SettingLevel";
+
+export class Settings {
+ public constructor(private readonly page: Page) {}
+
+ /**
+ * Open the top left user menu, returning a Locator to the resulting context menu.
+ */
+ public async openUserMenu(): Promise {
+ await this.page.getByRole("button", { name: "User menu" }).click();
+ const locator = this.page.locator(".mx_ContextualMenu");
+ await locator.waitFor();
+ return locator;
+ }
+
+ /**
+ * Close dialog currently open dialog
+ */
+ public async closeDialog(): Promise {
+ return this.page.getByRole("button", { name: "Close dialog", exact: true }).click();
+ }
+
+ /**
+ * Sets the value for a setting. The room ID is optional if the
+ * setting is not being set for a particular room, otherwise it
+ * should be supplied. The value may be null to indicate that the
+ * level should no longer have an override.
+ * @param {string} settingName The name of the setting to change.
+ * @param {String} roomId The room ID to change the value in, may be
+ * null.
+ * @param {SettingLevel} level The level to change the value at.
+ * @param {*} value The new value of the setting, may be null.
+ * @return {Promise} Resolves when the setting has been changed.
+ */
+ public async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise {
+ return this.page.evaluate<
+ Promise,
+ {
+ settingName: string;
+ roomId: string | null;
+ level: SettingLevel;
+ value: any;
+ }
+ >(
+ ({ settingName, roomId, level, value }) => {
+ return window.mxSettingsStore.setValue(settingName, roomId, level, value);
+ },
+ { settingName, roomId, level, value },
+ );
+ }
+
+ /**
+ * Switch settings tab to the one by the given name
+ * @param tab the name of the tab to switch to.
+ */
+ public async switchTab(tab: string): Promise {
+ await this.page
+ .locator(".mx_TabbedView_tabLabels")
+ .locator(".mx_TabbedView_tabLabel", { hasText: tab })
+ .click();
+ }
+
+ /**
+ * Open user settings (via user menu), returns a locator to the dialog
+ * @param tab the name of the tab to switch to after opening, optional.
+ */
+ public async openUserSettings(tab?: string): Promise {
+ const locator = await this.openUserMenu();
+ await locator.getByRole("menuitem", { name: "All settings", exact: true }).click();
+ if (tab) await this.switchTab(tab);
+ return this.page.locator(".mx_Dialog").filter({ has: this.page.locator(".mx_UserSettingsDialog") });
+ }
+
+ /**
+ * Open room settings (via room menu), returns a locator to the dialog
+ * @param tab the name of the tab to switch to after opening, optional.
+ */
+ public async openRoomSettings(tab?: string): Promise {
+ await this.page.getByRole("main").getByRole("button", { name: "Room options", exact: true }).click();
+ await this.page.locator(".mx_RoomTile_contextMenu").getByRole("menuitem", { name: "Settings" }).click();
+ if (tab) await this.switchTab(tab);
+ return this.page.locator(".mx_Dialog").filter({ has: this.page.locator(".mx_RoomSettingsDialog") });
+ }
+}
diff --git a/playwright/plugins/homeserver/index.ts b/playwright/plugins/homeserver/index.ts
index 39a6f639b8..bd01f0e555 100644
--- a/playwright/plugins/homeserver/index.ts
+++ b/playwright/plugins/homeserver/index.ts
@@ -31,6 +31,13 @@ export interface HomeserverInstance {
* @param displayName optional display name to set on the newly registered user
*/
registerUser(username: string, password: string, displayName?: string): Promise;
+
+ /**
+ * Logs into synapse with the given username/password
+ * @param userId login username
+ * @param password login password
+ */
+ loginUser(userId: string, password: string): Promise;
}
export interface StartHomeserverOpts {
diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts
index fc7eb5fb44..78a37d3a17 100644
--- a/playwright/plugins/homeserver/synapse/index.ts
+++ b/playwright/plugins/homeserver/synapse/index.ts
@@ -196,4 +196,27 @@ export class Synapse implements Homeserver, HomeserverInstance {
displayName,
};
}
+
+ public async loginUser(userId: string, password: string): Promise {
+ const url = `${this.config.baseUrl}/_matrix/client/v3/login`;
+ const res = await this.request.post(url, {
+ data: {
+ type: "m.login.password",
+ identifier: {
+ type: "m.id.user",
+ user: userId,
+ },
+ password: password,
+ },
+ });
+ const json = await res.json();
+
+ return {
+ password,
+ accessToken: json.access_token,
+ userId: json.user_id,
+ deviceId: json.device_id,
+ homeServer: json.home_server,
+ };
+ }
}
diff --git a/playwright/sample-files/riot.png b/playwright/sample-files/riot.png
new file mode 100644
index 0000000000..ee42954c78
Binary files /dev/null and b/playwright/sample-files/riot.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png
new file mode 100644
index 0000000000..2ad9f6565d
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-darwin.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png
new file mode 100644
index 0000000000..20ef5f738c
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/Appearance-user-settings-tab-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-darwin.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-darwin.png
new file mode 100644
index 0000000000..12996f4e5b
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-darwin.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-linux.png
new file mode 100644
index 0000000000..4964e9c8db
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-11-linux.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-darwin.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-darwin.png
new file mode 100644
index 0000000000..6b1e058c6a
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-darwin.png differ
diff --git a/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-linux.png
new file mode 100644
index 0000000000..d145a18655
Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab.spec.ts/font-slider-21-linux.png differ
diff --git a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png
new file mode 100644
index 0000000000..2cb327d89c
Binary files /dev/null and b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png
new file mode 100644
index 0000000000..d70e3667c9
Binary files /dev/null and b/playwright/snapshots/settings/general-user-settings-tab.spec.ts/general-linux.png differ
diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png
new file mode 100644
index 0000000000..24d7c675a3
Binary files /dev/null and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ
diff --git a/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png
new file mode 100644
index 0000000000..afea3e1444
Binary files /dev/null and b/playwright/snapshots/settings/security-user-settings-tab.spec.ts/Security-user-settings-tab-with-posthog-enable-b5d89-csLearnMoreDialog-should-be-rendered-properly-1-linux.png differ