/* Copyright 2024-2025 New Vector Ltd. Copyright 2023 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import { type Page } from "@playwright/test"; import { sample, uniqueId } from "lodash-es"; // We want to avoid using `mergeTests` in index.ts because it drops useful type // information about the fixtures. Instead, we add `services` into our fixture // suite by using its `test` as a base, so that there is a linear hierarchy. import { test as base } from "./panel.js"; import { type Credentials } from "../utils/api.js"; /** * Adds an initScript to the given page which will populate localStorage appropriately so that Element will use the given credentials. * * Warning: this is an incomplete implementation, particularly in MAS environments: for example, a logout operation will not work correctly. */ export async function populateLocalStorageWithCredentials(page: Page, credentials: Credentials) { await page.addInitScript( ({ credentials }) => { window.localStorage.setItem("mx_hs_url", credentials.homeserverBaseUrl); window.localStorage.setItem("mx_user_id", credentials.userId); window.localStorage.setItem("mx_access_token", credentials.accessToken); window.localStorage.setItem("mx_device_id", credentials.deviceId); window.localStorage.setItem("mx_is_guest", "false"); window.localStorage.setItem("mx_has_pickle_key", "false"); window.localStorage.setItem("mx_has_access_token", "true"); // XXX: If the homeserver is using MAS, then Element uses additional localstorage settings to store more // data. The fact that we don't populate them means that, for example, when you hit "Remove this device" in // the app, it attempts to hit the legacy `/logout` endpoint, which returns a 40x error, which is silently // ignored. // // If you fix this, (1) yay! (2) please remember to update the warning on the doc-comments of this method // and its callers. window.localStorage.setItem( "mx_local_settings", JSON.stringify({ // Retain any other settings which may have already been set ...JSON.parse(window.localStorage.getItem("mx_local_settings") ?? "{}"), // Ensure the language is set to a consistent value language: "en", }), ); }, { credentials }, ); } export interface TestFixtures { /** * The displayname to use for the user registered in {@link #credentials}. * * To set it, call `test.use({ displayName: "myDisplayName" })` in the test file or `describe` block. * See {@link https://playwright.dev/docs/api/class-test#test-use}. */ displayName?: string; /** * A test fixture which registers a test user on the {@link #homeserver} and supplies the details * of the registered user. */ credentials: Credentials; /** * The same as {@link https://playwright.dev/docs/api/class-fixtures#fixtures-page|`page`}, * but adds an initScript which will populate localStorage with the user's details from * {@link #credentials} and {@link #homeserver}. * * Warning: this is an incomplete implementation, particularly in MAS environments: for example, a logout operation * will not work correctly. * * Similar to {@link #user}, but doesn't load the app. */ pageWithCredentials: Page; /** * A (rather poorly-named) test fixture which registers a user per {@link #credentials}, stores * the credentials into localStorage per {@link #pageWithCredentials}, and then loads the front page of the * app. * * Warning: the user credentials are an incomplete implementation, particularly in MAS environments: for example, a logout operation * will not work correctly. */ user: Credentials; } export const test = base.extend({ displayName: undefined, // We don't directly depend upon the `context` fixture, but we do need to make sure that it has been run // before this fixture, since it is responsible for configuring the APIRequestContext on the homeserver, so // without it we cannot register the user. credentials: async ({ context, homeserver, displayName: testDisplayName }, use, testInfo) => { const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"]; const password = uniqueId("password_"); const displayName = testDisplayName ?? sample(names)!; const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName); console.log(`Registered test user ${credentials.userId} with displayname ${displayName}`); await use({ ...credentials, displayName, }); }, pageWithCredentials: async ({ page, credentials }, use) => { await populateLocalStorageWithCredentials(page, credentials); await use(page); }, user: async ({ pageWithCredentials: page, credentials }, use) => { await page.goto("/"); await page.waitForSelector(".mx_MatrixChat", { timeout: 30000 }); await use(credentials); }, });