Fix the interactivity of buttons while reconnecting or in earpiece mode (#3486)

* Fix the interactivity of buttons while reconnecting or in earpiece mode

When we're in one of these modes, we need to ensure that everything above the overlay (the header and footer buttons) is interactive, while everything obscured by the overlay (the media tiles) is non-interactive and removed from the accessibility tree. It's not a very easy task to trap focus *outside* an element, so the best solution I could come up with is to set tabindex="-1" manually on all interactive elements belonging to the media tiles.

* Write a Playwright test for reconnecting

* fix lints

Signed-off-by: Timo K <toger5@hotmail.de>

* fix test

Signed-off-by: Timo K <toger5@hotmail.de>

* enable http2 for matrx-rtc host to allow the jwt service to talk to the SFU

* remove rate limit for delayed events

* more time to connect to livekit SFU

* Due to a Firefox issue we set the start anchor for the tab test to the Mute microphone button

* adapt to most recent Element Web version

* Use the "End call" button as proofe for a started call

* Currrenty disabled due to recent Element Web
- not indicating the number of participants
- bypassing Lobby

* linting

* disable 'can only interact with header and footer while reconnecting' for firefox

---------

Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Timo <16718859+toger5@users.noreply.github.com>
Co-authored-by: Timo K <toger5@hotmail.de>
Co-authored-by: fkwp <github-fkwp@w4ve.de>
This commit is contained in:
Robin
2025-09-18 12:58:47 +02:00
committed by GitHub
parent df7bd8ff2b
commit 4be395500f
18 changed files with 182 additions and 48 deletions

View File

@@ -159,8 +159,8 @@ export const widgetTest = test.extend<MyFixtures>({
} = await registerUser(browser, userB);
// Invite the second user
await ewPage1.getByRole("button", { name: "Add room" }).click();
await ewPage1.getByText("New room").click();
await ewPage1.getByRole("button", { name: "Add", exact: true }).click();
await ewPage1.getByRole("menuitem", { name: "New Room" }).click();
await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room");
await ewPage1.getByRole("button", { name: "Create room" }).click();
await expect(ewPage1.getByText("You created this room.")).toBeVisible();
@@ -184,9 +184,9 @@ export const widgetTest = test.extend<MyFixtures>({
// Accept the invite
await expect(
ewPage2.getByRole("treeitem", { name: "Welcome Room" }),
ewPage2.getByRole("option", { name: "Welcome Room" }),
).toBeVisible();
await ewPage2.getByRole("treeitem", { name: "Welcome Room" }).click();
await ewPage2.getByRole("option", { name: "Welcome Room" }).click();
await ewPage2.getByRole("button", { name: "Accept" }).click();
await expect(
ewPage2.getByRole("main").getByRole("heading", { name: "Welcome Room" }),

View File

@@ -0,0 +1,60 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { expect, test } from "@playwright/test";
// Skip test for Firefox, due to page.keyboard.press("Tab") not reliable on headless mode
test.skip(
({ browserName }) => browserName === "firefox",
'This test is not working on firefox, page.keyboard.press("Tab") not reliable in headless mode',
);
test("can only interact with header and footer while reconnecting", async ({
page,
}) => {
await page.goto("/");
await page.getByTestId("home_callName").click();
await page.getByTestId("home_callName").fill("Test call");
await page.getByTestId("home_displayName").click();
await page.getByTestId("home_displayName").fill("Test user");
// If we do not call fastForward here, we end up with Date.now() returning an actual timestamp
// but once we call `await page.clock.fastForward(20000);` later this will reset Date.now() to 0
// and we will never get into probablyDisconnected state?
await page.clock.fastForward(10);
await page.getByTestId("home_go").click();
await expect(page.locator("video")).toBeVisible();
await expect(page.getByTestId("lobby_joinCall")).toBeVisible();
// Join the call
await page.getByTestId("lobby_joinCall").click();
// The media tile for the local user should become visible
await new Promise((resolve) => setTimeout(resolve, 1500));
await expect(page.getByTestId("name_tag")).toContainText("Test user");
// Now disconnect from the internet
await page.route("https://synapse.m.localhost/**/*", async (route) => {
await new Promise((resolve) => setTimeout(resolve, 10000));
await route.continue();
});
await page.clock.fastForward(20000);
await expect(
page.getByRole("dialog", { name: "Reconnecting…" }),
).toBeVisible();
// Tab order should jump directly from header to footer, skipping media tiles
await page.getByRole("button", { name: "Mute microphone" }).focus();
await expect(
page.getByRole("button", { name: "Mute microphone" }),
).toBeFocused();
await page.keyboard.press("Tab");
await expect(page.getByRole("button", { name: "Stop video" })).toBeFocused();
// Most critically, we should be able to press the hangup button
await page.getByRole("button", { name: "End call" }).click();
});

View File

@@ -49,7 +49,10 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => {
// Check the join indicator on the room list
await expect(
brooks.page.locator("div").filter({ hasText: /^Joined • 1$/ }),
brooks.page
.locator('iframe[title="Element Call"]')
.contentFrame()
.getByRole("button", { name: "End call" }),
).toBeVisible();
// Join from the other side
@@ -59,26 +62,28 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => {
).toBeVisible();
await whistler.page.getByRole("button", { name: "Join" }).click();
await expect(
whistler.page
.locator('iframe[title="Element Call"]')
.contentFrame()
.getByTestId("lobby_joinCall"),
).toBeVisible();
// Currently disabled due to recent Element Web is bypassing Lobby
// await expect(
// whistler.page
// .locator('iframe[title="Element Call"]')
// .contentFrame()
// .getByTestId("lobby_joinCall"),
// ).toBeVisible();
//
// await whistler.page
// .locator('iframe[title="Element Call"]')
// .contentFrame()
// .getByTestId("lobby_joinCall")
// .click();
await whistler.page
.locator('iframe[title="Element Call"]')
.contentFrame()
.getByTestId("lobby_joinCall")
.click();
// Currrenty disabled due to recent Element Web not indicating the number of participants
// await expect(
// whistler.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
// ).toBeVisible();
await expect(
whistler.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
).toBeVisible();
await expect(
brooks.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
).toBeVisible();
// await expect(
// brooks.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
// ).toBeVisible();
// Whistler leaves
await whistler.page.waitForTimeout(1000);