Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ export default defineConfig<{}, WorkerOptions>({
use: {
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
video: "retain-on-failure",
video: "on-first-retry",
baseURL,
trace: "on-first-retry",
},
webServer: {
command: process.env.CI ? "pnpm exec serve -p 8080 -L ./webapp" : "nx --outputStyle stream start",
command: process.env.CI ? "pnpm exec serve -p 8080 -L -u ./webapp" : "nx --outputStyle stream start",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compression making a real difference does surprise me!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably negligible, but quite moot on a loopback interface anyhow

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also to free up CPU cycles to hopefully run two workers in parallel per shard without causing instability.

url: `${baseURL}/config.json`,
reuseExistingServer: true,
timeout: (process.env.CI ? 30 : 120) * 1000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ test.describe("Cryptography", function () {
await app.viewRoomByName("Test room");

// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000);
await expect(
page
.locator(".mx_EventTile")
.filter({ hasText: "You need to verify this device for access to historical messages" }),
).toHaveCount(2);

// There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all();
Expand Down
11 changes: 3 additions & 8 deletions apps/web/playwright/e2e/login/login-consent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,11 @@ test.use({
const credentials = await homeserver.registerUser(username, password, displayName);
console.log(`Registered test user @user:localhost with displayname ${displayName}`);

// No need to restart the homeserver afterwards: the context fixture above restarts it before each test
await use({
...credentials,
displayName,
});

// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
await homeserver.restart();
},
});

Expand Down Expand Up @@ -328,9 +326,6 @@ test.describe("Login", () => {
// Create a different device which is cross-signed, meaning we need to verify this device
await createBot(page, homeserver, credentials, true);

// Wait to avoid homeserver rate limit on logins
await page.waitForTimeout(100);

// Load the page and see that we are asked to verify
await page.goto("/#/welcome");
await login(page, homeserver, credentials);
Expand Down Expand Up @@ -413,7 +408,8 @@ test.describe("Login", () => {
// Given we have an encrypted chat
await page.goto("/#/welcome");
await login(page, homeserver, credentials);
await page.waitForTimeout(5000);
await expect(page).toHaveURL(/\/#\/home$/);
await expect(page.getByRole("navigation", { name: "Room list" })).toBeVisible();
await createRoom(page, "My encrypted room", true);
await logOutOfElement(page, true);

Expand Down Expand Up @@ -442,7 +438,6 @@ test.describe("Login", () => {
async ({ page, homeserver, credentials }) => {
// Given we have another device
await createBot(page, homeserver, credentials, true);
await page.waitForTimeout(100); // Avoid server rate limit

// When we log in and ask to reset our identity
await page.goto("/#/welcome");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ test.describe("1:1 chat room", () => {
await expect(
page.getByRole("group", { name: "Rooms" }).locator(".mx_RoomTile").getByText(user2.displayName!),
).not.toBeVisible();
await page.waitForTimeout(500); // avoid race condition with routing
// avoid race condition with routing: wait for the app to navigate away from the left room
await expect(page).toHaveURL(/#\/home/);

// open new 1:1 chat room
await page.goto(`/#/user/${user2.userId}?action=chat`);
Expand Down
32 changes: 22 additions & 10 deletions apps/web/playwright/e2e/pinned-messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,31 @@
const roomId = room.roomId;

for (const message of messages) {
await cli.sendMessage(roomId, { body: message, msgtype: "m.text" });

// TODO: without this wait, some tests that send lots of messages flake
// from time to time. I (andyb) have done some investigation, but it
// needs more work to figure out. The messages do arrive over sync, but
// they never appear in the timeline, and they never fire a
// Room.timeline event. I think this only happens with events that refer
// to other events (e.g. replies), so it might be caused by the
// referring event arriving before the referred-to event.
await this.page.waitForTimeout(100);
const { event_id: eventId } = await cli.sendMessage(roomId, { body: message, msgtype: "m.text" });

// Wait for our client to receive the event before sending the next one. Without this, tests that
// send lots of messages flake: the messages arrive over sync but sometimes never appear in the
// timeline, which seems to happen when an event that refers to another arrives before its target.
await this.waitForEventReceived(roomId, eventId);

Check warning on line 70 in apps/web/playwright/e2e/pinned-messages/index.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected `await` inside a loop.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AaGr0ZFne9fGg4lbD51L&open=AaGr0ZFne9fGg4lbD51L&pullRequest=35711
}
}

/**
* Wait for the app's client to have received the event with the supplied ID in the supplied room.
*/
private async waitForEventReceived(roomId: string, eventId: string) {
await expect
.poll(
() =>
this.app.client.evaluate(
(cli, { roomId, eventId }) => !!cli.getRoom(roomId)?.findEventById(eventId),
{ roomId, eventId },
),
{ message: `event ${eventId} was not received by the client` },
)
.toBe(true);
}

/**
* Open the room with the supplied name.
*/
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/e2e/room/room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ test.describe("Room Directory", () => {
await app.viewRoomById(roomBId);

// Let the app settle to avoid flakiness
await page.waitForTimeout(500);
await expect(page.locator(".mx_RoomHeader").getByRole("heading", { level: 1 })).toHaveText("Room B");

// Display Room A
await app.viewRoomById(roomAId);
Expand Down
26 changes: 5 additions & 21 deletions apps/web/playwright/e2e/spotlight/spotlight.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ async function startDM(app: ElementAppPage, page: Page, name: string): Promise<v
const spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(name);
await page.waitForTimeout(1000); // wait for the dialog code to settle
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
const result = spotlight.results;
await expect(result).toHaveCount(1);
Expand Down Expand Up @@ -107,7 +106,6 @@ test.describe("Spotlight", () => {

test("should be able to add and remove filters via keyboard", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(1000); // wait for the dialog to settle, otherwise our keypresses might race with an update

// initially, public spaces should be highlighted (because there are no other suggestions)
await expect(spotlight.dialog.locator("#mx_SpotlightDialog_button_explorePublicSpaces")).toHaveAttribute(
Expand All @@ -120,7 +118,11 @@ test.describe("Spotlight", () => {
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).toHaveText("Public spaces");
await spotlight.searchBox.press("Backspace");
await expect(spotlight.dialog.locator(".mx_SpotlightDialog_filter")).not.toBeAttached();
await page.waitForTimeout(200); // Again, wait to settle so keypresses arrive correctly
// Wait for the default selection to return so the next keypress isn't lost to a re-render
await expect(spotlight.dialog.locator("#mx_SpotlightDialog_button_explorePublicSpaces")).toHaveAttribute(
"aria-selected",
"true",
);

await spotlight.searchBox.press("ArrowDown");
await expect(spotlight.dialog.locator("#mx_SpotlightDialog_button_explorePublicRooms")).toHaveAttribute(
Expand All @@ -135,7 +137,6 @@ test.describe("Spotlight", () => {

test("should find joined rooms", async ({ page, app, room1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.search(room1.name);
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
Expand All @@ -147,7 +148,6 @@ test.describe("Spotlight", () => {

test("should find known public rooms", async ({ page, app, room1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room1.name);
const resultLocator = spotlight.results;
Expand All @@ -161,7 +161,6 @@ test.describe("Spotlight", () => {

test("should find unknown public rooms", async ({ page, app, room2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room2.name);
const resultLocator = spotlight.results;
Expand All @@ -176,7 +175,6 @@ test.describe("Spotlight", () => {

test("should find unknown public world readable rooms", async ({ page, app, room3 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3.name);
const resultLocator = spotlight.results;
Expand All @@ -193,7 +191,6 @@ test.describe("Spotlight", () => {
// We obviously don’t have federation or bridges in local e2e tests
test.skip("should find unknown public rooms on other homeservers", async ({ page, app, room3 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.PublicRooms);
await spotlight.search(room3.name);
await page.locator("[aria-haspopup=true][role=button]").click();
Expand All @@ -204,7 +201,6 @@ test.describe("Spotlight", () => {
.locator("..")
.locator("[role=menuitemradio]")
.click();
await page.waitForTimeout(500); // wait for the dialog to settle

const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
Expand All @@ -214,7 +210,6 @@ test.describe("Spotlight", () => {

test("should find known people", async ({ page, app, bot1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials!.displayName!);
const resultLocator = spotlight.results;
Expand All @@ -233,7 +228,6 @@ test.describe("Spotlight", () => {
*/
test("should find unknown people", async ({ page, app, bot2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials!.displayName!);
const resultLocator = spotlight.results;
Expand All @@ -249,7 +243,6 @@ test.describe("Spotlight", () => {

// Starting a DM with ByteBot (will be turned into a group dm later)
let spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials!.displayName!);
let resultLocator = spotlight.results;
Expand Down Expand Up @@ -290,7 +283,6 @@ test.describe("Spotlight", () => {
spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials!.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(
Expand All @@ -303,7 +295,6 @@ test.describe("Spotlight", () => {
spotlight = await app.openSpotlight();
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials!.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(
Expand All @@ -317,7 +308,6 @@ test.describe("Spotlight", () => {
// Test against https://github.com/vector-im/element-web/issues/22851
test("should show each person result only once", async ({ page, app, bot1 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);

// 2 rounds of search to simulate the bug conditions. Specifically, the first search
Expand All @@ -328,7 +318,6 @@ test.describe("Spotlight", () => {
for (let i = 0; i < 2; i++) {
console.log("Iteration: " + i);
await spotlight.search(bot1.credentials!.userId);
await page.waitForTimeout(1000); // wait for the dialog to settle
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
await expect(resultLocator.first()).toContainText(bot1.credentials!.userId);
Expand All @@ -337,10 +326,8 @@ test.describe("Spotlight", () => {

test("should allow opening group chat dialog", async ({ page, app, bot2 }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot2.credentials!.displayName!);
await page.waitForTimeout(3000); // wait for the dialog to settle

const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
Expand All @@ -362,18 +349,15 @@ test.describe("Spotlight", () => {
await startDM(app, page, bot1.credentials!.displayName!);
await page.goto("/#/home");
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search(bot1.credentials!.displayName!);
await page.waitForTimeout(3000); // wait for the dialog to settle
await expect(spotlight.dialog.locator(".mx_Spinner")).not.toBeAttached();
const resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(1);
});

test("should be able to navigate results via keyboard", async ({ page, app }) => {
const spotlight = await app.openSpotlight();
await page.waitForTimeout(500); // wait for the dialog to settle
await spotlight.filter(Filter.People);
await spotlight.search("b");

Expand Down
27 changes: 21 additions & 6 deletions apps/web/playwright/e2e/timeline/timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ const expectAvatar = async (cli: Client, e: Locator, avatarUrl: string): Promise
await expect(e.locator(".mx_BaseAvatar img")).toHaveAttribute("src", url!);
};

/** Wait for the app's client to have seen its own profile change in the room's member state. */
const waitForOwnProfile = async (app: ElementAppPage, roomId: string, displayName: string, avatarUrl: string) => {
await expect
.poll(() =>
app.client.evaluate(
(client, { roomId, displayName, avatarUrl }) => {
const member = client.getRoom(roomId)?.getMember(client.getSafeUserId());
return member?.rawDisplayName === displayName && member.getMxcAvatarUrl() === avatarUrl;
},
{ roomId, displayName, avatarUrl },
),
)
.toBe(true);
};

const sendEvent = async (client: Client, roomId: string, html = false): Promise<ISendEventResponse> => {
const content: IContent = {
msgtype: "m.text" as MsgType,
Expand Down Expand Up @@ -105,9 +120,9 @@ test.describe("Timeline", () => {
await sendEvent(app.client, room.roomId);
await app.client.setDisplayName("Alan (away)");
await app.client.setAvatarUrl(newAvatarUrl);
// XXX: If we send the second event too quickly, there won't be
// If we send the second event too quickly, there won't be
// enough time for the client to register the profile change
await page.waitForTimeout(500);
await waitForOwnProfile(app, room.roomId, NEW_NAME, newAvatarUrl);
await sendEvent(app.client, room.roomId);
await app.viewRoomByName(ROOM_NAME);

Expand All @@ -124,9 +139,9 @@ test.describe("Timeline", () => {
await sendEvent(app.client, room.roomId);
await app.client.setDisplayName(NEW_NAME);
await app.client.setAvatarUrl(newAvatarUrl);
// XXX: If we send the second event too quickly, there won't be
// If we send the second event too quickly, there won't be
// enough time for the client to register the profile change
await page.waitForTimeout(500);
await waitForOwnProfile(app, room.roomId, NEW_NAME, newAvatarUrl);
await sendEvent(app.client, room.roomId);
await app.viewRoomByName(ROOM_NAME);

Expand Down Expand Up @@ -986,7 +1001,7 @@ test.describe("Timeline", () => {
await composerOptions.getByRole("menuitem", { name: "Voice Message" }).click();

// Record an empty message
await page.waitForTimeout(2000);
await expect(app.getComposer().getByText(/^0?0:0[2-9]$/)).toBeVisible();

const roomViewBody = page.locator(".mx_RoomView_body");
await roomViewBody
Expand All @@ -1004,7 +1019,7 @@ test.describe("Timeline", () => {
await composerOptions.getByRole("menuitem", { name: "Voice Message" }).click();

// Record an empty message
await page.waitForTimeout(3000);
await expect(app.getComposer().getByText(/^0?0:0[3-9]$/)).toBeVisible();

const roomViewBody = page.locator(".mx_RoomView_body");
await roomViewBody
Expand Down
4 changes: 3 additions & 1 deletion apps/web/playwright/pages/Spotlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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 type { Locator, Page } from "@playwright/test";
import { type Locator, type Page, expect } from "@playwright/test";
import { CommandOrControl } from "../e2e/utils";

export enum Filter {
Expand All @@ -27,6 +27,8 @@ export class Spotlight {
await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
}
await this.page.keyboard.press(`${CommandOrControl}+KeyK`);
// Wait for the dialog to finish opening, otherwise keypresses can race with its updates
await expect(this.searchBox.getByRole("textbox", { name: "Search" })).toBeFocused();
}

public async filter(filter: Filter) {
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/vector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import { parseAppUrl } from "./url_utils";
import { preloadCryptoIfLoggedIn } from "./preloadCrypto";
import "./modernizr.cjs";
import { polyfillTouchEvent } from "../@types/polyfill";

Expand Down Expand Up @@ -107,6 +108,8 @@
// the browser to use as much parallelism as it can.
// Load parallelism is based on research in https://github.com/element-hq/element-web/issues/12253
async function start(): Promise<void> {
preloadCryptoIfLoggedIn();

Check warning on line 111 in apps/web/src/vector/index.ts

View workflow job for this annotation

GitHub Actions / Tests

Uncovered Line

apps/web/src/vector/index.ts line 111 is not covered by tests

if (shouldPolyFillIntlSegmenter()) {
await import(/* webpackChunkName: "intl-segmenter-polyfill" */ "@formatjs/intl-segmenter/polyfill-force.js");
}
Expand Down
Loading
Loading