From 0d1488af4c99f5a4ca4663f0cc3d2ebe91356f67 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 14 Jan 2026 17:58:51 +0100 Subject: [PATCH 1/6] playwright: Federated call between legacy and compat --- playwright/fixtures/widget-user.ts | 10 +-- playwright/widget/federated-call.test.ts | 71 +++++++++++++++++++++ playwright/widget/huddle-call.test.ts | 12 ++-- playwright/widget/test-helpers.ts | 78 +++++++++++++++++++++++- 4 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 playwright/widget/federated-call.test.ts diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index eeddda47..31422fcc 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -9,7 +9,7 @@ Please see LICENSE in the repository root for full details. import { type Page, test, expect, type JSHandle } from "@playwright/test"; import type { MatrixClient } from "matrix-js-sdk"; -import { TestHelpers } from "../widget/test-helpers.ts"; +import { HOST1, TestHelpers } from "../widget/test-helpers.ts"; export type UserBaseFixture = { mxId: string; @@ -26,9 +26,7 @@ export type BaseWidgetSetup = { export interface MyFixtures { asWidget: BaseWidgetSetup; callType: "room" | "dm"; - addUser: ( - username: string /**, homeserver: string*/, - ) => Promise; + addUser: (username: string, host: string) => Promise; } // Minimal config.json for the local element-web instance @@ -174,12 +172,14 @@ export const widgetTest = test.extend({ addUser: async ({ browser }, use) => { await use( async ( - username: string /**, homeserver?: string*/, + username: string, + host: string = HOST1, ): Promise => { const uniqueSuffix = Date.now(); const { page, clientHandle, mxId } = await TestHelpers.registerUser( browser, `${username.toLowerCase()}_${uniqueSuffix}`, + host, ); return { mxId, diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts new file mode 100644 index 00000000..c7f6ac18 --- /dev/null +++ b/playwright/widget/federated-call.test.ts @@ -0,0 +1,71 @@ +/* +Copyright 2026 Element Creations 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"; + +import { widgetTest } from "../fixtures/widget-user"; +import { HOST1, HOST2, TestHelpers } from "./test-helpers"; + +widgetTest( + "Test federated call default rtc mode", + async ({ addUser, browserName }) => { + test.skip( + browserName === "firefox", + "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", + ); + + const florian = await addUser("floriant", HOST1); + const timo = await addUser("timo", HOST2); + + const roomName = "Call Room"; + + await TestHelpers.createRoom(roomName, florian.page, [timo.mxId]); + + await TestHelpers.acceptRoomInvite(roomName, timo.page); + + await florian.page.pause(); + + await TestHelpers.setEmbeddedElementCallRtcMode(florian.page, "legacy"); + await TestHelpers.setEmbeddedElementCallRtcMode(timo.page, "compat"); + + await TestHelpers.startCallInCurrentRoom(florian.page, false); + await TestHelpers.joinCallFromLobby(florian.page); + + // timo joins + await TestHelpers.joinCallInCurrentRoom(timo.page); + + // We should see 2 video tiles everywhere now + for (const user of [timo, florian]) { + const frame = user.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + await expect(frame.getByTestId("videoTile")).toHaveCount(2); + + // There are no other options than to wait for all media to be ready? + // Or it is too flaky :/ + await user.page.waitForTimeout(3000); + // No one should be waiting for media + await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); + + // There should be 5 video elements, visible and autoplaying + const videoElements = await frame.locator("video").all(); + expect(videoElements.length).toBe(2); + + const blockDisplayCount = await frame + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + expect(blockDisplayCount).toBe(2); + } + + // await florian.page.pause(); + }, +); diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 6acf176c..177ec585 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user.ts"; -import { TestHelpers } from "./test-helpers.ts"; +import { HOST1, TestHelpers } from "./test-helpers.ts"; widgetTest("Create and join a group call", async ({ addUser, browserName }) => { test.skip( @@ -18,11 +18,11 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { test.slow(); // We are registering multiple users here, give it more time - const valere = await addUser("Valere"); - const timo = await addUser("Timo"); - const robin = await addUser("Robin"); - const halfshot = await addUser("Halfshot"); - const florian = await addUser("florian"); + const valere = await addUser("Valere", HOST1); + const timo = await addUser("Timo", HOST1); + const robin = await addUser("Robin", HOST1); + const halfshot = await addUser("Halfshot", HOST1); + const florian = await addUser("florian", HOST1); const roomName = "Group Call Room"; await TestHelpers.createRoom(roomName, valere.page, [ diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 4ef05ef4..4d1a048a 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -15,6 +15,9 @@ import { type MatrixClient } from "matrix-js-sdk"; const PASSWORD = "foobarbaz1!"; +export const HOST1 = "https://app.m.localhost/#/welcome"; +export const HOST2 = "https://app.othersite.m.localhost/#/welcome"; + export class TestHelpers { public static async startCallInCurrentRoom( page: Page, @@ -31,12 +34,38 @@ export class TestHelpers { await page.getByRole("menuitem", { name: "Element Call" }).click(); } + public static async joinCallFromLobby(page: Page): Promise { + await expect( + page + .locator('iframe[title="Element Call"]') + .contentFrame() + .getByTestId("lobby_joinCall"), + ).toBeVisible(); + + await page + .locator('iframe[title="Element Call"]') + .contentFrame() + .getByTestId("lobby_joinCall") + .click(); + } + + public static async joinCallInCurrentRoom( + page: Page, + audioOnly: boolean = false, + ): Promise { + const label = audioOnly ? "Voice call started" : "Video call started"; + await expect(page.getByText(label)).toBeVisible(); + await expect(page.getByRole("button", { name: "Join" })).toBeVisible(); + await page.getByRole("button", { name: "Join" }).click(); + } + /** * Registers a new user and returns page, clientHandle and mxId. */ public static async registerUser( browser: Browser, username: string, + host: string = HOST1, ): Promise<{ page: Page; clientHandle: JSHandle; @@ -46,7 +75,7 @@ export class TestHelpers { reducedMotion: "reduce", }); const page = await userContext.newPage(); - await page.goto("http://localhost:8081/#/welcome"); + await page.goto(host); await page.getByRole("link", { name: "Create Account" }).click(); await page.getByRole("textbox", { name: "Username" }).fill(username); await page @@ -128,6 +157,53 @@ export class TestHelpers { } } + public static async acceptRoomInvite( + roomName: string, + page: Page, + ): Promise { + await expect(page.getByRole("option", { name: roomName })).toBeVisible(); + await page.getByRole("option", { name: roomName }).click(); + await page.getByRole("button", { name: "Accept" }).click(); + + await expect( + page.getByRole("main").getByRole("heading", { name: roomName }), + ).toBeVisible(); + } + + /** + * Opens the widget and then goes to the settings to set the RTC mode. + * then closes the widget lobby. + * + * WORKS IF A ROOM IS CURRENTLY OPENED IN THE PAGE + */ + public static async setEmbeddedElementCallRtcMode( + page: Page, + mode: "legacy" | "compat" | "2_0", + ): Promise { + await page.getByRole("button", { name: "Video call" }).click(); + await page.getByRole("menuitem", { name: "Element Call" }).click(); + const iframe = page.locator('iframe[title="Element Call"]').contentFrame(); + + await iframe.getByRole("button", { name: "Settings" }).click(); + await iframe.getByRole("tab", { name: "Preferences" }).click(); + + // await iframe.getByText("Developer mode", { exact: true }).click(); + await iframe.getByText("Developer mode", { exact: true }).check(); // Idempotent: won't uncheck if already checked + + // Move to Developer tab now + await iframe.getByRole("tab", { name: "Developer" }).click(); + if (mode === "legacy") { + await iframe.getByText("Legacy: state events").click(); + } else if (mode === "2_0") { + await iframe.getByText("Matrix 2.0").click(); + } else { + // compat + await iframe.getByText("Compatibility: state events").click(); + } + await iframe.getByTestId("modal_close").click(); + await page.getByRole("button", { name: "Close lobby" }).click(); + } + /** * Sets the current Element Web app to use the dev Element Call URL. * @param page - The EW page From 243f369a24fa8de0b4e83be930a74e33b22c5dd7 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 15 Jan 2026 11:50:37 +0100 Subject: [PATCH 2/6] re-use test helper in other files --- playwright/widget/huddle-call.test.ts | 30 +++------------------------ playwright/widget/test-helpers.ts | 7 ++++++- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 177ec585..b42c0ab2 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -36,15 +36,7 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { // Accept the invite // This isn't super stable to get this as this super generic locator, // but it works for now. - await expect( - user.page.getByRole("option", { name: roomName }), - ).toBeVisible(); - await user.page.getByRole("option", { name: roomName }).click(); - await user.page.getByRole("button", { name: "Accept" }).click(); - - await expect( - user.page.getByRole("main").getByRole("heading", { name: roomName }), - ).toBeVisible(); + await TestHelpers.acceptRoomInvite(roomName, user.page); } // Start the call as Valere @@ -53,24 +45,10 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { valere.page.locator('iframe[title="Element Call"]'), ).toBeVisible(); - await expect( - valere.page - .locator('iframe[title="Element Call"]') - .contentFrame() - .getByTestId("lobby_joinCall"), - ).toBeVisible(); - - await valere.page - .locator('iframe[title="Element Call"]') - .contentFrame() - .getByTestId("lobby_joinCall") - .click(); + await TestHelpers.joinCallFromLobby(valere.page); for (const user of [timo, robin, halfshot, florian]) { - // THis is the header button that notifies about an ongoing call - await expect(user.page.getByText("Video call started")).toBeVisible(); - await expect(user.page.getByRole("button", { name: "Join" })).toBeVisible(); - await user.page.getByRole("button", { name: "Join" }).click(); + await TestHelpers.joinCallInCurrentRoom(user.page); } for (const user of [timo, robin, halfshot, florian]) { @@ -155,6 +133,4 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { // this kind of stuff way easier to test if we could look out for aria attributes. expect(blockDisplayCount).toBe(4); } - - await valere.page.pause(); }); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 4d1a048a..18db48de 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -53,6 +53,7 @@ export class TestHelpers { page: Page, audioOnly: boolean = false, ): Promise { + // This is the header button that notifies about an ongoing call const label = audioOnly ? "Voice call started" : "Video call started"; await expect(page.getByText(label)).toBeVisible(); await expect(page.getByRole("button", { name: "Join" })).toBeVisible(); @@ -128,7 +129,6 @@ export class TestHelpers { page: Page, andInvite: string[] = [], ): Promise { - await page.pause(); await page .getByRole("navigation", { name: "Room list" }) .getByRole("button", { name: "New conversation" }) @@ -157,6 +157,11 @@ export class TestHelpers { } } + /** + * Accepts a room invite using the room name. + * Locatest the invite in the room list. + * + */ public static async acceptRoomInvite( roomName: string, page: Page, From d25d6d319632823564b095361d9268b0159c800d Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 15 Jan 2026 12:06:41 +0100 Subject: [PATCH 3/6] Test all pairs of rtc mode in federated calls --- playwright/widget/federated-call.test.ts | 103 ++++++++++++----------- playwright/widget/test-helpers.ts | 8 +- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts index c7f6ac18..51ed1b41 100644 --- a/playwright/widget/federated-call.test.ts +++ b/playwright/widget/federated-call.test.ts @@ -8,64 +8,73 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user"; -import { HOST1, HOST2, TestHelpers } from "./test-helpers"; +import { HOST1, HOST2, type RtcMode, TestHelpers } from "./test-helpers"; -widgetTest( - "Test federated call default rtc mode", - async ({ addUser, browserName }) => { - test.skip( - browserName === "firefox", - "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", - ); +const modePairs: [RtcMode, RtcMode][] = [ + ["compat", "compat"], + ["legacy", "legacy"], + ["legacy", "compat"], + ["compat", "legacy"], +]; - const florian = await addUser("floriant", HOST1); - const timo = await addUser("timo", HOST2); +modePairs.forEach(([rtcMode1, rtcMode2]) => { + widgetTest( + `Test federated call with rtc modes ${rtcMode1} and ${rtcMode2}`, + async ({ addUser, browserName }) => { + test.skip( + browserName === "firefox", + "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", + ); - const roomName = "Call Room"; + const florian = await addUser("floriant", HOST1); + const timo = await addUser("timo", HOST2); - await TestHelpers.createRoom(roomName, florian.page, [timo.mxId]); + const roomName = "Call Room"; - await TestHelpers.acceptRoomInvite(roomName, timo.page); + await TestHelpers.createRoom(roomName, florian.page, [timo.mxId]); - await florian.page.pause(); + await TestHelpers.acceptRoomInvite(roomName, timo.page); - await TestHelpers.setEmbeddedElementCallRtcMode(florian.page, "legacy"); - await TestHelpers.setEmbeddedElementCallRtcMode(timo.page, "compat"); + await florian.page.pause(); - await TestHelpers.startCallInCurrentRoom(florian.page, false); - await TestHelpers.joinCallFromLobby(florian.page); + await TestHelpers.setEmbeddedElementCallRtcMode(florian.page, rtcMode1); + await TestHelpers.setEmbeddedElementCallRtcMode(timo.page, rtcMode2); - // timo joins - await TestHelpers.joinCallInCurrentRoom(timo.page); + await TestHelpers.startCallInCurrentRoom(florian.page, false); + await TestHelpers.joinCallFromLobby(florian.page); - // We should see 2 video tiles everywhere now - for (const user of [timo, florian]) { - const frame = user.page - .locator('iframe[title="Element Call"]') - .contentFrame(); - await expect(frame.getByTestId("videoTile")).toHaveCount(2); + // timo joins + await TestHelpers.joinCallInCurrentRoom(timo.page); - // There are no other options than to wait for all media to be ready? - // Or it is too flaky :/ - await user.page.waitForTimeout(3000); - // No one should be waiting for media - await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); + // We should see 2 video tiles everywhere now + for (const user of [timo, florian]) { + const frame = user.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + await expect(frame.getByTestId("videoTile")).toHaveCount(2); - // There should be 5 video elements, visible and autoplaying - const videoElements = await frame.locator("video").all(); - expect(videoElements.length).toBe(2); + // There are no other options than to wait for all media to be ready? + // Or it is too flaky :/ + await user.page.waitForTimeout(3000); + // No one should be waiting for media + await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(2); - } + // There should be 5 video elements, visible and autoplaying + const videoElements = await frame.locator("video").all(); + expect(videoElements.length).toBe(2); - // await florian.page.pause(); - }, -); + const blockDisplayCount = await frame + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + expect(blockDisplayCount).toBe(2); + } + + // await florian.page.pause(); + }, + ); +}); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 18db48de..b408a791 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -18,6 +18,8 @@ const PASSWORD = "foobarbaz1!"; export const HOST1 = "https://app.m.localhost/#/welcome"; export const HOST2 = "https://app.othersite.m.localhost/#/welcome"; +export type RtcMode = "legacy" | "compat" | "2_0"; + export class TestHelpers { public static async startCallInCurrentRoom( page: Page, @@ -183,7 +185,7 @@ export class TestHelpers { */ public static async setEmbeddedElementCallRtcMode( page: Page, - mode: "legacy" | "compat" | "2_0", + mode: RtcMode, ): Promise { await page.getByRole("button", { name: "Video call" }).click(); await page.getByRole("menuitem", { name: "Element Call" }).click(); @@ -197,9 +199,9 @@ export class TestHelpers { // Move to Developer tab now await iframe.getByRole("tab", { name: "Developer" }).click(); - if (mode === "legacy") { + if (mode == "legacy") { await iframe.getByText("Legacy: state events").click(); - } else if (mode === "2_0") { + } else if (mode == "2_0") { await iframe.getByText("Matrix 2.0").click(); } else { // compat From 9e933b74ca565b9c1cf226cc5f672ca14189b086 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 15 Jan 2026 14:31:33 +0100 Subject: [PATCH 4/6] cleanup notifications toast on start --- playwright/widget/test-helpers.ts | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index b408a791..226e414e 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -81,6 +81,7 @@ export class TestHelpers { await page.goto(host); await page.getByRole("link", { name: "Create Account" }).click(); await page.getByRole("textbox", { name: "Username" }).fill(username); + await page.getByRole("textbox", { name: "Password", exact: true }).click(); await page .getByRole("textbox", { name: "Password", exact: true }) .fill(PASSWORD); @@ -97,21 +98,10 @@ export class TestHelpers { timeout: 15_000, }); - const browserUnsupportedToast = page - .getByText("Element does not support this browser") - .locator("..") - .locator(".."); - - // Dismiss incompatible browser toast - const dismissButton = browserUnsupportedToast.getByRole("button", { - name: "Dismiss", - }); - try { - await expect(dismissButton).toBeVisible({ timeout: 700 }); - await dismissButton.click(); - } catch { - // dismissButton not visible, continue as normal - } + // Clean up any toasts that may block the screen + await TestHelpers.closeNotificationToast(page); + // focus the user menu to avoid having hover decoration + await page.getByRole("button", { name: "User menu" }).focus(); await TestHelpers.setDevToolElementCallDevUrl(page); @@ -126,6 +116,17 @@ export class TestHelpers { return { page, clientHandle, mxId }; } + /** + * Close the notification toast + */ + public static async closeNotificationToast(page: Page): Promise { + // Dismiss "Notification" toast + return page + .locator(".mx_Toast_toast", { hasText: "Notifications" }) + .getByRole("button", { name: "Dismiss" }) + .click(); + } + public static async createRoom( name: string, page: Page, From a00c6501c256d58f0444ce8aad0b713a9a2cc3de Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 15 Jan 2026 15:22:04 +0100 Subject: [PATCH 5/6] fix discarding pop ups --- playwright/widget/test-helpers.ts | 51 +++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 226e414e..63092a4d 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -98,10 +98,8 @@ export class TestHelpers { timeout: 15_000, }); - // Clean up any toasts that may block the screen - await TestHelpers.closeNotificationToast(page); - // focus the user menu to avoid having hover decoration - await page.getByRole("button", { name: "User menu" }).focus(); + await this.maybeDismissBrowserNotSupportedToast(page); + await this.maybeDismissServiceWorkerWarningToast(page); await TestHelpers.setDevToolElementCallDevUrl(page); @@ -116,15 +114,42 @@ export class TestHelpers { return { page, clientHandle, mxId }; } - /** - * Close the notification toast - */ - public static async closeNotificationToast(page: Page): Promise { - // Dismiss "Notification" toast - return page - .locator(".mx_Toast_toast", { hasText: "Notifications" }) - .getByRole("button", { name: "Dismiss" }) - .click(); + private static async maybeDismissBrowserNotSupportedToast( + page: Page, + ): Promise { + const browserUnsupportedToast = page + .getByText("Element does not support this browser") + .locator("..") + .locator(".."); + + // Dismiss incompatible browser toast + const dismissButton = browserUnsupportedToast.getByRole("button", { + name: "Dismiss", + }); + try { + await expect(dismissButton).toBeVisible({ timeout: 700 }); + await dismissButton.click(); + } catch { + // dismissButton not visible, continue as normal + } + } + + private static async maybeDismissServiceWorkerWarningToast( + page: Page, + ): Promise { + const toast = page + .locator(".mx_Toast_toast") + .getByText("Failed to load service worker"); + + try { + await expect(toast).toBeVisible({ timeout: 700 }); + await page + .locator(".mx_Toast_toast") + .getByRole("button", { name: "OK" }) + .click(); + } catch { + // toast not visible, continue as normal + } } public static async createRoom( From 16b08994cc418e38deb9a00a44a9a99b83cb713a Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 16 Jan 2026 12:53:10 +0100 Subject: [PATCH 6/6] Try fixup mixed conten error loading widget --- backend/dev_nginx.conf | 24 +++++++++++++++++++++--- playwright/widget/test-helpers.ts | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index be015060..d3ddbc53 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -181,7 +181,10 @@ server { } -# Convenience reverse proxy for the call.m.localhost domain to yarn dev --host +# Convenience reverse proxy for the call.m.localhost domain to element call +# running on the host either via +# - yarn dev --host or +# - falling back to http (the element call docker container) server { listen 80; listen [::]:80; @@ -197,7 +200,7 @@ server { ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; - + # 1. Attempt HTTPS first location ^~ / { proxy_set_header Host $host; @@ -208,8 +211,23 @@ server { proxy_pass https://host.docker.internal:3000; proxy_ssl_verify off; + # 2. Redirect specific errors (e.g., 502 Bad Gateway or 504 Timeout) + # to the named fallback location + error_page 502 503 504 = @http_fallback; + + } + + # 3. Fallback location using HTTP + location @http_fallback { + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_pass http://host.docker.internal:8080; + } - error_page 500 502 503 504 /50x.html; } diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 63092a4d..ff3cb121 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -248,7 +248,7 @@ export class TestHelpers { "Developer.elementCallUrl", null, "device", - "http://localhost:8080/room", + "https://call.m.localhost/room", ); }); } else {