diff --git a/playwright/e2e/crypto/backups-mas.spec.ts b/playwright/e2e/crypto/backups-mas.spec.ts new file mode 100644 index 00000000000..614bde50646 --- /dev/null +++ b/playwright/e2e/crypto/backups-mas.spec.ts @@ -0,0 +1,91 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 { test, expect } from "../../element-web-test"; +import { registerAccountMas } from "../oidc"; +import { isDendrite } from "../../plugins/homeserver/dendrite"; +import { TestClientServerAPI } from "../csAPI"; +import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts"; + +// These tests register an account with MAS because then we go through the "normal" registration flow +// and crypto gets set up. Using the 'user' fixture create a a user an synthesizes an existing login, +// which is faster but leaves us without crypto set up. +test.use(masHomeserver); +test.describe("Encryption state after registration", () => { + test.skip(isDendrite, "does not yet support MAS"); + + test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => { + await page.goto("/#/login"); + await page.getByRole("button", { name: "Continue" }).click(); + await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!"); + + await app.settings.openUserSettings("Security & Privacy"); + await expect(page.getByText("This session is backing up your keys.")).toBeVisible(); + }); + + test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => { + await page.goto("/#/login"); + await page.getByRole("button", { name: "Continue" }).click(); + await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!"); + + await page.getByRole("button", { name: "Add room" }).click(); + await page.getByRole("menuitem", { name: "New room" }).click(); + await page.getByRole("textbox", { name: "Name" }).fill("test room"); + await page.getByRole("button", { name: "Create room" }).click(); + + await expect(page.getByRole("heading", { name: "Set up recovery" })).toBeVisible(); + }); +}); + +test.describe("Key backup reset from elsewhere", () => { + test.skip(isDendrite, "does not yet support MAS"); + + test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => { + const testUsername = "alice"; + const testPassword = "Pa$sW0rD!"; + + // there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake + // clock so we can skip the delay + await page.clock.install(); + + await page.goto("/#/login"); + await page.getByRole("button", { name: "Continue" }).click(); + await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword); + + await page.getByRole("button", { name: "Add room" }).click(); + await page.getByRole("menuitem", { name: "New room" }).click(); + await page.getByRole("textbox", { name: "Name" }).fill("test room"); + await page.getByRole("button", { name: "Create room" }).click(); + + // @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not. + const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken()); + + const csAPI = new TestClientServerAPI(request, homeserver, accessToken); + + const backupInfo = await csAPI.getCurrentBackupInfo(); + + await csAPI.deleteBackupVersion(backupInfo.version); + + await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession"); + await page.getByRole("button", { name: "Send message" }).click(); + + await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup"); + await page.getByRole("button", { name: "Send message" }).click(); + + // Should be the message we sent plus the room creation event + await expect(page.locator(".mx_EventTile")).toHaveCount(2); + await expect( + page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"), + ).toBeVisible(); + + // Wait for it to try uploading the key + await page.clock.fastForward(20000); + + await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible(); + }); +}); diff --git a/playwright/e2e/crypto/backups.spec.ts b/playwright/e2e/crypto/backups.spec.ts index 6e6c3e04917..a49495edfab 100644 --- a/playwright/e2e/crypto/backups.spec.ts +++ b/playwright/e2e/crypto/backups.spec.ts @@ -9,10 +9,6 @@ Please see LICENSE files in the repository root for full details. import { type Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; -import { registerAccountMas } from "../oidc"; -import { isDendrite } from "../../plugins/homeserver/dendrite"; -import { TestClientServerAPI } from "../csAPI"; -import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts"; async function expectBackupVersionToBe(page: Page, version: string) { await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText( @@ -22,85 +18,6 @@ async function expectBackupVersionToBe(page: Page, version: string) { await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(6) td")).toHaveText(version); } -// These tests register an account with MAS because then we go through the "normal" registration flow -// and crypto gets set up. Using the 'user' fixture create a a user an synthesizes an existing login, -// which is faster but leaves us without crypto set up. -test.describe("Encryption state after registration", () => { - test.use(masHomeserver); - test.skip(isDendrite, "does not yet support MAS"); - - test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => { - await page.goto("/#/login"); - await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!"); - - await app.settings.openUserSettings("Security & Privacy"); - await expect(page.getByText("This session is backing up your keys.")).toBeVisible(); - }); - - test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => { - await page.goto("/#/login"); - await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!"); - - await page.getByRole("button", { name: "Add room" }).click(); - await page.getByRole("menuitem", { name: "New room" }).click(); - await page.getByRole("textbox", { name: "Name" }).fill("test room"); - await page.getByRole("button", { name: "Create room" }).click(); - - await expect(page.getByRole("heading", { name: "Set up recovery" })).toBeVisible(); - }); -}); - -test.describe("Key backup reset from elsewhere", () => { - test.use(masHomeserver); - test.skip(isDendrite, "does not yet support MAS"); - - test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => { - const testUsername = "alice"; - const testPassword = "Pa$sW0rD!"; - - // there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake - // clock so we can skip the delay - await page.clock.install(); - - await page.goto("/#/login"); - await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword); - - await page.getByRole("button", { name: "Add room" }).click(); - await page.getByRole("menuitem", { name: "New room" }).click(); - await page.getByRole("textbox", { name: "Name" }).fill("test room"); - await page.getByRole("button", { name: "Create room" }).click(); - - // @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not. - const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken()); - - const csAPI = new TestClientServerAPI(request, homeserver, accessToken); - - const backupInfo = await csAPI.getCurrentBackupInfo(); - - await csAPI.deleteBackupVersion(backupInfo.version); - - await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession"); - await page.getByRole("button", { name: "Send message" }).click(); - - await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup"); - await page.getByRole("button", { name: "Send message" }).click(); - - // Should be the message we sent plus the room creation event - await expect(page.locator(".mx_EventTile")).toHaveCount(2); - await expect( - page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"), - ).toBeVisible(); - - // Wait for it to try uploading the key - await page.clock.fastForward(20000); - - await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible(); - }); -}); - test.describe("Backups", () => { test.use({ displayName: "Hanako", diff --git a/playwright/e2e/crypto/dehydration.spec.ts b/playwright/e2e/crypto/dehydration.spec.ts index 7d05d71ae15..7c1bbd7ac48 100644 --- a/playwright/e2e/crypto/dehydration.spec.ts +++ b/playwright/e2e/crypto/dehydration.spec.ts @@ -19,30 +19,30 @@ function getMemberTileByName(page: Page, name: string): Locator { return page.locator(`.mx_EntityTile, [title="${name}"]`); } -test.describe("Dehydration", () => { - test.skip(isDendrite, "does not yet support dehydration v2"); - - test.use({ - displayName: NAME, - synapseConfigOptions: { - experimental_features: { - msc2697_enabled: false, - msc3814_enabled: true, - }, +test.use({ + displayName: NAME, + synapseConfigOptions: { + experimental_features: { + msc2697_enabled: false, + msc3814_enabled: true, }, - config: async ({ config, context }, use) => { - const wellKnown = { - ...config.default_server_config, - "org.matrix.msc3814": true, - }; - - await context.route("https://localhost/.well-known/matrix/client", async (route) => { - await route.fulfill({ json: wellKnown }); - }); + }, + config: async ({ config, context }, use) => { + const wellKnown = { + ...config.default_server_config, + "org.matrix.msc3814": true, + }; + + await context.route("https://localhost/.well-known/matrix/client", async (route) => { + await route.fulfill({ json: wellKnown }); + }); + + await use(config); + }, +}); - await use(config); - }, - }); +test.describe("Dehydration", () => { + test.skip(isDendrite, "does not yet support dehydration v2"); test("Create dehydrated device", async ({ page, user, app }, workerInfo) => { // Create a backup (which will create SSSS, and dehydrated device) diff --git a/playwright/e2e/forgot-password/forgot-password.spec.ts b/playwright/e2e/forgot-password/forgot-password.spec.ts index 621cf3fd3c4..71475e892eb 100644 --- a/playwright/e2e/forgot-password/forgot-password.spec.ts +++ b/playwright/e2e/forgot-password/forgot-password.spec.ts @@ -16,20 +16,21 @@ const username = "user1234"; const password = "oETo7MPf0o"; const email = "user@nowhere.dummy"; -test.describe("Forgot Password", () => { - test.skip(isDendrite, "not yet wired up"); - test.use(emailHomeserver); - test.use({ - config: { - // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. - // We point that to a guaranteed-invalid domain. - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, +test.use(emailHomeserver); +test.use({ + config: { + // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. + // We point that to a guaranteed-invalid domain. + default_server_config: { + "m.homeserver": { + base_url: "https://server.invalid", }, }, - }); + }, +}); + +test.describe("Forgot Password", () => { + test.skip(isDendrite, "not yet wired up"); test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => { await page.goto("/"); diff --git a/playwright/e2e/login/consent.spec.ts b/playwright/e2e/login/consent.spec.ts index 9127ae54bb8..aab190298fa 100644 --- a/playwright/e2e/login/consent.spec.ts +++ b/playwright/e2e/login/consent.spec.ts @@ -9,12 +9,12 @@ Please see LICENSE files in the repository root for full details. import { test, expect } from "../../element-web-test"; import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts"; -test.describe("Consent", () => { - test.use(consentHomeserver); - test.use({ - displayName: "Bob", - }); +test.use(consentHomeserver); +test.use({ + displayName: "Bob", +}); +test.describe("Consent", () => { test("should prompt the user to consent to terms when server deems it necessary", async ({ context, page, diff --git a/playwright/e2e/login/login.spec.ts b/playwright/e2e/login/login-consent.spec.ts similarity index 69% rename from playwright/e2e/login/login.spec.ts rename to playwright/e2e/login/login-consent.spec.ts index 24b23fdbd8d..ab70e1d1869 100644 --- a/playwright/e2e/login/login.spec.ts +++ b/playwright/e2e/login/login-consent.spec.ts @@ -9,13 +9,11 @@ Please see LICENSE files in the repository root for full details. import { Page } from "playwright-core"; import { expect, test } from "../../element-web-test"; -import { doTokenRegistration } from "./utils"; -import { isDendrite } from "../../plugins/homeserver/dendrite"; import { selectHomeserver } from "../utils"; import { Credentials, HomeserverInstance } from "../../plugins/homeserver"; import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts"; -import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts"; +// This test requires fixed credentials for the device signing keys below to work const username = "user1234"; const password = "p4s5W0rD"; @@ -70,38 +68,42 @@ const DEVICE_SIGNING_KEYS_BODY = { }, }; -async function login(page: Page, homeserver: HomeserverInstance) { +async function login(page: Page, homeserver: HomeserverInstance, credentials: Credentials) { await page.getByRole("link", { name: "Sign in" }).click(); await selectHomeserver(page, homeserver.baseUrl); - await page.getByRole("textbox", { name: "Username" }).fill(username); - await page.getByPlaceholder("Password").fill(password); + await page.getByRole("textbox", { name: "Username" }).fill(credentials.username); + await page.getByPlaceholder("Password").fill(credentials.password); await page.getByRole("button", { name: "Sign in" }).click(); } -test.describe("Login", () => { - test.use({ - config: { - // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. - // We point that to a guaranteed-invalid domain. - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, +test.use(consentHomeserver); +test.use({ + config: { + // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. + // We point that to a guaranteed-invalid domain. + default_server_config: { + "m.homeserver": { + base_url: "https://server.invalid", }, }, - }); - - test.describe("Password login", () => { - test.use(consentHomeserver); - - let creds: Credentials; - - test.beforeEach(async ({ homeserver }) => { - creds = await homeserver.registerUser(username, password); + }, + credentials: async ({ context, homeserver }, use) => { + const displayName = "Dave"; + const credentials = await homeserver.registerUser(username, password, displayName); + console.log(`Registered test user @user:localhost with displayname ${displayName}`); + + await use({ + ...credentials, + displayName, }); + }, +}); +test.describe("Login", () => { + test.describe("Password login", () => { test("Loads the welcome page by default; then logs in with an existing account and lands on the home screen", async ({ + credentials, page, homeserver, checkA11y, @@ -135,16 +137,16 @@ test.describe("Login", () => { // cy.percySnapshot("Login"); await checkA11y(); - await page.getByRole("textbox", { name: "Username" }).fill(username); - await page.getByPlaceholder("Password").fill(password); + await page.getByRole("textbox", { name: "Username" }).fill(credentials.username); + await page.getByPlaceholder("Password").fill(credentials.password); await page.getByRole("button", { name: "Sign in" }).click(); await expect(page).toHaveURL(/\/#\/home$/); }); - test("Follows the original link after login", async ({ page, homeserver }) => { + test("Follows the original link after login", async ({ page, homeserver, credentials }) => { await page.goto("/#/room/!room:id"); // should redirect to the welcome page - await login(page, homeserver); + await login(page, homeserver, credentials); await expect(page).toHaveURL(/\/#\/room\/!room:id$/); await expect(page.getByRole("button", { name: "Join the discussion" })).toBeVisible(); @@ -155,9 +157,10 @@ test.describe("Login", () => { page, homeserver, request, + credentials, }) => { const res = await request.post(`${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, { - headers: { Authorization: `Bearer ${creds.accessToken}` }, + headers: { Authorization: `Bearer ${credentials.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY, }); if (res.status() / 100 !== 2) { @@ -166,7 +169,7 @@ test.describe("Login", () => { expect(res.status() / 100).toEqual(2); await page.goto("/"); - await login(page, homeserver); + await login(page, homeserver, credentials); await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible(); @@ -184,10 +187,14 @@ test.describe("Login", () => { page, homeserver, request, + credentials, }) => { const res = await request.post( `${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, - { headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY }, + { + headers: { Authorization: `Bearer ${credentials.accessToken}` }, + data: DEVICE_SIGNING_KEYS_BODY, + }, ); if (res.status() / 100 !== 2) { console.log("Uploading dummy keys failed", await res.json()); @@ -195,7 +202,7 @@ test.describe("Login", () => { expect(res.status() / 100).toEqual(2); await page.goto("/"); - await login(page, homeserver); + await login(page, homeserver, credentials); await expect(page.getByRole("heading", { name: "Verify this device", level: 1 })).toBeVisible(); @@ -214,11 +221,15 @@ test.describe("Login", () => { page, homeserver, request, + credentials, }) => { - console.log(`uid ${creds.userId} body`, DEVICE_SIGNING_KEYS_BODY); + console.log(`uid ${credentials.userId} body`, DEVICE_SIGNING_KEYS_BODY); const res = await request.post( `${homeserver.baseUrl}/_matrix/client/v3/keys/device_signing/upload`, - { headers: { Authorization: `Bearer ${creds.accessToken}` }, data: DEVICE_SIGNING_KEYS_BODY }, + { + headers: { Authorization: `Bearer ${credentials.accessToken}` }, + data: DEVICE_SIGNING_KEYS_BODY, + }, ); if (res.status() / 100 !== 2) { console.log("Uploading dummy keys failed", await res.json()); @@ -226,9 +237,9 @@ test.describe("Login", () => { expect(res.status() / 100).toEqual(2); await page.goto("/"); - await login(page, homeserver); + await login(page, homeserver, credentials); - const h1 = await page.getByRole("heading", { name: "Verify this device", level: 1 }); + const h1 = page.getByRole("heading", { name: "Verify this device", level: 1 }); await expect(h1).toBeVisible(); await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0); @@ -237,25 +248,7 @@ test.describe("Login", () => { }); }); - // tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server - test.describe("SSO login", () => { - test.skip(isDendrite, "does not yet support SSO"); - test.use(legacyOAuthHomeserver); - - test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => { - // If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to - // your firewall settings: Synapse is unable to reach the OIDC server. - // - // If you are using ufw, try something like: - // sudo ufw allow in on docker0 - // - await doTokenRegistration(page, homeserver); - }); - }); - test.describe("logout", () => { - test.use(consentHomeserver); - test("should go to login page on logout", async ({ page, user }) => { await page.getByRole("button", { name: "User menu" }).click(); await expect(page.getByText(user.displayName, { exact: true })).toBeVisible(); @@ -267,29 +260,4 @@ test.describe("Login", () => { await expect(page).toHaveURL(/\/#\/login$/); }); }); - - test.describe("logout with logout_redirect_url", () => { - test.use(consentHomeserver); - test.use({ - config: { - // We redirect to decoder-ring because it's a predictable page that isn't Element itself. - // We could use example.org, matrix.org, or something else, however this puts dependency of external - // infrastructure on our tests. In the same vein, we don't really want to figure out how to ship a - // `test-landing.html` page when running with an uncontrolled Element (via `yarn start`). - // Using the decoder-ring is just as fine, and we can search for strategic names. - logout_redirect_url: "/decoder-ring/", - }, - }); - - test("should respect logout_redirect_url", async ({ page, user }) => { - await page.getByRole("button", { name: "User menu" }).click(); - await expect(page.getByText(user.displayName, { exact: true })).toBeVisible(); - - // give a change for the outstanding requests queue to settle before logging out - await page.waitForTimeout(2000); - - await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click(); - await expect(page).toHaveURL(/\/decoder-ring\/$/); - }); - }); }); diff --git a/playwright/e2e/login/login-sso.spec.ts b/playwright/e2e/login/login-sso.spec.ts new file mode 100644 index 00000000000..fbe190b9358 --- /dev/null +++ b/playwright/e2e/login/login-sso.spec.ts @@ -0,0 +1,29 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import { test } from "../../element-web-test"; +import { doTokenRegistration } from "./utils"; +import { isDendrite } from "../../plugins/homeserver/dendrite"; +import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts"; + +test.use(legacyOAuthHomeserver); + +// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server +test.describe("SSO login", () => { + test.skip(isDendrite, "does not yet support SSO"); + + test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => { + // If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to + // your firewall settings: Synapse is unable to reach the OIDC server. + // + // If you are using ufw, try something like: + // sudo ufw allow in on docker0 + // + await doTokenRegistration(page, homeserver); + }); +}); diff --git a/playwright/e2e/login/logout_redirect_url.spec.ts b/playwright/e2e/login/logout_redirect_url.spec.ts new file mode 100644 index 00000000000..c27acf96bec --- /dev/null +++ b/playwright/e2e/login/logout_redirect_url.spec.ts @@ -0,0 +1,35 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import { expect, test } from "../../element-web-test"; +import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts"; + +test.use(consentHomeserver); +test.use({ + config: { + // We redirect to decoder-ring because it's a predictable page that isn't Element itself. + // We could use example.org, matrix.org, or something else, however this puts dependency of external + // infrastructure on our tests. In the same vein, we don't really want to figure out how to ship a + // `test-landing.html` page when running with an uncontrolled Element (via `yarn start`). + // Using the decoder-ring is just as fine, and we can search for strategic names. + logout_redirect_url: "/decoder-ring/", + }, +}); + +test.describe("logout with logout_redirect_url", () => { + test("should respect logout_redirect_url", async ({ page, user }) => { + await page.getByRole("button", { name: "User menu" }).click(); + await expect(page.getByText(user.displayName, { exact: true })).toBeVisible(); + + // give a change for the outstanding requests queue to settle before logging out + await page.waitForTimeout(2000); + + await page.locator(".mx_UserMenu_contextMenu").getByRole("menuitem", { name: "Sign out" }).click(); + await expect(page).toHaveURL(/\/decoder-ring\/$/); + }); +}); diff --git a/playwright/e2e/login/soft_logout.spec.ts b/playwright/e2e/login/soft_logout.spec.ts index 6b4aa5a40d8..2c9325e7571 100644 --- a/playwright/e2e/login/soft_logout.spec.ts +++ b/playwright/e2e/login/soft_logout.spec.ts @@ -6,122 +6,38 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { Page } from "@playwright/test"; - import { test, expect } from "../../element-web-test"; -import { doTokenRegistration } from "./utils"; -import { Credentials } from "../../plugins/homeserver"; -import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts"; - -test.describe("Soft logout", () => { - test.use({ - displayName: "Alice", - config: { - // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. - // We point that to a guaranteed-invalid domain. - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, +import { interceptRequestsWithSoftLogout } from "./utils"; + +test.use({ + displayName: "Alice", + config: { + // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. + // We point that to a guaranteed-invalid domain. + default_server_config: { + "m.homeserver": { + base_url: "https://server.invalid", }, }, - }); - - test.describe("with password user", () => { - test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => { - await interceptRequestsWithSoftLogout(page, user); - await expect(page.getByText("You're signed out")).toBeVisible(); - await page.getByPlaceholder("Password").fill(user.password); - await page.getByPlaceholder("Password").press("Enter"); - - // back to the welcome page - await expect(page).toHaveURL(/\/#\/home/); - await expect( - page.getByRole("heading", { name: "Now, let's help you get started", exact: true }), - ).toBeVisible(); - }); - - test("still shows the soft-logout page when the page is reloaded after a soft-logout", async ({ - page, - user, - }) => { - await interceptRequestsWithSoftLogout(page, user); - await expect(page.getByText("You're signed out")).toBeVisible(); - await page.reload(); - await expect(page.getByText("You're signed out")).toBeVisible(); - }); - }); - - test.describe("with SSO user", () => { - test.use(legacyOAuthHomeserver); - test.use({ - user: async ({ page, homeserver }, use) => { - const user = await doTokenRegistration(page, homeserver); - - // Eventually, we should end up at the home screen. - await expect(page).toHaveURL(/\/#\/home$/); - await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); - - await use(user); - }, - }); - - test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => { - await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); - - await interceptRequestsWithSoftLogout(page, user); - - await expect(page.getByText("You're signed out")).toBeVisible(); - await page.getByRole("button", { name: "Continue with OAuth test" }).click(); - - // click the submit button - await page.getByRole("button", { name: "Submit" }).click(); - - // Synapse prompts us to grant permission to Element - await expect(page.getByRole("heading", { name: "Continue to your account" })).toBeVisible(); - await page.getByRole("link", { name: "Continue" }).click(); - - // back to the welcome page - await expect(page).toHaveURL(/\/#\/home$/); - await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); - }); - }); + }, }); -/** - * Intercept calls to /sync and have them fail with a soft-logout - * - * Any further requests to /sync with the same access token are blocked. - */ -async function interceptRequestsWithSoftLogout(page: Page, user: Credentials): Promise { - await page.route("**/_matrix/client/*/sync*", async (route, req) => { - const accessToken = await req.headerValue("Authorization"); - - // now, if the access token on this request matches the expired one, block it - if (accessToken === `Bearer ${user.accessToken}`) { - console.log("Intercepting request with soft-logged-out access token"); - await route.fulfill({ - status: 401, - json: { - errcode: "M_UNKNOWN_TOKEN", - error: "Soft logout", - soft_logout: true, - }, - }); - return; - } +test.describe("Soft logout with password user", () => { + test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => { + await interceptRequestsWithSoftLogout(page, user); + await expect(page.getByText("You're signed out")).toBeVisible(); + await page.getByPlaceholder("Password").fill(user.password); + await page.getByPlaceholder("Password").press("Enter"); - // otherwise, pass through as normal - await route.continue(); + // back to the welcome page + await expect(page).toHaveURL(/\/#\/home/); + await expect(page.getByRole("heading", { name: "Now, let's help you get started", exact: true })).toBeVisible(); }); - const promise = page.waitForResponse((resp) => resp.url().includes("/sync") && resp.status() === 401); - - // do something to make the active /sync return: create a new room - await page.evaluate(() => { - // don't wait for this to complete: it probably won't, because of the broken sync - window.mxMatrixClientPeg.get().createRoom({}); + test("still shows the soft-logout page when the page is reloaded after a soft-logout", async ({ page, user }) => { + await interceptRequestsWithSoftLogout(page, user); + await expect(page.getByText("You're signed out")).toBeVisible(); + await page.reload(); + await expect(page.getByText("You're signed out")).toBeVisible(); }); - - await promise; -} +}); diff --git a/playwright/e2e/login/soft_logout_oauth.spec.ts b/playwright/e2e/login/soft_logout_oauth.spec.ts new file mode 100644 index 00000000000..19b1fc0124c --- /dev/null +++ b/playwright/e2e/login/soft_logout_oauth.spec.ts @@ -0,0 +1,59 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 { test, expect } from "../../element-web-test"; +import { doTokenRegistration, interceptRequestsWithSoftLogout } from "./utils"; +import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts"; + +test.use({ + displayName: "Alice", + config: { + // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. + // We point that to a guaranteed-invalid domain. + default_server_config: { + "m.homeserver": { + base_url: "https://server.invalid", + }, + }, + }, +}); + +test.use(legacyOAuthHomeserver); +test.describe("Soft logout with SSO user", () => { + test.use({ + user: async ({ page, homeserver }, use) => { + const user = await doTokenRegistration(page, homeserver); + + // Eventually, we should end up at the home screen. + await expect(page).toHaveURL(/\/#\/home$/); + await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); + + await use(user); + }, + }); + + test("shows the soft-logout page when a request fails, and allows a re-login", async ({ page, user }) => { + await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); + + await interceptRequestsWithSoftLogout(page, user); + + await expect(page.getByText("You're signed out")).toBeVisible(); + await page.getByRole("button", { name: "Continue with OAuth test" }).click(); + + // click the submit button + await page.getByRole("button", { name: "Submit" }).click(); + + // Synapse prompts us to grant permission to Element + await expect(page.getByRole("heading", { name: "Continue to your account" })).toBeVisible(); + await page.getByRole("link", { name: "Continue" }).click(); + + // back to the welcome page + await expect(page).toHaveURL(/\/#\/home$/); + await expect(page.getByRole("heading", { name: "Welcome Alice", exact: true })).toBeVisible(); + }); +}); diff --git a/playwright/e2e/login/utils.ts b/playwright/e2e/login/utils.ts index 0a728faecc1..cc98d8819a2 100644 --- a/playwright/e2e/login/utils.ts +++ b/playwright/e2e/login/utils.ts @@ -20,7 +20,7 @@ export async function doTokenRegistration( await page.getByRole("button", { name: "Edit" }).click(); await page.getByRole("textbox", { name: "Other homeserver" }).fill(homeserver.baseUrl); - await page.getByRole("button", { name: "Continue" }).click(); + await page.getByRole("button", { name: "Continue", exact: true }).click(); // wait for the dialog to go away await expect(page.locator(".mx_ServerPickerDialog")).toHaveCount(0); @@ -56,5 +56,44 @@ export async function doTokenRegistration( homeServer: window.mxMatrixClientPeg.get().getHomeserverUrl(), password: null, displayName: "Alice", + username: window.mxMatrixClientPeg.get().getUserIdLocalpart(), })); } + +/** + * Intercept calls to /sync and have them fail with a soft-logout + * + * Any further requests to /sync with the same access token are blocked. + */ +export async function interceptRequestsWithSoftLogout(page: Page, user: Credentials): Promise { + await page.route("**/_matrix/client/*/sync*", async (route, req) => { + const accessToken = await req.headerValue("Authorization"); + + // now, if the access token on this request matches the expired one, block it + if (accessToken === `Bearer ${user.accessToken}`) { + console.log("Intercepting request with soft-logged-out access token"); + await route.fulfill({ + status: 401, + json: { + errcode: "M_UNKNOWN_TOKEN", + error: "Soft logout", + soft_logout: true, + }, + }); + return; + } + + // otherwise, pass through as normal + await route.continue(); + }); + + const promise = page.waitForResponse((resp) => resp.url().includes("/sync") && resp.status() === 401); + + // do something to make the active /sync return: create a new room + await page.evaluate(() => { + // don't wait for this to complete: it probably won't, because of the broken sync + window.mxMatrixClientPeg.get().createRoom({}); + }); + + await promise; +} diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts index 60c5bbf025a..63cf0a5b59f 100644 --- a/playwright/e2e/oidc/oidc-native.spec.ts +++ b/playwright/e2e/oidc/oidc-native.spec.ts @@ -12,8 +12,8 @@ import { ElementAppPage } from "../../pages/ElementAppPage.ts"; import { isDendrite } from "../../plugins/homeserver/dendrite"; import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts"; +test.use(masHomeserver); test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { - test.use(masHomeserver); test.skip(isDendrite, "does not yet support MAS"); test.slow(); // trace recording takes a while here diff --git a/playwright/e2e/register/email.spec.ts b/playwright/e2e/register/email.spec.ts index 50cfb4297ae..74c8ba7962b 100644 --- a/playwright/e2e/register/email.spec.ts +++ b/playwright/e2e/register/email.spec.ts @@ -10,21 +10,22 @@ import { test, expect } from "../../element-web-test"; import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts"; import { isDendrite } from "../../plugins/homeserver/dendrite"; +test.use(emailHomeserver); +test.use({ + config: ({ config }, use) => + use({ + ...config, + default_server_config: { + ...config.default_server_config, + "m.identity_server": { + base_url: "https://server.invalid", + }, + }, + }), +}); + test.describe("Email Registration", async () => { test.skip(isDendrite, "not yet wired up"); - test.use(emailHomeserver); - test.use({ - config: ({ config }, use) => - use({ - ...config, - default_server_config: { - ...config.default_server_config, - "m.identity_server": { - base_url: "https://server.invalid", - }, - }, - }), - }); test.beforeEach(async ({ homeserver, page }) => { await page.goto("/#/register"); diff --git a/playwright/e2e/register/register.spec.ts b/playwright/e2e/register/register.spec.ts index 7756fe53c5a..90854de33a6 100644 --- a/playwright/e2e/register/register.spec.ts +++ b/playwright/e2e/register/register.spec.ts @@ -9,20 +9,20 @@ Please see LICENSE files in the repository root for full details. import { test, expect } from "../../element-web-test"; import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts"; -test.describe("Registration", () => { - test.use(consentHomeserver); - test.use({ - config: { - // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. - // We point that to a guaranteed-invalid domain. - default_server_config: { - "m.homeserver": { - base_url: "https://server.invalid", - }, +test.use(consentHomeserver); +test.use({ + config: { + // The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver. + // We point that to a guaranteed-invalid domain. + default_server_config: { + "m.homeserver": { + base_url: "https://server.invalid", }, }, - }); + }, +}); +test.describe("Registration", () => { test.beforeEach(async ({ page }) => { await page.goto("/#/register"); }); diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts index 2555e5a8353..d3d3cb352b4 100644 --- a/playwright/e2e/spaces/threads-activity-centre/index.ts +++ b/playwright/e2e/spaces/threads-activity-centre/index.ts @@ -13,6 +13,7 @@ import { test as base, expect } from "../../../element-web-test"; import { Bot } from "../../../pages/bot"; import { Client } from "../../../pages/client"; import { ElementAppPage } from "../../../pages/ElementAppPage"; +import { Credentials } from "../../../plugins/homeserver"; type RoomRef = { name: string; roomId: string }; @@ -336,12 +337,14 @@ export class Helpers { * @param room1 * @param room2 * @param msg - MessageBuilder + * @param user - the user to mention in the first message * @param hasMention - whether to include a mention in the first message */ async populateThreads( room1: { name: string; roomId: string }, room2: { name: string; roomId: string }, msg: MessageBuilder, + user: Credentials, hasMention = true, ) { if (hasMention) { @@ -350,9 +353,9 @@ export class Helpers { msg.threadedOff("Msg1", { "body": "User", "format": "org.matrix.custom.html", - "formatted_body": "User", + "formatted_body": `User`, "m.mentions": { - user_ids: ["@user:localhost"], + user_ids: [user.userId], }, }), ]); diff --git a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts index 16276c5b9db..c45222d035f 100644 --- a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts +++ b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts @@ -46,16 +46,21 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => { await util.assertNotificationTac(); }); - test("should show a highlight indicator when there is a mention in a thread", async ({ room1, util, msg }) => { + test("should show a highlight indicator when there is a mention in a thread", async ({ + room1, + util, + msg, + user, + }) => { await util.goTo(room1); await util.receiveMessages(room1, [ "Msg1", msg.threadedOff("Msg1", { "body": "User", "format": "org.matrix.custom.html", - "formatted_body": "User", + "formatted_body": `User`, "m.mentions": { - user_ids: ["@user:localhost"], + user_ids: [user.userId], }, }), ]); @@ -64,26 +69,30 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => { await util.assertHighlightIndicator(); }); - test("should show the rooms with unread threads", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => { - await util.goTo(room2); - await util.populateThreads(room1, room2, msg); - // The indicator should be shown - await util.assertHighlightIndicator(); - - // Verify that we have the expected rooms in the TAC - await util.openTac(); - await util.assertRoomsInTac([ - { room: room2.name, notificationLevel: "highlight" }, - { room: room1.name, notificationLevel: "notification" }, - ]); - - // Verify that we don't have a visual regression - await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png"); - }); + test( + "should show the rooms with unread threads", + { tag: "@screenshot" }, + async ({ room1, room2, util, msg, user }) => { + await util.goTo(room2); + await util.populateThreads(room1, room2, msg, user); + // The indicator should be shown + await util.assertHighlightIndicator(); + + // Verify that we have the expected rooms in the TAC + await util.openTac(); + await util.assertRoomsInTac([ + { room: room2.name, notificationLevel: "highlight" }, + { room: room1.name, notificationLevel: "notification" }, + ]); + + // Verify that we don't have a visual regression + await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png"); + }, + ); - test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg }) => { + test("should update with a thread is read", { tag: "@screenshot" }, async ({ room1, room2, util, msg, user }) => { await util.goTo(room2); - await util.populateThreads(room1, room2, msg); + await util.populateThreads(room1, room2, msg, user); // Click on the first room in TAC await util.openTac(); @@ -104,9 +113,9 @@ test.describe("Threads Activity Centre", { tag: "@no-firefox" }, () => { await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-notification-unread.png"); }); - test("should order by recency after notification level", async ({ room1, room2, util, msg }) => { + test("should order by recency after notification level", async ({ room1, room2, util, msg, user }) => { await util.goTo(room2); - await util.populateThreads(room1, room2, msg, false); + await util.populateThreads(room1, room2, msg, user, false); await util.openTac(); await util.assertRoomsInTac([ diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts index f2f2afd3b95..952ec0c0264 100644 --- a/playwright/element-web-test.ts +++ b/playwright/element-web-test.ts @@ -137,12 +137,12 @@ export const test = base.extend({ }, displayName: undefined, - credentials: async ({ homeserver, displayName: testDisplayName }, use) => { + 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", password, displayName); + const credentials = await homeserver.registerUser(`user_${testInfo.testId}`, password, displayName); console.log(`Registered test user @user:localhost with displayname ${displayName}`); await use({ diff --git a/playwright/plugins/homeserver/dendrite/index.ts b/playwright/plugins/homeserver/dendrite/index.ts index c9cb3332eb2..897cafc5a3d 100644 --- a/playwright/plugins/homeserver/dendrite/index.ts +++ b/playwright/plugins/homeserver/dendrite/index.ts @@ -6,31 +6,34 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { Fixtures, PlaywrightTestArgs } from "@playwright/test"; +import { Fixtures } from "@playwright/test"; -import { Fixtures as BaseFixtures } from "../../../element-web-test.ts"; import { DendriteContainer, PineconeContainer } from "../../../testcontainers/dendrite.ts"; import { Services } from "../../../services.ts"; -type Fixture = PlaywrightTestArgs & Services & BaseFixtures; -export const dendriteHomeserver: Fixtures = { - _homeserver: async ({ request }, use) => { - const container = - process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" - ? new DendriteContainer(request) - : new PineconeContainer(request); - await use(container); - }, - homeserver: async ({ logger, network, _homeserver: homeserver }, use) => { - const container = await homeserver - .withNetwork(network) - .withNetworkAliases("homeserver") - .withLogConsumer(logger.getConsumer("dendrite")) - .start(); +export const dendriteHomeserver: Fixtures<{}, Services> = { + _homeserver: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + const container = + process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" ? new DendriteContainer() : new PineconeContainer(); + await use(container); + }, + { scope: "worker" }, + ], + homeserver: [ + async ({ logger, network, _homeserver: homeserver }, use) => { + const container = await homeserver + .withNetwork(network) + .withNetworkAliases("homeserver") + .withLogConsumer(logger.getConsumer("dendrite")) + .start(); - await use(container); - await container.stop(); - }, + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], }; export function isDendrite(): boolean { diff --git a/playwright/plugins/homeserver/index.ts b/playwright/plugins/homeserver/index.ts index c05a5c345e8..50dea472f78 100644 --- a/playwright/plugins/homeserver/index.ts +++ b/playwright/plugins/homeserver/index.ts @@ -41,4 +41,5 @@ export interface Credentials { homeServer: string; password: string | null; // null for password-less users displayName?: string; + username: string; // the localpart of the userId } diff --git a/playwright/plugins/homeserver/synapse/consentHomeserver.ts b/playwright/plugins/homeserver/synapse/consentHomeserver.ts index 07d1fbbec2c..4abf22b16e4 100644 --- a/playwright/plugins/homeserver/synapse/consentHomeserver.ts +++ b/playwright/plugins/homeserver/synapse/consentHomeserver.ts @@ -10,47 +10,50 @@ import { Fixtures } from "@playwright/test"; import { Services } from "../../../services.ts"; -export const consentHomeserver: Fixtures = { - _homeserver: async ({ _homeserver: container, mailhog }, use) => { - container - .withCopyDirectoriesToContainer([ - { source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" }, - ]) - .withConfig({ - email: { - enable_notifs: false, - smtp_host: "mailhog", - smtp_port: 1025, - smtp_user: "username", - smtp_pass: "password", - require_transport_security: false, - notif_from: "Your Friendly %(app)s homeserver ", - app_name: "Matrix", - notif_template_html: "notif_mail.html", - notif_template_text: "notif_mail.txt", - notif_for_new_users: true, - client_base_url: "http://localhost/element", - }, - user_consent: { - template_dir: "/data/res/templates/privacy", - version: "1.0", - server_notice_content: { - msgtype: "m.text", - body: "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s", +export const consentHomeserver: Fixtures<{}, Services> = { + _homeserver: [ + async ({ _homeserver: container, mailhog }, use) => { + container + .withCopyDirectoriesToContainer([ + { source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" }, + ]) + .withConfig({ + email: { + enable_notifs: false, + smtp_host: "mailhog", + smtp_port: 1025, + smtp_user: "username", + smtp_pass: "password", + require_transport_security: false, + notif_from: "Your Friendly %(app)s homeserver ", + app_name: "Matrix", + notif_template_html: "notif_mail.html", + notif_template_text: "notif_mail.txt", + notif_for_new_users: true, + client_base_url: "http://localhost/element", }, - send_server_notice_to_guests: true, - block_events_error: - "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s", - require_at_registration: true, - }, - server_notices: { - system_mxid_localpart: "notices", - system_mxid_display_name: "Server Notices", - system_mxid_avatar_url: "mxc://localhost/oumMVlgDnLYFaPVkExemNVVZ", - room_name: "Server Notices", - }, - }) - .withConfigField("listeners[0].resources[0].names", ["client", "consent"]); - await use(container); - }, + user_consent: { + template_dir: "/data/res/templates/privacy", + version: "1.0", + server_notice_content: { + msgtype: "m.text", + body: "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s", + }, + send_server_notice_to_guests: true, + block_events_error: + "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s", + require_at_registration: true, + }, + server_notices: { + system_mxid_localpart: "notices", + system_mxid_display_name: "Server Notices", + system_mxid_avatar_url: "mxc://localhost/oumMVlgDnLYFaPVkExemNVVZ", + room_name: "Server Notices", + }, + }) + .withConfigField("listeners[0].resources[0].names", ["client", "consent"]); + await use(container); + }, + { scope: "worker" }, + ], }; diff --git a/playwright/plugins/homeserver/synapse/emailHomeserver.ts b/playwright/plugins/homeserver/synapse/emailHomeserver.ts index ab7affdee52..0df3a670484 100644 --- a/playwright/plugins/homeserver/synapse/emailHomeserver.ts +++ b/playwright/plugins/homeserver/synapse/emailHomeserver.ts @@ -10,19 +10,22 @@ import { Fixtures } from "@playwright/test"; import { Services } from "../../../services.ts"; -export const emailHomeserver: Fixtures = { - _homeserver: async ({ _homeserver: container, mailhog }, use) => { - container.withConfig({ - enable_registration_without_verification: undefined, - disable_msisdn_registration: undefined, - registrations_require_3pid: ["email"], - email: { - smtp_host: "mailhog", - smtp_port: 1025, - notif_from: "Your Friendly %(app)s homeserver ", - app_name: "my_branded_matrix_server", - }, - }); - await use(container); - }, +export const emailHomeserver: Fixtures<{}, Services> = { + _homeserver: [ + async ({ _homeserver: container, mailhog }, use) => { + container.withConfig({ + enable_registration_without_verification: undefined, + disable_msisdn_registration: undefined, + registrations_require_3pid: ["email"], + email: { + smtp_host: "mailhog", + smtp_port: 1025, + notif_from: "Your Friendly %(app)s homeserver ", + app_name: "my_branded_matrix_server", + }, + }); + await use(container); + }, + { scope: "worker" }, + ], }; diff --git a/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts b/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts index a6b39e04845..246829e422d 100644 --- a/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts +++ b/playwright/plugins/homeserver/synapse/legacyOAuthHomeserver.ts @@ -12,37 +12,40 @@ import { TestContainers } from "testcontainers"; import { Services } from "../../../services.ts"; import { OAuthServer } from "../../oauth_server"; -export const legacyOAuthHomeserver: Fixtures = { - _homeserver: async ({ _homeserver: container }, use) => { - const server = new OAuthServer(); - const port = server.start(); +export const legacyOAuthHomeserver: Fixtures<{}, Services> = { + _homeserver: [ + async ({ _homeserver: container }, use) => { + const server = new OAuthServer(); + const port = server.start(); - await TestContainers.exposeHostPorts(port); - container.withConfig({ - oidc_providers: [ - { - idp_id: "test", - idp_name: "OAuth test", - issuer: `http://localhost:${port}/oauth`, - authorization_endpoint: `http://localhost:${port}/oauth/auth.html`, - // the token endpoint receives requests from synapse, - // rather than the webapp, so needs to escape the docker container. - token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`, - userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`, - client_id: "synapse", - discover: false, - scopes: ["profile"], - skip_verification: true, - client_auth_method: "none", - user_mapping_provider: { - config: { - display_name_template: "{{ user.name }}", + await TestContainers.exposeHostPorts(port); + container.withConfig({ + oidc_providers: [ + { + idp_id: "test", + idp_name: "OAuth test", + issuer: `http://localhost:${port}/oauth`, + authorization_endpoint: `http://localhost:${port}/oauth/auth.html`, + // the token endpoint receives requests from synapse, + // rather than the webapp, so needs to escape the docker container. + token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`, + userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`, + client_id: "synapse", + discover: false, + scopes: ["profile"], + skip_verification: true, + client_auth_method: "none", + user_mapping_provider: { + config: { + display_name_template: "{{ user.name }}", + }, }, }, - }, - ], - }); - await use(container); - server.stop(); - }, + ], + }); + await use(container); + server.stop(); + }, + { scope: "worker" }, + ], }; diff --git a/playwright/plugins/homeserver/synapse/masHomeserver.ts b/playwright/plugins/homeserver/synapse/masHomeserver.ts index cb144fcaeeb..bf2dd13a1c4 100644 --- a/playwright/plugins/homeserver/synapse/masHomeserver.ts +++ b/playwright/plugins/homeserver/synapse/masHomeserver.ts @@ -12,52 +12,55 @@ import { Services } from "../../../services.ts"; import { Fixtures as BaseFixtures } from "../../../element-web-test.ts"; import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts"; -type Fixture = PlaywrightTestArgs & Services & BaseFixtures; -export const masHomeserver: Fixtures = { - mas: async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => { - const config = { - clients: [ - { - client_id: "0000000000000000000SYNAPSE", - client_auth_method: "client_secret_basic", - client_secret: "SomeRandomSecret", +type Fixture = PlaywrightTestArgs & BaseFixtures; +export const masHomeserver: Fixtures = { + mas: [ + async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => { + const config = { + clients: [ + { + client_id: "0000000000000000000SYNAPSE", + client_auth_method: "client_secret_basic", + client_secret: "SomeRandomSecret", + }, + ], + matrix: { + homeserver: "localhost", + secret: "AnotherRandomSecret", + endpoint: "http://homeserver:8008", }, - ], - matrix: { - homeserver: "localhost", - secret: "AnotherRandomSecret", - endpoint: "http://homeserver:8008", - }, - }; + }; - const container = await new MatrixAuthenticationServiceContainer(postgres) - .withNetwork(network) - .withNetworkAliases("mas") - .withLogConsumer(logger.getConsumer("mas")) - .withConfig(config) - .start(); + const container = await new MatrixAuthenticationServiceContainer(postgres) + .withNetwork(network) + .withNetworkAliases("mas") + .withLogConsumer(logger.getConsumer("mas")) + .withConfig(config) + .start(); - homeserver.withConfig({ - enable_registration: undefined, - enable_registration_without_verification: undefined, - disable_msisdn_registration: undefined, - password_config: undefined, - experimental_features: { - msc3861: { - enabled: true, - issuer: `http://mas:8080/`, - introspection_endpoint: "http://mas:8080/oauth2/introspect", - client_id: config.clients[0].client_id, - client_auth_method: config.clients[0].client_auth_method, - client_secret: config.clients[0].client_secret, - admin_token: config.matrix.secret, + homeserver.withConfig({ + enable_registration: undefined, + enable_registration_without_verification: undefined, + disable_msisdn_registration: undefined, + password_config: undefined, + experimental_features: { + msc3861: { + enabled: true, + issuer: `http://mas:8080/`, + introspection_endpoint: "http://mas:8080/oauth2/introspect", + client_id: config.clients[0].client_id, + client_auth_method: config.clients[0].client_auth_method, + client_secret: config.clients[0].client_secret, + admin_token: config.matrix.secret, + }, }, - }, - }); + }); - await use(container); - await container.stop(); - }, + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], config: async ({ homeserver, context, mas }, use) => { const issuer = `${mas.baseUrl}/`; diff --git a/playwright/services.ts b/playwright/services.ts index 58fc21775ea..b480cbc4054 100644 --- a/playwright/services.ts +++ b/playwright/services.ts @@ -30,80 +30,111 @@ export interface Services { mas?: StartedMatrixAuthenticationServiceContainer; } -export const test = base.extend({ - // eslint-disable-next-line no-empty-pattern - logger: async ({}, use, testInfo) => { - const logger = new ContainerLogger(); - await use(logger); - await logger.testFinished(testInfo); - }, - // eslint-disable-next-line no-empty-pattern - network: async ({}, use) => { - const network = await new Network().start(); - await use(network); - await network.stop(); - }, - postgres: async ({ logger, network }, use) => { - const container = await new PostgreSqlContainer() - .withNetwork(network) - .withNetworkAliases("postgres") - .withLogConsumer(logger.getConsumer("postgres")) - .withTmpFs({ - "/dev/shm/pgdata/data": "", - }) - .withEnvironment({ - PG_DATA: "/dev/shm/pgdata/data", - }) - .withCommand([ - "-c", - "shared_buffers=128MB", - "-c", - `fsync=off`, - "-c", - `synchronous_commit=off`, - "-c", - "full_page_writes=off", - ]) - .start(); - await use(container); - await container.stop(); - }, +export const test = base.extend<{}, Services>({ + logger: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + const logger = new ContainerLogger(); + await use(logger); + }, + { scope: "worker" }, + ], + network: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + const network = await new Network().start(); + await use(network); + await network.stop(); + }, + { scope: "worker" }, + ], + postgres: [ + async ({ logger, network }, use) => { + const container = await new PostgreSqlContainer() + .withNetwork(network) + .withNetworkAliases("postgres") + .withLogConsumer(logger.getConsumer("postgres")) + .withTmpFs({ + "/dev/shm/pgdata/data": "", + }) + .withEnvironment({ + PG_DATA: "/dev/shm/pgdata/data", + }) + .withCommand([ + "-c", + "shared_buffers=128MB", + "-c", + `fsync=off`, + "-c", + `synchronous_commit=off`, + "-c", + "full_page_writes=off", + ]) + .start(); + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], - mailhog: async ({ logger, network }, use) => { - const container = await new GenericContainer("mailhog/mailhog:latest") - .withNetwork(network) - .withNetworkAliases("mailhog") - .withExposedPorts(8025) - .withLogConsumer(logger.getConsumer("mailhog")) - .withWaitStrategy(Wait.forListeningPorts()) - .start(); - await use(container); - await container.stop(); - }, - mailhogClient: async ({ mailhog: container }, use) => { - await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) })); - }, + mailhog: [ + async ({ logger, network }, use) => { + const container = await new GenericContainer("mailhog/mailhog:latest") + .withNetwork(network) + .withNetworkAliases("mailhog") + .withExposedPorts(8025) + .withLogConsumer(logger.getConsumer("mailhog")) + .withWaitStrategy(Wait.forListeningPorts()) + .start(); + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], + mailhogClient: [ + async ({ mailhog: container }, use) => { + await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) })); + }, + { scope: "worker" }, + ], - synapseConfigOptions: [{}, { option: true }], - _homeserver: async ({ request }, use) => { - const container = new SynapseContainer(request); - await use(container); - }, - homeserver: async ({ logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => { - const container = await homeserver - .withNetwork(network) - .withNetworkAliases("homeserver") - .withLogConsumer(logger.getConsumer("synapse")) - .withConfig(synapseConfigOptions) - .start(); + synapseConfigOptions: [{}, { option: true, scope: "worker" }], + _homeserver: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + const container = new SynapseContainer(); + await use(container); + }, + { scope: "worker" }, + ], + homeserver: [ + async ({ logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => { + const container = await homeserver + .withNetwork(network) + .withNetworkAliases("homeserver") + .withLogConsumer(logger.getConsumer("synapse")) + .withConfig(synapseConfigOptions) + .start(); - await use(container); - await container.stop(); - }, - // eslint-disable-next-line no-empty-pattern - mas: async ({}, use) => { - // we stub the mas fixture to allow `homeserver` to depend on it to ensure - // when it is specified by `masHomeserver` it is started before the homeserver - await use(undefined); + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], + mas: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + // we stub the mas fixture to allow `homeserver` to depend on it to ensure + // when it is specified by `masHomeserver` it is started before the homeserver + await use(undefined); + }, + { scope: "worker" }, + ], + + context: async ({ logger, context, request, homeserver }, use, testInfo) => { + homeserver.setRequest(request); + await logger.testStarted(testInfo); + await use(context); + await logger.testFinished(testInfo); }, }); diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png index 465e58ca39e..7a108b441c8 100644 Binary files a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png and b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png index cc64e8bf781..c66606efcda 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-with-user-pill-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png index a634e876bc6..02cbff0e3fb 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-dm-without-user-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png index d01aae804c9..fa3cf904303 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-with-user-pill-linux.png differ diff --git a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png index 6e8e2fbddff..910040e20f4 100644 Binary files a/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png and b/playwright/snapshots/invite/invite-dialog.spec.ts/invite-dialog-room-without-user-linux.png differ diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png index c641f2a0aaf..655d45bc4a1 100644 Binary files a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-linux.png differ diff --git a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png index b9d81c5d5a8..e2bd16fb5af 100644 Binary files a/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png and b/playwright/snapshots/settings/account-user-settings-tab.spec.ts/account-smallscreen-linux.png differ diff --git a/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-user-linux.png b/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-user-linux.png index 2fb39b9f5f0..28483d5815c 100644 Binary files a/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-user-linux.png and b/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-user-linux.png differ diff --git a/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png b/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png index bfaa18d4c3e..e2c6718a930 100644 Binary files a/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png and b/playwright/snapshots/user-menu/user-menu.spec.ts/user-menu-linux.png differ diff --git a/playwright/testcontainers/HomeserverContainer.ts b/playwright/testcontainers/HomeserverContainer.ts index bbe075f2c95..09eea7da77a 100644 --- a/playwright/testcontainers/HomeserverContainer.ts +++ b/playwright/testcontainers/HomeserverContainer.ts @@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details. */ import { AbstractStartedContainer, GenericContainer } from "testcontainers"; +import { APIRequestContext } from "@playwright/test"; import { StartedSynapseContainer } from "./synapse.ts"; import { HomeserverInstance } from "../plugins/homeserver"; @@ -16,4 +17,6 @@ export interface HomeserverContainer extends GenericContainer { start(): Promise; } -export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {} +export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance { + setRequest(request: APIRequestContext): void; +} diff --git a/playwright/testcontainers/dendrite.ts b/playwright/testcontainers/dendrite.ts index 484b828b524..629ea70c65c 100644 --- a/playwright/testcontainers/dendrite.ts +++ b/playwright/testcontainers/dendrite.ts @@ -6,7 +6,6 @@ Please see LICENSE files in the repository root for full details. */ import { GenericContainer, Wait } from "testcontainers"; -import { APIRequestContext } from "@playwright/test"; import * as YAML from "yaml"; import { set } from "lodash"; @@ -208,11 +207,7 @@ const DEFAULT_CONFIG = { export class DendriteContainer extends GenericContainer implements HomeserverContainer { private config: typeof DEFAULT_CONFIG; - constructor( - private request: APIRequestContext, - image = "matrixdotorg/dendrite-monolith:main", - binary = "/usr/bin/dendrite", - ) { + constructor(image = "matrixdotorg/dendrite-monolith:main", binary = "/usr/bin/dendrite") { super(image); this.config = deepCopy(DEFAULT_CONFIG); @@ -254,13 +249,12 @@ export class DendriteContainer extends GenericContainer implements HomeserverCon container, `http://${container.getHost()}:${container.getMappedPort(8008)}`, this.config.client_api.registration_shared_secret, - this.request, ); } } export class PineconeContainer extends DendriteContainer { - constructor(request: APIRequestContext) { - super(request, "matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone"); + constructor() { + super("matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone"); } } diff --git a/playwright/testcontainers/synapse.ts b/playwright/testcontainers/synapse.ts index 46a4bcb415c..3b06db2325e 100644 --- a/playwright/testcontainers/synapse.ts +++ b/playwright/testcontainers/synapse.ts @@ -141,7 +141,7 @@ export type SynapseConfigOptions = Partial; export class SynapseContainer extends GenericContainer implements HomeserverContainer { private config: typeof DEFAULT_CONFIG; - constructor(private readonly request: APIRequestContext) { + constructor() { super(`ghcr.io/element-hq/synapse:${TAG}`); this.config = deepCopy(DEFAULT_CONFIG); @@ -221,23 +221,26 @@ export class SynapseContainer extends GenericContainer implements HomeserverCont await super.start(), `http://localhost:${port}`, this.config.registration_shared_secret, - this.request, ); } } export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer { private adminToken?: string; + private request?: APIRequestContext; constructor( container: StartedTestContainer, public readonly baseUrl: string, private readonly registrationSharedSecret: string, - private readonly request: APIRequestContext, ) { super(container); } + public setRequest(request: APIRequestContext): void { + this.request = request; + } + private async registerUserInternal( username: string, password: string, @@ -273,6 +276,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements deviceId: data.device_id, password, displayName, + username, }; } @@ -300,6 +304,7 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements userId: json.user_id, deviceId: json.device_id, homeServer: json.home_server, + username: userId.slice(1).split(":")[0], }; } diff --git a/playwright/testcontainers/utils.ts b/playwright/testcontainers/utils.ts index 36b8d8aabf7..1339e9c2fc1 100644 --- a/playwright/testcontainers/utils.ts +++ b/playwright/testcontainers/utils.ts @@ -24,6 +24,12 @@ export class ContainerLogger { }; } + public async testStarted(testInfo: TestInfo) { + for (const container in this.logs) { + this.logs[container] = ""; + } + } + public async testFinished(testInfo: TestInfo) { if (testInfo.status !== "passed") { for (const container in this.logs) { diff --git a/yarn.lock b/yarn.lock index 57c620081fb..a60f5290eaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1207,36 +1207,26 @@ "@csstools/color-helpers" "^5.0.1" "@csstools/css-calc" "^2.0.2" -"@csstools/css-parser-algorithms@^3.0.2": +"@csstools/css-parser-algorithms@^3.0.1", "@csstools/css-parser-algorithms@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.2.tgz#be03c710a60b34f95ea62e332c9ca0c2674f6d5f" integrity sha512-6tC/MnlEvs5suR4Ahef4YlBccJDHZuxGsAlxXmybWjZ5jPxlzLSMlRZ9mVHSRvlD+CmtE7+hJ+UQbfXrws/rUQ== -"@csstools/css-parser-algorithms@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" - integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== - -"@csstools/css-tokenizer@^3.0.2": +"@csstools/css-tokenizer@^3.0.1", "@csstools/css-tokenizer@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.2.tgz#1c1d7298f6a7b3db94afe53d949b9a7d6a8ebc57" integrity sha512-IuTRcD53WHsXPCZ6W7ubfGqReTJ9Ra0yRRFmXYP/Re8hFYYfoIYIK4080X5luslVLWimhIeFq0hj09urVMQzTw== -"@csstools/css-tokenizer@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" - integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== +"@csstools/media-query-list-parser@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz#9474e08e6d7767cf68c56bf1581b59d203360cb0" + integrity sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw== "@csstools/media-query-list-parser@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.0.tgz#d32a27f1d95f77c2a62c0f21f12a9e84b944bf5f" integrity sha512-nUfbCGeqCju55Po8ujRNQ8DjuKYth5UcsDj5HsVzSfqnaFdpOwYCUAhRJ2grfwrXhb9+KuRXHQ6JHzaI0Qhu8Q== -"@csstools/media-query-list-parser@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a" - integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A== - "@csstools/postcss-cascade-layers@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.0.tgz#ad9985c2d273554552a546f6b1584d03d8886a8d" @@ -1498,11 +1488,6 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz#7dfccb9df5499e627e7bfdbb4021a06813a45dba" integrity sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ== -"@csstools/selector-specificity@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b" - integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== - "@csstools/utilities@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@csstools/utilities/-/utilities-2.0.0.tgz#f7ff0fee38c9ffb5646d47b6906e0bc8868bde60" @@ -1609,46 +1594,46 @@ integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== "@fontsource/inconsolata@^5": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.1.1.tgz#bc5cc74d04dee8b2cb4e706cc33ac3dc50100191" - integrity sha512-jLLMagEJURTae5J30gehIsXRv96vjQ0XlALGxZC7DERWPqsJTa0oSsZR8k6IJfizU4ZeRl/aKWpZca2Lo3TvSg== + version "5.1.0" + resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.1.0.tgz#f6a76680173336d02d2ce4009699821a6be239ce" + integrity sha512-vYPdG3R46MhK+99De8e8MMyNad5BAb1oTnHMpojlctZyWJIcin8bKHFPUpQSNRhZ4HQL/+DCW+RTiG2RbnweTw== "@fontsource/inter@^5": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.1.tgz#401803b6ac4c877f5be94088aa89147ed5a2bd85" - integrity sha512-weN3E+rq0Xb3Z93VHJ+Rc7WOQX9ETJPTAJ+gDcaMHtjft67L58sfS65rAjC5tZUXQ2FdZ/V1/sSzCwZ6v05kJw== + version "5.1.0" + resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47" + integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A== -"@formatjs/ecma402-abstract@2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a" - integrity sha512-6sE5nyvDloULiyOMbOTJEEgWL32w+VHkZQs8S02Lnn8Y/O5aQhjOEXwWzvR7SsBE/exxlSpY2EsWZgqHbtLatg== +"@formatjs/ecma402-abstract@2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" + integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== dependencies: - "@formatjs/fast-memoize" "2.2.6" - "@formatjs/intl-localematcher" "0.5.10" + "@formatjs/fast-memoize" "2.2.5" + "@formatjs/intl-localematcher" "0.5.9" decimal.js "10" tslib "2" -"@formatjs/fast-memoize@2.2.6": - version "2.2.6" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.6.tgz#fac0a84207a1396be1f1aa4ee2805b179e9343d1" - integrity sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw== +"@formatjs/fast-memoize@2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" + integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== dependencies: tslib "2" -"@formatjs/intl-localematcher@0.5.10": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz#1e0bd3fc1332c1fe4540cfa28f07e9227b659a58" - integrity sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q== +"@formatjs/intl-localematcher@0.5.9": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" + integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== dependencies: tslib "2" "@formatjs/intl-segmenter@^11.5.7": - version "11.7.8" - resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.8.tgz#85990c7e3961ef686ed78b8cacb216852cadb061" - integrity sha512-+nqMCJ6LNLl+qXldE2uthF82O/2Yo6GZlyWbOY25fe3066iaHjmrR4nXd6AKRMCHNeBBx3rANFLm2B5cNTBzTQ== + version "11.7.7" + resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.7.tgz#8a5aaa316e11ca2d31b99222e6fcf1ab539b085e" + integrity sha512-610J5xz5DxtEpa16zNR89CrvA9qWHxQFkUB3FKiGao0Nwn7i8cl+oyBhuH9SvtXF9j2LUOM9VMdVCMzJkVANNw== dependencies: - "@formatjs/ecma402-abstract" "2.3.2" - "@formatjs/intl-localematcher" "0.5.10" + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-localematcher" "0.5.9" tslib "2" "@humanwhocodes/config-array@^0.13.0": @@ -2047,9 +2032,9 @@ "@babel/runtime" "^7.17.9" "@matrix-org/spec@^1.7.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.13.0.tgz#30c6c1e6993a93139f49d43e0c8fb9880f2f99ac" - integrity sha512-rvBRT3MRZXhPPclAbDY5L4x6xJbi7yqRAOvD11QWwu/RzFra8DYf58GSy45Kw70QyfyyRMBvKMil8zzY0+LMfA== + version "1.12.0" + resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.12.0.tgz#9e8d37f65dc8029ceeac1da47556e6ba59d44733" + integrity sha512-QHUQ79dMtd0eKQgebRye8li1y/S1172T6fXhu10dsFKtirSWblneoeFnCUR8hsUSBGWLtqBFcryVGa9uoBhqxg== "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -2414,35 +2399,35 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@8.48.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.48.0.tgz#320713e29566929894de42d54152064ec19cc9b3" - integrity sha512-pLtu0Fa1Ou0v3M1OEO1MB1EONJVmXEGtoTwFRCO1RPQI2ulmkG6BikINClFG5IBpoYKZ33WkEXuM6U5xh+pdZg== +"@sentry-internal/browser-utils@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.43.0.tgz#b064908a537d1cc17d8ddaf0f4c5d712557cbf40" + integrity sha512-5WhJZ3SA5sZVDBwOsChDd5JCzYcwBX7sEqBqEcm3pFru6TUihEnFIJmDIbreIyrQMwUhs3dTxnfnidgjr5z1Ag== dependencies: - "@sentry/core" "8.48.0" + "@sentry/core" "8.43.0" -"@sentry-internal/feedback@8.48.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.48.0.tgz#92d2301b0e7379716efae6c05bc4a4740687921a" - integrity sha512-6PwcJNHVPg0EfZxmN+XxVOClfQpv7MBAweV8t9i5l7VFr8sM/7wPNSeU/cG7iK19Ug9ZEkBpzMOe3G4GXJ5bpw== +"@sentry-internal/feedback@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.43.0.tgz#9477b999c9bca62335eb944a6f7246a96beb0111" + integrity sha512-rcGR2kzFu4vLXBQbI9eGJwjyToyjl36O2q/UKbiZBNJ5IFtDvKRLke6jIHq/YqiHPfFGpVtq5M/lYduDfA/eaQ== dependencies: - "@sentry/core" "8.48.0" + "@sentry/core" "8.43.0" -"@sentry-internal/replay-canvas@8.48.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.48.0.tgz#f88282b0594751407ca3016d0a63b133c2e37ac3" - integrity sha512-LdivLfBXXB9us1aAc6XaL7/L2Ob4vi3C/fEOXElehg3qHjX6q6pewiv5wBvVXGX1NfZTRvu+X11k6TZoxKsezw== +"@sentry-internal/replay-canvas@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.43.0.tgz#f5672a08c9eb588afa0bf36f07b9f5c29b5c9920" + integrity sha512-rL8G7E1GtozH8VNalRrBQNjYDJ5ChWS/vpQI5hUG11PZfvQFXEVatLvT3uO2l0xIlHm4idTsHOSLTe/usxnogQ== dependencies: - "@sentry-internal/replay" "8.48.0" - "@sentry/core" "8.48.0" + "@sentry-internal/replay" "8.43.0" + "@sentry/core" "8.43.0" -"@sentry-internal/replay@8.48.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.48.0.tgz#2cc802178f6b0185581b61058f2541b9f3384a8b" - integrity sha512-csILVupc5RkrsTrncuUTGmlB56FQSFjXPYWG8I8yBTGlXEJ+o8oTuF6+55R4vbw3EIzBveXWi4kEBbnQlXW/eg== +"@sentry-internal/replay@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.43.0.tgz#4e2e3844f52b47b16bf816d21857921bbfe85d62" + integrity sha512-geV5/zejLfGGwWHjylzrb1w8NI3U37GMG9/53nmv13FmTXUDF5XF2lh41KXFVYwvp7Ha4bd1FRQ9IU9YtBWskw== dependencies: - "@sentry-internal/browser-utils" "8.48.0" - "@sentry/core" "8.48.0" + "@sentry-internal/browser-utils" "8.43.0" + "@sentry/core" "8.43.0" "@sentry/babel-plugin-component-annotate@2.22.7": version "2.22.7" @@ -2450,15 +2435,15 @@ integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ== "@sentry/browser@^8.0.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.48.0.tgz#bdd7793ddd3ae7a65d595066bde93fbb63ce8b9d" - integrity sha512-fuuVULB5/1vI8NoIwXwR3xwhJJqk+y4RdSdajExGF7nnUDBpwUJyXsmYJnOkBO+oLeEs58xaCpotCKiPUNnE3g== + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.43.0.tgz#4eec67bc6fb278727304045b612ac392674cade6" + integrity sha512-LGvLLnfmR8+AEgFmd7Q7KHiOTiV0P1Lvio2ENDELhEqJOIiICauttibVmig+AW02qg4kMeywvleMsUYaZv2RVA== dependencies: - "@sentry-internal/browser-utils" "8.48.0" - "@sentry-internal/feedback" "8.48.0" - "@sentry-internal/replay" "8.48.0" - "@sentry-internal/replay-canvas" "8.48.0" - "@sentry/core" "8.48.0" + "@sentry-internal/browser-utils" "8.43.0" + "@sentry-internal/feedback" "8.43.0" + "@sentry-internal/replay" "8.43.0" + "@sentry-internal/replay-canvas" "8.43.0" + "@sentry/core" "8.43.0" "@sentry/bundler-plugin-core@2.22.7": version "2.22.7" @@ -2528,10 +2513,10 @@ "@sentry/cli-win32-i686" "2.39.1" "@sentry/cli-win32-x64" "2.39.1" -"@sentry/core@8.48.0": - version "8.48.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.48.0.tgz#3bb8d06305f0ec7c873453844687deafdeab168b" - integrity sha512-VGwYgTfLpvJ5LRO5A+qWo1gpo6SfqaGXL9TOzVgBucAdpzbrYHpZ87sEarDVq/4275uk1b0S293/mfsskFczyw== +"@sentry/core@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.43.0.tgz#e96a489e87a9999199f5ac27d8860da37c1fa8b4" + integrity sha512-ktyovtjkTMNud+kC/XfqHVCoQKreIKgx/hgeRvzPwuPyd1t1KzYmRL3DBkbcWVnyOPpVTHn+RsEI1eRcVYHtvw== "@sentry/webpack-plugin@^2.7.1": version "2.22.7" @@ -2576,9 +2561,9 @@ p-map "^4.0.0" "@stylistic/eslint-plugin@^2.9.0": - version "2.12.1" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz#e341beb4e4315084d8be20bceeeda7d8a46f079f" - integrity sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ== + version "2.11.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz#50d0289f36f7201055b7fa1729fdc1d8c46e93fa" + integrity sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw== dependencies: "@typescript-eslint/utils" "^8.13.0" eslint-visitor-keys "^4.2.0" @@ -2857,9 +2842,9 @@ integrity sha512-aqBg5oAGo/qh/+wxUfuMadDu2WO0MEWOblyzwaM1Ske2xilUxBfgPqapAFVAfrVTDMVwa0UMarzGot8m64IAzA== "@types/css-tree@^2.3.8": - version "2.3.10" - resolved "https://registry.yarnpkg.com/@types/css-tree/-/css-tree-2.3.10.tgz#b96abb37c1b51b03fe16054c186130c6ab69fef7" - integrity sha512-WcaBazJ84RxABvRttQjjFWgTcHvZR9jGr0Y3hccPkHjFyk/a3N8EuxjKr+QfrwjoM5b1yI1Uj1i7EzOAAwBwag== + version "2.3.9" + resolved "https://registry.yarnpkg.com/@types/css-tree/-/css-tree-2.3.9.tgz#54c404e0a803e7e660fdc08c84fe73ee5266cece" + integrity sha512-g1FE6xkPDP4tsccmTd6jIugjKZdxIDqAf9h2pc+4LsGgYbOyfa9phNjBHYbm6FtwIlNfT1NBx3f2zSeqO7aRAw== "@types/diff-match-patch@^1.0.32": version "1.0.36" @@ -3075,9 +3060,9 @@ integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ== "@types/lodash@^4.14.168": - version "4.17.14" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.14.tgz#bafc053533f4cdc5fcc9635af46a963c1f3deaff" - integrity sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A== + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb" + integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg== "@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4": version "0.1.4" @@ -3131,9 +3116,9 @@ undici-types "~6.20.0" "@types/node@18": - version "18.19.70" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.70.tgz#5a77508f5568d16fcd3b711c8102d7a430a04df7" - integrity sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ== + version "18.19.68" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.68.tgz#f4f10d9927a7eaf3568c46a6d739cc0967ccb701" + integrity sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw== dependencies: undici-types "~5.26.4" @@ -3214,9 +3199,11 @@ redux "^4.0.0" "@types/react-transition-group@^4.4.0": - version "4.4.12" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" - integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" + integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== + dependencies: + "@types/react" "*" "@types/react@*", "@types/react@18.3.18": version "18.3.18" @@ -3447,7 +3434,7 @@ semver "^7.6.0" ts-api-utils "^2.0.0" -"@typescript-eslint/utils@8.19.1", "@typescript-eslint/utils@^8.13.0": +"@typescript-eslint/utils@8.19.1": version "8.19.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.19.1.tgz#dd8eabd46b92bf61e573286e1c0ba6bd243a185b" integrity sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA== @@ -3457,7 +3444,7 @@ "@typescript-eslint/types" "8.19.1" "@typescript-eslint/typescript-estree" "8.19.1" -"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.13.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.16.0.tgz#c71264c437157feaa97842809836254a6fc833c3" integrity sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA== @@ -4361,13 +4348,13 @@ braces@^3.0.3, braces@~3.0.2: fill-range "^7.1.1" browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.2: - version "4.24.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" - integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== dependencies: - caniuse-lite "^1.0.30001688" - electron-to-chromium "^1.5.73" - node-releases "^2.0.19" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" update-browserslist-db "^1.1.1" bs58@^6.0.0: @@ -4509,7 +4496,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@1.0.30001690, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688: +caniuse-lite@1.0.30001690, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: version "1.0.30001690" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== @@ -5258,7 +5245,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7: +debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.5: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -5272,7 +5259,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.2, debug@~4.3.6: +debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@~4.3.6: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -5611,10 +5598,10 @@ ejs@^3.1.8: dependencies: jake "^10.8.5" -electron-to-chromium@^1.5.73: - version "1.5.78" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.78.tgz#223cdc76a5d15ac731136e68430e92cb8d612d13" - integrity sha512-UmwIt7HRKN1rsJfddG5UG7rCTCTAKoS9JeOy/R0zSenAyaZ8SU3RuXlwcratxhdxGRNpk03iq8O7BA3W7ibLVw== +electron-to-chromium@^1.5.41: + version "1.5.72" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz#a732805986d3a5b5fedd438ddf4616c7d78ac2df" + integrity sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw== emittery@^0.13.1: version "0.13.1" @@ -6281,7 +6268,7 @@ fast-fifo@^1.2.0, fast-fifo@^1.3.2: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.7: +fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -6292,17 +6279,6 @@ fast-glob@^3.2.7: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.9, fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -6314,9 +6290,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-uri@^3.0.1: - version "3.0.5" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.5.tgz#19f5f9691d0dab9b85861a7bb5d98fca961da9cd" - integrity sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q== + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== fastest-levenshtein@1.0.16, fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.16: version "1.0.16" @@ -6324,9 +6300,9 @@ fastest-levenshtein@1.0.16, fastest-levenshtein@^1.0.12, fastest-levenshtein@^1. integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.18.0.tgz#d631d7e25faffea81887fe5ea8c9010e1b36fee0" - integrity sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw== + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== dependencies: reusify "^1.0.4" @@ -6495,16 +6471,11 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.2.9: +flatted@^3.2.9, flatted@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flatted@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" - integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== - focus-lock@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-1.3.5.tgz#aa644576e5ec47d227b57eb14e1efb2abf33914c" @@ -8268,6 +8239,11 @@ knip@^5.36.2: zod "^3.22.4" zod-validation-error "^3.0.3" +known-css-properties@^0.34.0: + version "0.34.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.34.0.tgz#ccd7e9f4388302231b3f174a8b1d5b1f7b576cea" + integrity sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ== + known-css-properties@^0.35.0: version "0.35.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.35.0.tgz#f6f8e40ab4e5700fa32f5b2ef5218a56bc853bd6" @@ -8950,7 +8926,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.19: +node-releases@^2.0.18: version "2.0.19" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== @@ -9367,7 +9343,7 @@ pbf@^3.2.1, pbf@^3.3.0: ieee754 "^1.1.12" resolve-protobuf-schema "^2.1.0" -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1: +picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -10047,7 +10023,7 @@ postcss@8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38: +postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -10056,15 +10032,6 @@ postcss@^8.3.11, postcss@^8.4.33, postcss@^8.4.38: picocolors "^1.1.0" source-map-js "^1.2.1" -postcss@^8.4.49: - version "8.4.49" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" - integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.1" - source-map-js "^1.2.1" - posthog-js@1.157.2: version "1.157.2" resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.157.2.tgz#dc2515818ead408aefb900e90c535fb57beb1f59" @@ -11476,20 +11443,20 @@ stylelint-value-no-unknown-custom-properties@^6.0.1: resolve "^1.22.8" stylelint@^16.1.0: - version "16.12.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.12.0.tgz#13532dcbaed21348da0e9e0fb9a4e1e7f6dab2b8" - integrity sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg== - dependencies: - "@csstools/css-parser-algorithms" "^3.0.4" - "@csstools/css-tokenizer" "^3.0.3" - "@csstools/media-query-list-parser" "^4.0.2" - "@csstools/selector-specificity" "^5.0.0" + version "16.10.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.10.0.tgz#452b42a5d82f2ad910954eb2ba2b3a2ec583cd75" + integrity sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.1" + "@csstools/css-tokenizer" "^3.0.1" + "@csstools/media-query-list-parser" "^3.0.1" + "@csstools/selector-specificity" "^4.0.0" "@dual-bundle/import-meta-resolve" "^4.1.0" balanced-match "^2.0.0" colord "^2.9.3" cosmiconfig "^9.0.0" css-functions-list "^3.2.3" - css-tree "^3.0.1" + css-tree "^3.0.0" debug "^4.3.7" fast-glob "^3.3.2" fastest-levenshtein "^1.0.16" @@ -11501,22 +11468,22 @@ stylelint@^16.1.0: ignore "^6.0.2" imurmurhash "^0.1.4" is-plain-object "^5.0.0" - known-css-properties "^0.35.0" + known-css-properties "^0.34.0" mathml-tag-names "^2.1.3" meow "^13.2.0" micromatch "^4.0.8" normalize-path "^3.0.0" - picocolors "^1.1.1" - postcss "^8.4.49" + picocolors "^1.0.1" + postcss "^8.4.47" postcss-resolve-nested-selector "^0.1.6" postcss-safe-parser "^7.0.1" - postcss-selector-parser "^7.0.0" + postcss-selector-parser "^6.1.2" postcss-value-parser "^4.2.0" resolve-from "^5.0.0" string-width "^4.2.3" supports-hyperlinks "^3.1.0" svg-tags "^1.0.0" - table "^6.9.0" + table "^6.8.2" write-file-atomic "^5.0.1" sugarss@^4.0.1: @@ -11603,10 +11570,10 @@ tabbable@^6.0.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== -table@^6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" - integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== +table@^6.8.2: + version "6.8.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2"