diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..0ed1ee4a --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,35 @@ +name: Playwright Tests +on: + pull_request: {} + push: + branches: [livekit, full-mesh] +jobs: + test: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Enable Corepack + run: corepack enable + - uses: actions/setup-node@v4 + with: + cache: "yarn" + node-version-file: ".node-version" + - name: Install dependencies + run: yarn install --frozen-lockfile + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run backend components + run: | + docker compose -f dev-backend-docker-compose.yml up -d + docker ps + - name: Copy config file + run: cp config/config.devenv.json public/config.json + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 3 diff --git a/.gitignore b/.gitignore index ff40eccc..33c19918 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ dist-ssr public/config.json backend/synapse_tmp/* /coverage + +# Yarn yarn-error.log /.pnp.* /.yarn/* @@ -18,3 +20,9 @@ yarn-error.log !/.yarn/sdks !/.yarn/versions /.links.yaml + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/README.md b/README.md index 2970fa79..848793fe 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,64 @@ yarn backend # podman-compose -f dev-backend-docker-compose.yml up ``` +### Playwright tests + +Our Playwright tests run automatically as part of our CI along with our other tests, +on every pull request. + +You may need to follow instructions to set up your development environment for running +Playwright by following and +. + +However the Playwright tests are run, an element-call instance must be running on +https://localhost:3000 (this is configured in `playwright.config.ts`) - this is what will +be tested. + +The local backend environment should be running for the test to work: +`yarn backend` + +There are a few different ways to run the tests yourself. The simplest is to run: + +```shell +yarn run test:playwright +``` + +This will run the Playwright tests once, non-interactively. + +There is a more user-friendly way to run the tests in interactive mode: + +```shell +yarn run test:playwright:open +``` + +The easiest way to develop new test is to use the codegen feature of Playwright: + +```shell +npx playwright codegen +``` + +This will record your action and write the test code for you. Use the tool bar to test visibility, text content, +clicking. + +##### Investigate a failed test from the CI + +In the failed action page, click on the failed job, then scroll down to the `upload-artifact` step. +You will find a link to download the zip report, as per: + +``` +Artifact playwright-report has been successfully uploaded! Final size is 1360358 bytes. Artifact ID is 2746265841 +Artifact download URL: https://github.com/element-hq/element-call/actions/runs/13837660687/artifacts/2746265841 +``` + +Unzip the report then use this command to open the report in your browser: + +```shell +npx playwright show-report ~/Downloads/playwright-report/ +``` + +Under the failed test there is a small icon looking like "3 columns" (next to test name file name), +click on it to see the live screenshots/console output. + ### Test Coverage diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 8282057d..37121d3d 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -46,10 +46,15 @@ experimental_features: max_event_delay_duration: 24h rc_message: - # This needs to match at least the heart-beat frequency plus a bit of headroom - # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s + # This needs to match at least e2ee key sharing frequency plus a bit of headroom + # Note key sharing events are bursty per_second: 0.5 burst_count: 30 + # This needs to match at least the heart-beat frequency plus a bit of headroom + # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s +rc_delayed_event_mgmt: + per_second: 1 + burst_count: 20 ``` ### MatrixRTC Backend diff --git a/package.json b/package.json index ba8c110a..913ef06a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "i18n:check": "i18next --fail-on-warnings --fail-on-update", "test": "vitest", "test:coverage": "vitest --coverage", - "backend": "docker-compose -f dev-backend-docker-compose.yml up" + "backend": "docker-compose -f dev-backend-docker-compose.yml up", + "test:playwright": "playwright test", + "test:playwright:open": "yarn test:playwright --ui" }, "devDependencies": { "@babel/core": "^7.16.5", @@ -43,6 +45,7 @@ "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/sdk-trace-web": "^1.9.1", "@opentelemetry/semantic-conventions": "^1.25.1", + "@playwright/test": "^1.51.0", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-visually-hidden": "^1.0.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..0724a43a --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,76 @@ +/* +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 { defineConfig, devices } from "@playwright/test"; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./playwright", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "https://localhost:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + permissions: ["microphone", "camera"], + ignoreHTTPSErrors: true, + launchOptions: { + args: [ + "--use-fake-ui-for-media-stream", + "--use-fake-device-for-media-stream", + "--mute-audio", + ], + }, + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + ignoreHTTPSErrors: true, + launchOptions: { + firefoxUserPrefs: { + "permissions.default.microphone": 1, + "permissions.default.camera": 1, + }, + }, + }, + }, + + // No safari for now, until I find a solution to fix `Not allowed to request resource` due to calling + // clear http to the homeserver + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "yarn dev", + url: "https://localhost:3000", + reuseExistingServer: !process.env.CI, + ignoreHTTPSErrors: true, + }, +}); diff --git a/playwright/create-call.spec.ts b/playwright/create-call.spec.ts new file mode 100644 index 00000000..759cd2db --- /dev/null +++ b/playwright/create-call.spec.ts @@ -0,0 +1,55 @@ +/* +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"; + +test("Start a new call then leave and show the feedback screen", async ({ + page, +}) => { + await page.goto("/"); + + await page.getByTestId("home_callName").click(); + await page.getByTestId("home_callName").fill("HelloCall"); + await page.getByTestId("home_displayName").click(); + await page.getByTestId("home_displayName").fill("John Doe"); + await page.getByTestId("home_go").click(); + + await expect(page.locator("video")).toBeVisible(); + await expect(page.getByTestId("lobby_joinCall")).toBeVisible(); + + // Check the button toolbar + // await expect(page.getByRole('button', { name: 'Mute microphone' })).toBeVisible(); + // await expect(page.getByRole('button', { name: 'Stop video' })).toBeVisible(); + await expect(page.getByRole("button", { name: "Settings" })).toBeVisible(); + await expect(page.getByRole("button", { name: "End call" })).toBeVisible(); + + // Join the call + await page.getByTestId("lobby_joinCall").click(); + + // Ensure that the call is connected + await page + .locator("div") + .filter({ hasText: /^HelloCall$/ }) + .click(); + // Check the number of participants + await expect(page.locator("div").filter({ hasText: /^1$/ })).toBeVisible(); + // The tooltip with the name should be visible + await expect(page.getByTestId("name_tag")).toContainText("John Doe"); + + // leave the call + await page.getByTestId("incall_leave").click(); + await expect(page.getByRole("heading")).toContainText( + "John Doe, your call has ended. How did it go?", + ); + await expect(page.getByRole("main")).toContainText( + "Why not finish by setting up a password to keep your account?", + ); + + await expect( + page.getByRole("link", { name: "Not now, return to home screen" }), + ).toBeVisible(); +}); diff --git a/playwright/landing.spec.ts b/playwright/landing.spec.ts new file mode 100644 index 00000000..b22a037e --- /dev/null +++ b/playwright/landing.spec.ts @@ -0,0 +1,30 @@ +/* +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 { test, expect } from "@playwright/test"; + +test("has title", async ({ page }) => { + await page.goto("/"); + + await expect(page).toHaveTitle(/Element Call/); +}); + +test("Landing page", async ({ page }) => { + await page.goto("/"); + + // There should be a login button in the header + await expect(page.getByRole("link", { name: "Log In" })).toBeVisible(); + + await expect( + page.getByRole("heading", { name: "Start new call" }), + ).toBeVisible(); + + await expect(page.getByTestId("home_callName")).toBeVisible(); + await expect(page.getByTestId("home_displayName")).toBeVisible(); + + await expect(page.getByTestId("home_go")).toBeVisible(); +}); diff --git a/vitest.config.js b/vitest.config.js index 68fef5be..a6c3107f 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -13,6 +13,7 @@ export default defineConfig((configEnv) => }, }, setupFiles: ["src/vitest.setup.ts"], + include: ["src/**/*.test.ts", "src/**/*.test.tsx"], coverage: { reporter: ["html", "json"], include: ["src/"], @@ -21,6 +22,7 @@ export default defineConfig((configEnv) => "src/utils/test.ts", "src/utils/test-viewmodel.ts", "src/utils/test-fixtures.ts", + "playwright/**", ], }, }, diff --git a/yarn.lock b/yarn.lock index 5c15cdd7..3316e6e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3054,6 +3054,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.51.0": + version: 1.51.0 + resolution: "@playwright/test@npm:1.51.0" + dependencies: + playwright: "npm:1.51.0" + bin: + playwright: cli.js + checksum: 10c0/ae83dd2c3a32133de58f44a9dbcd73a8059155ebd8acc736ba8bd0a7ca99b194afe2e8f5a500861d18b1c8f06b4e4ea8de4a2402297c59053d4becc404b47e0a + languageName: node + linkType: hard + "@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": version: 1.1.2 resolution: "@protobufjs/aspromise@npm:1.1.2" @@ -6851,6 +6862,7 @@ __metadata: "@opentelemetry/sdk-trace-base": "npm:^1.25.1" "@opentelemetry/sdk-trace-web": "npm:^1.9.1" "@opentelemetry/semantic-conventions": "npm:^1.25.1" + "@playwright/test": "npm:^1.51.0" "@radix-ui/react-dialog": "npm:^1.0.4" "@radix-ui/react-slider": "npm:^1.1.2" "@radix-ui/react-visually-hidden": "npm:^1.0.3" @@ -7946,6 +7958,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -7956,6 +7978,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -10266,6 +10297,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.51.0": + version: 1.51.0 + resolution: "playwright-core@npm:1.51.0" + bin: + playwright-core: cli.js + checksum: 10c0/8f5de23088c5e97c00327f356b17e0223181e921baf99f4e38d9a3b18d0693db288f8b5389e96d0cb4a1b55f03870f140dd7346128a0c02ce36d11eb92153841 + languageName: node + linkType: hard + +"playwright@npm:1.51.0": + version: 1.51.0 + resolution: "playwright@npm:1.51.0" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.51.0" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10c0/e8509ea500e03e8051fd243f2347ac3196ff8dde4c20ae3aba4cf723e2b647a0158d209fba062995dab90590229a483d723562cf1ea8b2fc11698617027416fd + languageName: node + linkType: hard + "pluralize@npm:^8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0"