/* Copyright 2024 New Vector Ltd. Copyright 2022, 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 * as fs from "node:fs"; import type { Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { ElementAppPage } from "../../pages/ElementAppPage"; import { Credentials } from "../../plugins/homeserver"; const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker"; const STICKER_PICKER_WIDGET_NAME = "Fake Stickers"; const STICKER_NAME = "Test Sticker"; const ROOM_NAME_1 = "Sticker Test"; const ROOM_NAME_2 = "Sticker Test Two"; const STICKER_IMAGE = fs.readFileSync("playwright/sample-files/riot.png"); function getStickerMessage(contentUri: string, mimetype: string): string { return JSON.stringify({ action: "m.sticker", api: "fromWidget", data: { name: "teststicker", description: STICKER_NAME, file: "test.png", content: { body: STICKER_NAME, info: { h: 480, mimetype: mimetype, size: 13818, w: 480, }, msgtype: "m.sticker", url: contentUri, }, }, requestId: "1", widgetId: STICKER_PICKER_WIDGET_ID, }); } function getWidgetHtml(contentUri: string, mimetype: string) { const stickerMessage = getStickerMessage(contentUri, mimetype); return ` Fake Sticker Picker `; } async function openStickerPicker(app: ElementAppPage) { const options = await app.openMessageComposerOptions(); await options.getByRole("menuitem", { name: "Sticker" }).click(); } async function sendStickerFromPicker(page: Page) { const iframe = page.frameLocator(`iframe[title="${STICKER_PICKER_WIDGET_NAME}"]`); await iframe.locator("#sendsticker").click(); // Sticker picker should close itself after sending. await expect(page.locator(".mx_AppTileFullWidth#stickers")).not.toBeVisible(); } async function expectTimelineSticker(page: Page, roomId: string, contentUri: string) { const contentId = contentUri.split("/").slice(-1)[0]; // Make sure it's in the right room await expect(page.locator(".mx_EventTile_sticker > a")).toHaveAttribute("href", new RegExp(`/${roomId}/`)); // Make sure the image points at the sticker image. We will briefly show it // using the thumbnail URL, but as soon as that fails, we will switch to the // download URL. await expect(page.locator(`img[alt="${STICKER_NAME}"]`)).toHaveAttribute( "src", new RegExp(`/localhost/${contentId}`), ); } async function expectFileTile(page: Page, roomId: string, contentUri: string) { await expect(page.locator(".mx_MFileBody_info_filename")).toContainText(STICKER_NAME); } async function setWidgetAccountData( app: ElementAppPage, user: Credentials, stickerPickerUrl: string, provideCreatorUserId: boolean = true, ) { await app.client.setAccountData("m.widgets", { [STICKER_PICKER_WIDGET_ID]: { content: { type: "m.stickerpicker", name: STICKER_PICKER_WIDGET_NAME, url: stickerPickerUrl, creatorUserId: provideCreatorUserId ? user.userId : undefined, }, sender: user.userId, state_key: STICKER_PICKER_WIDGET_ID, type: "m.widget", id: STICKER_PICKER_WIDGET_ID, }, }); } test.describe("Stickers", () => { test.use({ displayName: "Sally", room: async ({ app }, use) => { const roomId = await app.client.createRoom({ name: ROOM_NAME_1 }); await use({ roomId }); }, }); // We spin up a web server for the sticker picker so that we're not testing to see if // sysadmins can deploy sticker pickers on the same Element domain - we actually want // to make sure that cross-origin postMessage works properly. This makes it difficult // to write the test though, as we have to juggle iframe logistics. // // See sendStickerFromPicker() for more detail on iframe comms. let stickerPickerUrl: string; test("should send a sticker to multiple rooms", async ({ webserver, page, app, user, room }) => { const roomId2 = await app.client.createRoom({ name: ROOM_NAME_2 }); const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" }); const widgetHtml = getWidgetHtml(contentUri, "image/png"); stickerPickerUrl = webserver.start(widgetHtml); setWidgetAccountData(app, user, stickerPickerUrl); await app.viewRoomByName(ROOM_NAME_1); await expect(page).toHaveURL(`/#/room/${room.roomId}`); await openStickerPicker(app); await sendStickerFromPicker(page); await expectTimelineSticker(page, room.roomId, contentUri); // Ensure that when we switch to a different room that the sticker // goes to the right place await app.viewRoomByName(ROOM_NAME_2); await expect(page).toHaveURL(`/#/room/${roomId2}`); await openStickerPicker(app); await sendStickerFromPicker(page); await expectTimelineSticker(page, roomId2, contentUri); }); test("should handle a sticker picker widget missing creatorUserId", async ({ webserver, page, app, user, room, }) => { const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "image/png" }); const widgetHtml = getWidgetHtml(contentUri, "image/png"); stickerPickerUrl = webserver.start(widgetHtml); setWidgetAccountData(app, user, stickerPickerUrl, false); await app.viewRoomByName(ROOM_NAME_1); await expect(page).toHaveURL(`/#/room/${room.roomId}`); await openStickerPicker(app); await sendStickerFromPicker(page); await expectTimelineSticker(page, room.roomId, contentUri); }); test("should render invalid mimetype as a file", async ({ webserver, page, app, user, room }) => { const { content_uri: contentUri } = await app.client.uploadContent(STICKER_IMAGE, { type: "application/octet-stream", }); const widgetHtml = getWidgetHtml(contentUri, "application/octet-stream"); stickerPickerUrl = webserver.start(widgetHtml); setWidgetAccountData(app, user, stickerPickerUrl); await app.viewRoomByName(ROOM_NAME_1); await expect(page).toHaveURL(`/#/room/${room.roomId}`); await openStickerPicker(app); await sendStickerFromPicker(page); await expectFileTile(page, room.roomId, contentUri); }); });