/* Copyright 2024 New Vector Ltd. Copyright 2023 Suguru Hirahara SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import { type Route } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { getSampleFilePath } from "../../sample-files"; const USER_NAME = "Bob"; const USER_NAME_NEW = "Alice"; const EXTERNAL_ACCOUNT_MANAGEMENT_URL = "https://just.for.test.io/"; test.describe("Account user settings tab", () => { 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("Account"); await use(locator); }, }); test("should be rendered properly", { tag: "@screenshot" }, async ({ uut, user, axe }) => { await expect(uut).toMatchScreenshot("account.png"); // Assert that the top heading is rendered await expect(uut.getByRole("heading", { name: "Account", exact: true })).toBeVisible(); const profile = uut.locator(".mx_UserProfileSettings_profile"); await profile.scrollIntoViewIfNeeded(); await expect(profile.getByRole("textbox", { name: "Display Name" })).toHaveValue(USER_NAME); // Assert that a userId is rendered await expect(uut.getByLabel("Username")).toHaveText(user.userId); // 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"); await accountSection.scrollIntoViewIfNeeded(); // Assert that input areas for changing a password exists await expect(accountSection.getByLabel("Current password")).toBeVisible(); await expect(accountSection.getByLabel("New Password")).toBeVisible(); await expect(accountSection.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(); // 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/, ); await expect(axe).toHaveNoViolations(); }); test("should respond to small screen sizes", { tag: "@screenshot" }, async ({ page, uut }) => { await page.setViewportSize({ width: 700, height: 600 }); await expect(uut).toMatchScreenshot("account-smallscreen.png"); }); test.describe("with external account management", () => { test.use({ page: async ({ page }, runFixture) => { const authMetadataHandler = async (route: Route): Promise => { await route.fulfill({ json: { issuer: EXTERNAL_ACCOUNT_MANAGEMENT_URL, authorization_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}authorize`, token_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}token`, revocation_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}revoke`, registration_endpoint: `${EXTERNAL_ACCOUNT_MANAGEMENT_URL}register`, response_types_supported: ["code"], grant_types_supported: ["authorization_code", "refresh_token"], response_modes_supported: ["query", "fragment"], code_challenge_methods_supported: ["S256"], account_management_uri: EXTERNAL_ACCOUNT_MANAGEMENT_URL, }, }); }; await page.route("**/_matrix/client/v1/auth_metadata", authMetadataHandler); await page.route("**/_matrix/client/unstable/org.matrix.msc2965/auth_metadata", authMetadataHandler); await runFixture(page); }, }); test("should render the manage account button properly", { tag: "@screenshot" }, async ({ uut, axe }) => { const manageAccountButton = uut.getByTestId("external-account-management-link"); await expect(manageAccountButton).toBeVisible(); await expect(manageAccountButton).toHaveAttribute("href", EXTERNAL_ACCOUNT_MANAGEMENT_URL); await expect(manageAccountButton).toHaveAttribute("target", "_blank"); await expect(manageAccountButton).toHaveText(/Manage account/); const profileButtons = uut.locator(".mx_UserProfileSettings_profile_buttons"); await profileButtons.scrollIntoViewIfNeeded(); await expect(profileButtons).toMatchScreenshot("account-manage-account-button.png"); await expect(axe).toHaveNoViolations(); }); }); test("should show tooltips on narrow screen", async ({ page, uut }) => { await page.setViewportSize({ width: 700, height: 600 }); await page.getByRole("tab", { name: "Account" }).hover(); await expect(page.getByRole("tooltip")).toHaveText("Account"); }); test("should support adding and removing a profile picture", async ({ uut, page }) => { const profileSettings = uut.locator(".mx_UserProfileSettings"); // Upload a picture await profileSettings.getByAltText("Upload").setInputFiles(getSampleFilePath("riot.png")); // Image should be visible await expect(profileSettings.locator(".mx_AvatarSetting_avatar img")).toBeVisible(); // Open the menu & click remove await profileSettings.getByRole("button", { name: "Profile Picture" }).click(); await page.getByRole("menuitem", { name: "Remove" }).click(); // Assert that the image disappeared await expect(profileSettings.locator(".mx_AvatarSetting_avatar img")).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, user }) => { // Change the diaplay name to USER_NAME_NEW const displayNameInput = uut .locator(".mx_SettingsTab .mx_UserProfileSettings") .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 const menu = await app.openUserMenu(); await expect(menu.getByRole("img", { name: user.userId }).getByText("A")).toBeVisible(); // Alice await expect(page.locator(".mx_RoomView_wrapper .mx_BaseAvatar").getByText("A")).toBeVisible(); // Alice }); // ported to a playwright test because the jest test was very flakey for no obvious reason test("should display an error if the code is incorrect when adding a phone number", async ({ uut, page }) => { const dummyUrl = "https://nowhere.dummy/_matrix/client/unstable/add_threepid/msisdn/submit_token"; await page.route( `**/_matrix/client/v3/account/3pid/msisdn/requestToken`, async (route) => { await route.fulfill({ json: { success: true, sid: "1", msisdn: "447700900000", intl_fmt: "+44 7700 900000", submit_url: dummyUrl, }, }); }, { times: 1 }, ); await page.route( dummyUrl, async (route) => { await route.fulfill({ status: 400, json: { errcode: "M_THREEPID_AUTH_FAILED", error: "That code is definitely wrong", }, }); }, { times: 1 }, ); const phoneSection = page.getByTestId("mx_AccountPhoneNumbers"); await phoneSection.getByRole("textbox", { name: "Phone Number" }).fill("07700900000"); await phoneSection.getByRole("button", { name: "Add" }).click(); await phoneSection .getByRole("textbox", { name: "Verification code" }) .fill("A small eurasian field mouse dancing the paso doble"); await phoneSection.getByRole("button", { name: "Continue" }).click(); await expect(page.getByRole("heading", { name: "Unable to verify phone number." })).toBeVisible(); }); });